Skip to content

Commit ee9bbd2

Browse files
committed
Adjust lock scope for improved concurrency (#1283)
Removed unnecessary context dependency from the Lock interface. Moved parts of the client patching logic outside the lock scope to enhance concurrency and reduce contention.
1 parent a98ac24 commit ee9bbd2

File tree

13 files changed

+273
-423
lines changed

13 files changed

+273
-423
lines changed

server/backend/database/mongo/client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,14 +825,15 @@ func (c *Client) FindDocInfoByKeyAndOwner(
825825
docKey key.Key,
826826
createDocIfNotExist bool,
827827
) (*database.DocInfo, error) {
828+
now := gotime.Now()
829+
828830
filter := bson.M{
829831
"project_id": clientRefKey.ProjectID,
830832
"key": docKey,
831833
"removed_at": bson.M{
832834
"$exists": false,
833835
},
834836
}
835-
now := gotime.Now()
836837
res, err := c.collection(ColDocuments).UpdateOne(ctx, filter, bson.M{
837838
"$set": bson.M{
838839
"accessed_at": now,

server/backend/sync/locker.go

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
package sync
1919

2020
import (
21-
"context"
2221
"errors"
2322

2423
"github.com/yorkie-team/yorkie/pkg/locker"
@@ -53,32 +52,41 @@ func New() *LockerManager {
5352
}
5453

5554
// Locker creates locker of the given key.
56-
func (c *LockerManager) Locker(
57-
_ context.Context,
58-
key Key,
59-
) (Locker, error) {
60-
return &internalLocker{
55+
func (c *LockerManager) Locker(key Key) Locker {
56+
locker := &internalLocker{
6157
key.String(),
6258
c.locks,
63-
}, nil
59+
}
60+
locker.lock()
61+
return locker
62+
}
63+
64+
// LockerWithTryLock creates locker of the given key with try lock.
65+
func (c *LockerManager) LockerWithTryLock(key Key) (Locker, bool) {
66+
locker := &internalLocker{
67+
key.String(),
68+
c.locks,
69+
}
70+
ok := locker.tryLock()
71+
return locker, ok
6472
}
6573

6674
// A Locker represents an object that can be locked and unlocked.
6775
type Locker interface {
68-
// Lock locks the mutex with a cancelable context
69-
Lock(ctx context.Context) error
76+
// Lock locks the mutex.
77+
lock()
7078

7179
// TryLock locks the mutex if not already locked by another session.
72-
TryLock(ctx context.Context) error
80+
tryLock() bool
7381

7482
// Unlock unlocks the mutex.
75-
Unlock(ctx context.Context) error
83+
Unlock() error
7684

77-
// RLock acquires a read lock with a cancelable context.
78-
RLock(ctx context.Context) error
85+
// RLock acquires a read lock.
86+
RLock()
7987

8088
// RUnlock releases a read lock previously acquired by RLock.
81-
RUnlock(ctx context.Context) error
89+
RUnlock() error
8290
}
8391

8492
type internalLocker struct {
@@ -87,42 +95,26 @@ type internalLocker struct {
8795
}
8896

8997
// Lock locks the mutex.
90-
func (il *internalLocker) Lock(_ context.Context) error {
98+
func (il *internalLocker) lock() {
9199
il.locks.Lock(il.key)
92-
93-
return nil
94100
}
95101

96102
// TryLock locks the mutex if not already locked by another session.
97-
func (il *internalLocker) TryLock(_ context.Context) error {
98-
if !il.locks.TryLock(il.key) {
99-
return ErrAlreadyLocked
100-
}
101-
102-
return nil
103+
func (il *internalLocker) tryLock() bool {
104+
return il.locks.TryLock(il.key)
103105
}
104106

105107
// Unlock unlocks the mutex.
106-
func (il *internalLocker) Unlock(_ context.Context) error {
107-
if err := il.locks.Unlock(il.key); err != nil {
108-
return err
109-
}
110-
111-
return nil
108+
func (il *internalLocker) Unlock() error {
109+
return il.locks.Unlock(il.key)
112110
}
113111

114112
// RLock locks the mutex for reading..
115-
func (il *internalLocker) RLock(_ context.Context) error {
113+
func (il *internalLocker) RLock() {
116114
il.locks.RLock(il.key)
117-
118-
return nil
119115
}
120116

121117
// RUnlock unlocks the read lock.
122-
func (il *internalLocker) RUnlock(_ context.Context) error {
123-
if err := il.locks.RUnlock(il.key); err != nil {
124-
return err
125-
}
126-
127-
return nil
118+
func (il *internalLocker) RUnlock() error {
119+
return il.locks.RUnlock(il.key)
128120
}

server/clients/clients.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,14 @@ func Deactivate(
7676
return nil, err
7777
}
7878

79-
if err := be.ClusterClient.DetachDocument(ctx, project, actorID, docID, project.PublicKey, docInfo.Key); err != nil {
79+
if err := be.ClusterClient.DetachDocument(
80+
ctx,
81+
project,
82+
actorID,
83+
docID,
84+
project.PublicKey,
85+
docInfo.Key,
86+
); err != nil {
8087
return nil, err
8188
}
8289
}

server/clients/housekeeping.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,9 @@ func DeactivateInactives(
4141
) (types.ID, error) {
4242
start := time.Now()
4343

44-
locker, err := be.Lockers.Locker(ctx, deactivateCandidatesKey)
45-
if err != nil {
46-
return database.DefaultProjectID, err
47-
}
48-
49-
if err := locker.Lock(ctx); err != nil {
50-
return database.DefaultProjectID, err
51-
}
52-
44+
locker := be.Lockers.Locker(deactivateCandidatesKey)
5345
defer func() {
54-
if err := locker.Unlock(ctx); err != nil {
46+
if err := locker.Unlock(); err != nil {
5547
logging.From(ctx).Error(err)
5648
}
5749
}()

server/documents/housekeeping.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,9 @@ func CompactDocuments(
4242
) (types.ID, error) {
4343
start := time.Now()
4444

45-
locker, err := be.Lockers.Locker(ctx, compactionCandidatesKey)
46-
if err != nil {
47-
return database.DefaultProjectID, err
48-
}
49-
50-
if err := locker.Lock(ctx); err != nil {
51-
return database.DefaultProjectID, err
52-
}
53-
45+
locker := be.Lockers.Locker(compactionCandidatesKey)
5446
defer func() {
55-
if err := locker.Unlock(ctx); err != nil {
47+
if err := locker.Unlock(); err != nil {
5648
logging.From(ctx).Error(err)
5749
}
5850
}()

server/packs/packs.go

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -76,28 +76,19 @@ func PushPull(
7676
opts PushPullOptions,
7777
) (*ServerPack, error) {
7878
start := gotime.Now()
79-
defer func() {
80-
be.Metrics.ObservePushPullResponseSeconds(gotime.Since(start).Seconds())
81-
}()
8279

8380
// TODO: Changes may be reordered or missing during communication on the network.
8481
// We should check the change.pack with checkpoint to make sure the changes are in the correct order.
8582
initialServerSeq := docInfo.ServerSeq
8683

8784
// 01. push changes: filter out the changes that are already saved in the database.
8885
cpAfterPush, pushedChanges := pushChanges(ctx, clientInfo, docInfo, reqPack, initialServerSeq)
89-
hostname := be.Config.Hostname
90-
be.Metrics.AddPushPullReceivedChanges(hostname, project, reqPack.ChangesLen())
91-
be.Metrics.AddPushPullReceivedOperations(hostname, project, reqPack.OperationsLen())
9286

9387
// 02. pull pack: pull changes or a snapshot from the database and create a response pack.
9488
respPack, err := pullPack(ctx, be, clientInfo, docInfo, reqPack, cpAfterPush, initialServerSeq, opts.Mode)
9589
if err != nil {
9690
return nil, err
9791
}
98-
be.Metrics.AddPushPullSentChanges(hostname, project, respPack.ChangesLen())
99-
be.Metrics.AddPushPullSentOperations(hostname, project, respPack.OperationsLen())
100-
be.Metrics.AddPushPullSnapshotBytes(hostname, project, respPack.SnapshotLen())
10192

10293
// 03. update the client's document and checkpoint.
10394
docRefKey := docInfo.RefKey()
@@ -115,7 +106,7 @@ func PushPull(
115106
}
116107
}
117108

118-
// 04. store pushed changes, docInfo and checkpoint of the client to DB.
109+
// 04. store the pushed changes and update the document info in DB.
119110
if len(pushedChanges) > 0 || reqPack.IsRemoved {
120111
if err := be.DB.CreateChangeInfos(
121112
ctx,
@@ -128,12 +119,7 @@ func PushPull(
128119
return nil, err
129120
}
130121
}
131-
132-
if !clientInfo.IsServerClient() {
133-
if err := be.DB.UpdateClientInfoAfterPushPull(ctx, clientInfo, docInfo); err != nil {
134-
return nil, err
135-
}
136-
}
122+
respPack.ApplyDocInfo(docInfo)
137123

138124
// 05. update min version vector of the document.
139125
minVersionVector, err := be.DB.UpdateMinVersionVector(
@@ -149,7 +135,12 @@ func PushPull(
149135
respPack.VersionVector = minVersionVector
150136
}
151137

152-
respPack.ApplyDocInfo(docInfo)
138+
// 06. update client's checkpoint of the document in DB.
139+
if !clientInfo.IsServerClient() {
140+
if err := be.DB.UpdateClientInfoAfterPushPull(ctx, clientInfo, docInfo); err != nil {
141+
return nil, err
142+
}
143+
}
153144

154145
pullLog := strconv.Itoa(respPack.ChangesLen())
155146
if respPack.SnapshotLen() > 0 {
@@ -163,8 +154,15 @@ func PushPull(
163154
pullLog,
164155
gotime.Since(start),
165156
)
157+
hostname := be.Config.Hostname
158+
be.Metrics.AddPushPullReceivedChanges(hostname, project, reqPack.ChangesLen())
159+
be.Metrics.AddPushPullReceivedOperations(hostname, project, reqPack.OperationsLen())
160+
be.Metrics.AddPushPullSentChanges(hostname, project, respPack.ChangesLen())
161+
be.Metrics.AddPushPullSentOperations(hostname, project, respPack.OperationsLen())
162+
be.Metrics.AddPushPullSnapshotBytes(hostname, project, respPack.SnapshotLen())
163+
be.Metrics.ObservePushPullResponseSeconds(gotime.Since(start).Seconds())
166164

167-
// 06. publish document change event then store snapshot asynchronously.
165+
// 07. publish document change event then store snapshot asynchronously.
168166
if len(pushedChanges) > 0 || reqPack.IsRemoved {
169167
be.Background.AttachGoroutine(func(ctx context.Context) {
170168
publisherID, err := clientInfo.ID.ToActorID()
@@ -200,30 +198,21 @@ func PushPull(
200198
}
201199
}
202200

203-
locker, err := be.Lockers.Locker(ctx, SnapshotKey(project.ID, reqPack.DocumentKey))
204-
if err != nil {
205-
logging.From(ctx).Error(err)
206-
return
207-
}
208-
209-
// NOTE: If the snapshot is already being created by another routine, it
210-
// is not necessary to recreate it, so we can skip it.
211-
if err := locker.TryLock(ctx); err != nil {
201+
// NOTE(hackerwins): If the snapshot is already being created by another routine,
202+
// it is not necessary to recreate it, so we can skip it.
203+
locker, ok := be.Lockers.LockerWithTryLock(SnapshotKey(project.ID, reqPack.DocumentKey))
204+
if !ok {
212205
return
213206
}
214207
defer func() {
215-
if err := locker.Unlock(ctx); err != nil {
208+
if err := locker.Unlock(); err != nil {
216209
logging.From(ctx).Error(err)
217210
return
218211
}
219212
}()
220213

221214
start := gotime.Now()
222-
if err := storeSnapshot(
223-
ctx,
224-
be,
225-
docInfo,
226-
); err != nil {
215+
if err := storeSnapshot(ctx, be, docInfo); err != nil {
227216
logging.From(ctx).Error(err)
228217
}
229218
be.Metrics.ObservePushPullSnapshotDurationSeconds(
@@ -260,18 +249,18 @@ func BuildInternalDocForServerSeq(
260249
docInfo *database.DocInfo,
261250
serverSeq int64,
262251
) (*document.InternalDocument, error) {
263-
docRefKey := docInfo.RefKey()
252+
docKey := docInfo.RefKey()
264253

265254
// NOTE(hackerwins): If the document is already in the cache, we can skip
266255
// the database query and use the cached document. If the document's server
267256
// sequence in the cache is greater than the given server sequence, we can't
268257
// build the document from the document. In this case, we need to
269258
// query the database to get the closest snapshot information.
270-
doc, ok := be.SnapshotCache.Get(docRefKey)
259+
doc, ok := be.SnapshotCache.Get(docKey)
271260
if !ok || serverSeq < doc.Checkpoint().ServerSeq {
272261
snapshotInfo, err := be.DB.FindClosestSnapshotInfo(
273262
ctx,
274-
docRefKey,
263+
docKey,
275264
serverSeq,
276265
true,
277266
)
@@ -293,7 +282,7 @@ func BuildInternalDocForServerSeq(
293282

294283
changes, err := be.DB.FindChangesBetweenServerSeqs(
295284
ctx,
296-
docRefKey,
285+
docKey,
297286
doc.Checkpoint().ServerSeq+1,
298287
serverSeq,
299288
)
@@ -314,7 +303,7 @@ func BuildInternalDocForServerSeq(
314303
if !be.Config.SnapshotDisableGC {
315304
vector, err := be.DB.GetMinVersionVector(
316305
ctx,
317-
docRefKey,
306+
docKey,
318307
doc.VersionVector(),
319308
)
320309
if err != nil {
@@ -330,7 +319,7 @@ func BuildInternalDocForServerSeq(
330319
if err != nil {
331320
return nil, err
332321
}
333-
be.SnapshotCache.Add(docRefKey, clone)
322+
be.SnapshotCache.Add(docKey, clone)
334323

335324
if logging.Enabled(zap.DebugLevel) {
336325
logging.From(ctx).Debugf(

0 commit comments

Comments
 (0)