-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Memberlist: Optimise receive path by not cloning current ring state. (#…
…76) When memberlist is used as the backing store for the ring, processing updates can be costly if the ring state is very large. This is partly due to the ring state being cloned upon the arrival of every message. It is believed that this can be avoided. Additionally, the reduced memory allocations will reduce the amount of work required by the GC. The included benchmark shows a good improvement: Command: ``` dskit/bench$ go test -v -bench BenchmarkMemberlistReceiveWithRingDesc -benchmem -cpu 6 ``` ``` name old time/op new time/op delta MemberlistReceiveWithRingDesc-6 1.76ms ± 5% 1.01ms ± 6% -42.32% (p=0.000 n=10+9) name old alloc/op new alloc/op delta MemberlistReceiveWithRingDesc-6 566kB ± 0% 254kB ± 0% -55.09% (p=0.000 n=9+10) name old allocs/op new allocs/op delta MemberlistReceiveWithRingDesc-6 1.86k ± 0% 0.64k ± 0% -65.83% (p=0.000 n=10+10) ```
- Loading branch information
Showing
4 changed files
with
121 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package bench | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/grafana/dskit/flagext" | ||
"github.com/grafana/dskit/kv/codec" | ||
"github.com/grafana/dskit/kv/memberlist" | ||
"github.com/grafana/dskit/ring" | ||
"github.com/grafana/dskit/services" | ||
) | ||
|
||
type dnsProviderMock struct { | ||
resolved []string | ||
} | ||
|
||
func (p *dnsProviderMock) Resolve(ctx context.Context, addrs []string) error { | ||
p.resolved = addrs | ||
return nil | ||
} | ||
|
||
func (p dnsProviderMock) Addresses() []string { | ||
return p.resolved | ||
} | ||
|
||
func encodeMessage(b *testing.B, key string, d *ring.Desc) []byte { | ||
c := ring.GetCodec() | ||
val, err := c.Encode(d) | ||
require.NoError(b, err) | ||
|
||
kvPair := memberlist.KeyValuePair{ | ||
Key: key, | ||
Value: val, | ||
Codec: c.CodecID(), | ||
} | ||
|
||
ser, err := kvPair.Marshal() | ||
require.NoError(b, err) | ||
return ser | ||
} | ||
|
||
func generateUniqueTokens(ingester, numTokens int) []uint32 { | ||
// Generate unique tokens without using ring.GenerateTokens in order to not | ||
// rely on random number generation. Also, because generating unique tokens | ||
// with GenerateTokens can be quite expensive, it pollutes the CPU profile | ||
// to the point of being useless. | ||
tokens := make([]uint32, numTokens) | ||
for i := range tokens { | ||
tokens[i] = uint32((ingester * 100000) + (i * 10)) | ||
} | ||
return tokens | ||
} | ||
|
||
// Benchmark the memberlist receive path when it is being used as the ring backing store. | ||
func BenchmarkMemberlistReceiveWithRingDesc(b *testing.B) { | ||
c := ring.GetCodec() | ||
|
||
var cfg memberlist.KVConfig | ||
flagext.DefaultValues(&cfg) | ||
cfg.TCPTransport = memberlist.TCPTransportConfig{ | ||
BindAddrs: []string{"localhost"}, | ||
} | ||
cfg.Codecs = []codec.Codec{c} | ||
|
||
mkv := memberlist.NewKV(cfg, log.NewNopLogger(), &dnsProviderMock{}, prometheus.NewPedanticRegistry()) | ||
require.NoError(b, services.StartAndAwaitRunning(context.Background(), mkv)) | ||
defer services.StopAndAwaitTerminated(context.Background(), mkv) //nolint:errcheck | ||
|
||
// Build the initial ring state: | ||
// - The ring isn't actually in use, so the fields such as address/zone are not important. | ||
// - The number of keys in the store has no impact for this test, so simulate a single ring. | ||
// - The number of instances in the ring does have a big impact. | ||
const numInstances = 600 | ||
const numTokens = 128 | ||
{ | ||
initialDesc := ring.NewDesc() | ||
for i := 0; i < numInstances; i++ { | ||
tokens := generateUniqueTokens(i, numTokens) | ||
initialDesc.AddIngester(fmt.Sprintf("instance-%d", i), "127.0.0.1", "zone", tokens, ring.ACTIVE, time.Now()) | ||
} | ||
// Send a single update to populate the store. | ||
msg := encodeMessage(b, "ring", initialDesc) | ||
mkv.NotifyMsg(msg) | ||
} | ||
|
||
// Pre-encode some payloads. It's not significant what the payloads actually | ||
// update in the ring, though it may be important for future optimisations. | ||
testMsgs := make([][]byte, 100) | ||
for i := range testMsgs { | ||
testDesc := ring.NewDesc() | ||
testDesc.AddIngester(fmt.Sprintf("instance-%d", i), "127.0.0.1", "zone", nil, ring.ACTIVE, time.Now()) | ||
testMsgs[i] = encodeMessage(b, "ring", testDesc) | ||
} | ||
|
||
b.ResetTimer() | ||
|
||
for i := 0; i < b.N; i++ { | ||
mkv.NotifyMsg(testMsgs[i%len(testMsgs)]) | ||
} | ||
} |