Skip to content

Commit b6f0f3c

Browse files
authored
Allowing renenerate ingester tokens (#6063)
* Allowing renenerate ingester tokens Signed-off-by: alanprot <[email protected]> * Changelog + lint Signed-off-by: alanprot <[email protected]> --------- Signed-off-by: alanprot <[email protected]>
1 parent d1b6d26 commit b6f0f3c

File tree

5 files changed

+99
-0
lines changed

5 files changed

+99
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* [ENHANCEMENT] Upgrade Alpine to 3.19. #6014
2020
* [ENHANCEMENT] Upgrade go to 1.21.11 #6014
2121
* [ENHANCEMENT] Ingester: Add a new experimental `-ingester.labels-string-interning-enabled` flag to enable string interning for metrics labels. #6057
22+
* [ENHANCEMENT] Ingester: Add link to renew 10% of the ingesters tokens in the admin page. #6063
2223
* [BUGFIX] Configsdb: Fix endline issue in db password. #5920
2324
* [BUGFIX] Ingester: Fix `user` and `type` labels for the `cortex_ingester_tsdb_head_samples_appended_total` TSDB metric. #5952
2425
* [BUGFIX] Querier: Enforce max query length check for `/api/v1/series` API even though `ignoreMaxQueryLength` is set to true. #6018

pkg/api/api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ type Ingester interface {
283283
client.IngesterServer
284284
FlushHandler(http.ResponseWriter, *http.Request)
285285
ShutdownHandler(http.ResponseWriter, *http.Request)
286+
RenewTokenHandler(http.ResponseWriter, *http.Request)
286287
Push(context.Context, *cortexpb.WriteRequest) (*cortexpb.WriteResponse, error)
287288
}
288289

@@ -292,8 +293,10 @@ func (a *API) RegisterIngester(i Ingester, pushConfig distributor.Config) {
292293

293294
a.indexPage.AddLink(SectionDangerous, "/ingester/flush", "Trigger a Flush of data from Ingester to storage")
294295
a.indexPage.AddLink(SectionDangerous, "/ingester/shutdown", "Trigger Ingester Shutdown (Dangerous)")
296+
a.indexPage.AddLink(SectionDangerous, "/ingester/renewTokens", "Renew Ingester Tokens (10%)")
295297
a.RegisterRoute("/ingester/flush", http.HandlerFunc(i.FlushHandler), false, "GET", "POST")
296298
a.RegisterRoute("/ingester/shutdown", http.HandlerFunc(i.ShutdownHandler), false, "GET", "POST")
299+
a.RegisterRoute("/ingester/renewTokens", http.HandlerFunc(i.RenewTokenHandler), false, "GET", "POST")
297300
a.RegisterRoute("/ingester/push", push.Handler(pushConfig.MaxRecvMsgSize, a.sourceIPs, i.Push), true, "POST") // For testing and debugging.
298301

299302
// Legacy Routes

pkg/ingester/ingester.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,11 @@ func (i *Ingester) updateActiveSeries(ctx context.Context) {
971971
}
972972
}
973973

974+
func (i *Ingester) RenewTokenHandler(w http.ResponseWriter, r *http.Request) {
975+
i.lifecycler.RenewTokens(0.1, r.Context())
976+
w.WriteHeader(http.StatusNoContent)
977+
}
978+
974979
// ShutdownHandler triggers the following set of operations in order:
975980
// - Change the state of ring to stop accepting writes.
976981
// - Flush all the chunks.

pkg/ring/lifecycler.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,52 @@ func (i *Lifecycler) initRing(ctx context.Context) error {
706706
return err
707707
}
708708

709+
func (i *Lifecycler) RenewTokens(ratio float64, ctx context.Context) {
710+
if ratio > 1 {
711+
ratio = 1
712+
}
713+
err := i.KVStore.CAS(ctx, i.RingKey, func(in interface{}) (out interface{}, retry bool, err error) {
714+
if in == nil {
715+
return in, false, nil
716+
}
717+
718+
ringDesc := in.(*Desc)
719+
_, ok := ringDesc.Ingesters[i.ID]
720+
721+
if !ok {
722+
return in, false, nil
723+
}
724+
725+
tokensToBeRenewed := int(float64(i.cfg.NumTokens) * ratio)
726+
ringTokens, _ := ringDesc.TokensFor(i.ID)
727+
728+
// Removing random tokens
729+
for i := 0; i < tokensToBeRenewed; i++ {
730+
if len(ringTokens) == 0 {
731+
break
732+
}
733+
index := mathrand.Int() % len(ringTokens)
734+
ringTokens = append(ringTokens[:index], ringTokens[index+1:]...)
735+
}
736+
737+
needTokens := i.cfg.NumTokens - len(ringTokens)
738+
level.Info(i.logger).Log("msg", "renewing new tokens", "count", needTokens, "ring", i.RingName)
739+
ringDesc.AddIngester(i.ID, i.Addr, i.Zone, ringTokens, i.GetState(), i.getRegisteredAt())
740+
newTokens := i.tg.GenerateTokens(ringDesc, i.ID, i.Zone, needTokens, true)
741+
742+
ringTokens = append(ringTokens, newTokens...)
743+
sort.Sort(ringTokens)
744+
745+
ringDesc.AddIngester(i.ID, i.Addr, i.Zone, ringTokens, i.GetState(), i.getRegisteredAt())
746+
i.setTokens(ringTokens)
747+
return ringDesc, true, nil
748+
})
749+
750+
if err != nil {
751+
level.Error(i.logger).Log("msg", "failed to regenerate tokens", "ring", i.RingName, "err", err)
752+
}
753+
}
754+
709755
// Verifies that tokens that this ingester has registered to the ring still belong to it.
710756
// Gossiping ring may change the ownership of tokens in case of conflicts.
711757
// If ingester doesn't own its tokens anymore, this method generates new tokens and puts them to the ring.

pkg/ring/lifecycler_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/pkg/errors"
1212
"github.com/stretchr/testify/assert"
1313
"github.com/stretchr/testify/require"
14+
"golang.org/x/exp/slices"
1415

1516
"github.com/cortexproject/cortex/pkg/ring/kv/consul"
1617
"github.com/cortexproject/cortex/pkg/util/flagext"
@@ -75,6 +76,49 @@ func TestLifecycler_JoinShouldNotBlock(t *testing.T) {
7576
}
7677
}
7778

79+
func TestLifecycler_RenewTokens(t *testing.T) {
80+
ringStore, closer := consul.NewInMemoryClient(GetCodec(), log.NewNopLogger(), nil)
81+
t.Cleanup(func() { assert.NoError(t, closer.Close()) })
82+
83+
var ringConfig Config
84+
flagext.DefaultValues(&ringConfig)
85+
ringConfig.KVStore.Mock = ringStore
86+
87+
ctx := context.Background()
88+
lifecyclerConfig := testLifecyclerConfig(ringConfig, "ing1")
89+
lifecyclerConfig.HeartbeatPeriod = 100 * time.Millisecond
90+
lifecyclerConfig.NumTokens = 512
91+
92+
l1, err := NewLifecycler(lifecyclerConfig, &nopFlushTransferer{}, "ingester", ringKey, true, true, log.NewNopLogger(), nil)
93+
require.NoError(t, err)
94+
95+
require.NoError(t, services.StartAndAwaitRunning(ctx, l1))
96+
defer services.StopAndAwaitTerminated(ctx, l1) // nolint:errcheck
97+
98+
waitRingInstance(t, 3*time.Second, l1, func(instance InstanceDesc) error {
99+
if instance.State != ACTIVE {
100+
return errors.New("should be active")
101+
}
102+
return nil
103+
})
104+
105+
originalTokens := l1.getTokens()
106+
require.Len(t, originalTokens, 512)
107+
require.IsIncreasing(t, originalTokens)
108+
l1.RenewTokens(0.1, ctx)
109+
newTokens := l1.getTokens()
110+
require.Len(t, newTokens, 512)
111+
require.IsIncreasing(t, newTokens)
112+
diff := 0
113+
for i := 0; i < len(originalTokens); i++ {
114+
if !slices.Contains(originalTokens, newTokens[i]) {
115+
diff++
116+
}
117+
}
118+
119+
require.Equal(t, 51, diff)
120+
}
121+
78122
func TestLifecycler_DefferedJoin(t *testing.T) {
79123
ringStore, closer := consul.NewInMemoryClient(GetCodec(), log.NewNopLogger(), nil)
80124
t.Cleanup(func() { assert.NoError(t, closer.Close()) })

0 commit comments

Comments
 (0)