Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c117bb2

Browse files
committedNov 21, 2022
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 c117bb2

File tree

2 files changed

+44
-33
lines changed

2 files changed

+44
-33
lines changed
 

‎ring.go

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ func (shard *ringShard) Vote(up bool) bool {
213213
type ringSharding struct {
214214
opt *RingOptions
215215

216+
addrsMu sync.Mutex
216217
mu sync.RWMutex
217218
shards *ringShards
218219
closed bool
@@ -245,46 +246,62 @@ func (c *ringSharding) OnNewNode(fn func(rdb *Client)) {
245246
// decrease number of shards, that you use. It will reuse shards that
246247
// existed before and close the ones that will not be used anymore.
247248
func (c *ringSharding) SetAddrs(addrs map[string]string) {
248-
c.mu.Lock()
249+
c.addrsMu.Lock()
250+
defer c.addrsMu.Unlock()
251+
252+
cleanup := func(shards map[string]*ringShard) {
253+
for addr, shard := range shards {
254+
if err := shard.Client.Close(); err != nil {
255+
internal.Logger.Printf(context.Background(), "shard.Close %s failed: %s", addr, err)
256+
}
257+
}
258+
}
249259

260+
c.mu.RLock()
250261
if c.closed {
251-
c.mu.Unlock()
262+
c.mu.RUnlock()
252263
return
253264
}
265+
existing := c.shards
266+
c.mu.RUnlock()
254267

255-
shards, cleanup := c.newRingShards(addrs, c.shards)
268+
shards, created, unused := c.newRingShards(addrs, existing)
269+
270+
c.mu.Lock()
271+
if c.closed {
272+
cleanup(created)
273+
c.mu.Unlock()
274+
return
275+
}
256276
c.shards = shards
257277
c.rebalanceLocked()
258278
c.mu.Unlock()
259279

260-
cleanup()
280+
cleanup(unused)
261281
}
262282

263283
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-
}
284+
addrs map[string]string, existing *ringShards,
285+
) (shards *ringShards, created, unused map[string]*ringShard) {
286+
287+
shards = &ringShards{m: make(map[string]*ringShard, len(addrs))}
288+
created = make(map[string]*ringShard) // indexed by addr
289+
unused = make(map[string]*ringShard) // indexed by addr
276290

277-
shards := &ringShards{
278-
m: make(map[string]*ringShard),
291+
if existing != nil {
292+
for _, shard := range existing.list {
293+
unused[shard.addr] = shard
294+
}
279295
}
280296

281297
for name, addr := range addrs {
282-
if shard, ok := shardMap[addr]; ok {
298+
if shard, ok := unused[addr]; ok {
283299
shards.m[name] = shard
284-
delete(unusedShards, addr)
300+
delete(unused, addr)
285301
} else {
286302
shard := newRingShard(c.opt, addr)
287303
shards.m[name] = shard
304+
created[addr] = shard
288305

289306
for _, fn := range c.onNewNode {
290307
fn(shard.Client)
@@ -296,13 +313,7 @@ func (c *ringSharding) newRingShards(
296313
shards.list = append(shards.list, shard)
297314
}
298315

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-
}
316+
return
306317
}
307318

308319
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)
Please sign in to comment.