Skip to content

Commit 6c05a9f

Browse files
fix: reduce SetAddrs shards lock contention
Introduces a new lock to make `SetAddrs` calls exclusive. This allows release of shards lock for the duration of potentially long `newRingShards` call. `TestRingSetAddrsContention` observes number of pings increased from <1000 to ~40_000. See #2190 (comment) Updates #2077
1 parent a31c1d6 commit 6c05a9f

File tree

2 files changed

+47
-33
lines changed

2 files changed

+47
-33
lines changed

ring.go

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ type ringSharding struct {
219219
hash ConsistentHash
220220
numShard int
221221
onNewNode []func(rdb *Client)
222+
223+
// ensures exclusive access to SetAddrs so there is no need
224+
// to hold mu for the duration of potentially long shard creation
225+
setAddrsMu sync.Mutex
222226
}
223227

224228
type ringShards struct {
@@ -245,46 +249,62 @@ func (c *ringSharding) OnNewNode(fn func(rdb *Client)) {
245249
// decrease number of shards, that you use. It will reuse shards that
246250
// existed before and close the ones that will not be used anymore.
247251
func (c *ringSharding) SetAddrs(addrs map[string]string) {
248-
c.mu.Lock()
252+
c.setAddrsMu.Lock()
253+
defer c.setAddrsMu.Unlock()
249254

255+
cleanup := func(shards map[string]*ringShard) {
256+
for addr, shard := range shards {
257+
if err := shard.Client.Close(); err != nil {
258+
internal.Logger.Printf(context.Background(), "shard.Close %s failed: %s", addr, err)
259+
}
260+
}
261+
}
262+
263+
c.mu.RLock()
250264
if c.closed {
251-
c.mu.Unlock()
265+
c.mu.RUnlock()
252266
return
253267
}
268+
existing := c.shards
269+
c.mu.RUnlock()
270+
271+
shards, created, unused := c.newRingShards(addrs, existing)
254272

255-
shards, cleanup := c.newRingShards(addrs, c.shards)
273+
c.mu.Lock()
274+
if c.closed {
275+
cleanup(created)
276+
c.mu.Unlock()
277+
return
278+
}
256279
c.shards = shards
257280
c.rebalanceLocked()
258281
c.mu.Unlock()
259282

260-
cleanup()
283+
cleanup(unused)
261284
}
262285

263286
func (c *ringSharding) newRingShards(
264-
addrs map[string]string, existingShards *ringShards,
265-
) (*ringShards, func()) {
266-
shardMap := make(map[string]*ringShard) // indexed by addr
267-
unusedShards := make(map[string]*ringShard) // indexed by addr
268-
269-
if existingShards != nil {
270-
for _, shard := range existingShards.list {
271-
addr := shard.Client.opt.Addr
272-
shardMap[addr] = shard
273-
unusedShards[addr] = shard
274-
}
275-
}
287+
addrs map[string]string, existing *ringShards,
288+
) (shards *ringShards, created, unused map[string]*ringShard) {
289+
290+
shards = &ringShards{m: make(map[string]*ringShard, len(addrs))}
291+
created = make(map[string]*ringShard) // indexed by addr
292+
unused = make(map[string]*ringShard) // indexed by addr
276293

277-
shards := &ringShards{
278-
m: make(map[string]*ringShard),
294+
if existing != nil {
295+
for _, shard := range existing.list {
296+
unused[shard.addr] = shard
297+
}
279298
}
280299

281300
for name, addr := range addrs {
282-
if shard, ok := shardMap[addr]; ok {
301+
if shard, ok := unused[addr]; ok {
283302
shards.m[name] = shard
284-
delete(unusedShards, addr)
303+
delete(unused, addr)
285304
} else {
286305
shard := newRingShard(c.opt, addr)
287306
shards.m[name] = shard
307+
created[addr] = shard
288308

289309
for _, fn := range c.onNewNode {
290310
fn(shard.Client)
@@ -296,13 +316,7 @@ func (c *ringSharding) newRingShards(
296316
shards.list = append(shards.list, shard)
297317
}
298318

299-
return shards, func() {
300-
for addr, shard := range unusedShards {
301-
if err := shard.Client.Close(); err != nil {
302-
internal.Logger.Printf(context.Background(), "shard.Close %s failed: %s", addr, err)
303-
}
304-
}
305-
}
319+
return
306320
}
307321

308322
func (c *ringSharding) List() []*ringShard {

ring_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,15 @@ var _ = Describe("Redis Ring", func() {
124124
})
125125
Expect(ring.Len(), 1)
126126
gotShard := ring.ShardByName("ringShardOne")
127-
Expect(gotShard).To(Equal(wantShard))
127+
Expect(gotShard).To(BeIdenticalTo(wantShard))
128128

129129
ring.SetAddrs(map[string]string{
130130
"ringShardOne": ":" + ringShard1Port,
131131
"ringShardTwo": ":" + ringShard2Port,
132132
})
133133
Expect(ring.Len(), 2)
134134
gotShard = ring.ShardByName("ringShardOne")
135-
Expect(gotShard).To(Equal(wantShard))
135+
Expect(gotShard).To(BeIdenticalTo(wantShard))
136136
})
137137

138138
It("uses 3 shards after setting it to 3 shards", func() {
@@ -156,8 +156,8 @@ var _ = Describe("Redis Ring", func() {
156156
gotShard1 := ring.ShardByName(shardName1)
157157
gotShard2 := ring.ShardByName(shardName2)
158158
gotShard3 := ring.ShardByName(shardName3)
159-
Expect(gotShard1).To(Equal(wantShard1))
160-
Expect(gotShard2).To(Equal(wantShard2))
159+
Expect(gotShard1).To(BeIdenticalTo(wantShard1))
160+
Expect(gotShard2).To(BeIdenticalTo(wantShard2))
161161
Expect(gotShard3).ToNot(BeNil())
162162

163163
ring.SetAddrs(map[string]string{
@@ -168,8 +168,8 @@ var _ = Describe("Redis Ring", func() {
168168
gotShard1 = ring.ShardByName(shardName1)
169169
gotShard2 = ring.ShardByName(shardName2)
170170
gotShard3 = ring.ShardByName(shardName3)
171-
Expect(gotShard1).To(Equal(wantShard1))
172-
Expect(gotShard2).To(Equal(wantShard2))
171+
Expect(gotShard1).To(BeIdenticalTo(wantShard1))
172+
Expect(gotShard2).To(BeIdenticalTo(wantShard2))
173173
Expect(gotShard3).To(BeNil())
174174
})
175175
})

0 commit comments

Comments
 (0)