Skip to content

Commit 99d313e

Browse files
gregns1RIT3shSapata
authored andcommitted
CBG-4746: Deduplicate channel set outside processEntry (#7861)
1 parent 8df1f75 commit 99d313e

3 files changed

Lines changed: 72 additions & 57 deletions

File tree

db/change_cache.go

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ type changeCache struct {
6868
initialSequence uint64 // DB's current sequence at startup time.
6969
receivedSeqs map[uint64]struct{} // Set of all sequences received
7070
pendingLogs LogPriorityQueue // Out-of-sequence entries waiting to be cached
71-
notifyChange func(context.Context, channels.Set) // Client callback that notifies of channel changes
71+
notifyChangeFunc func(context.Context, channels.Set) // Client callback that notifies of channel changes
7272
started base.AtomicBool // Set by the Start method
7373
stopped base.AtomicBool // Set by the Stop method
7474
skippedSeqs *SkippedSequenceSkiplist // Skipped sequences still pending on the DCP caching feed
@@ -145,15 +145,15 @@ func DefaultCacheOptions() CacheOptions {
145145

146146
// Initializes a new changeCache.
147147
// lastSequence is the last known database sequence assigned.
148-
// notifyChange is an optional function that will be called to notify of channel changes.
148+
// notifyChangeFunc is an optional function that will be called to notify of channel changes.
149149
// After calling Init(), you must call .Start() to start using the cache, otherwise it will be in a locked state
150150
// and callers will block on trying to obtain the lock.
151151

152152
func (c *changeCache) Init(ctx context.Context, dbContext *DatabaseContext, channelCache ChannelCache, notifyChange func(context.Context, channels.Set), options *CacheOptions, metaKeys *base.MetadataKeys) error {
153153
c.db = dbContext
154154
c.logCtx = ctx
155155

156-
c.notifyChange = notifyChange
156+
c.notifyChangeFunc = notifyChange
157157
c.receivedSeqs = make(map[uint64]struct{})
158158
c.terminator = make(chan bool)
159159
c.initTime = time.Now()
@@ -277,11 +277,10 @@ func (c *changeCache) InsertPendingEntries(ctx context.Context) error {
277277
// Trigger _addPendingLogs to process any entries that have been pending too long:
278278
c.lock.Lock()
279279
changedChannels := c._addPendingLogs(ctx)
280-
if c.notifyChange != nil && len(changedChannels) > 0 {
281-
c.notifyChange(ctx, changedChannels)
282-
}
283280
c.lock.Unlock()
284281

282+
c.notifyChange(ctx, changedChannels)
283+
285284
return nil
286285
}
287286

@@ -445,7 +444,8 @@ func (c *changeCache) DocChanged(event sgbucket.FeedEvent, docType DocumentType)
445444
UnusedSequence: true,
446445
}
447446
changedChannels := c.processEntry(ctx, change)
448-
changedChannelsCombined = changedChannelsCombined.Update(changedChannels)
447+
channelSet := channels.SetFromArrayNoValidate(changedChannels)
448+
changedChannelsCombined = changedChannelsCombined.Update(channelSet)
449449
}
450450
base.DebugfCtx(ctx, base.KeyCache, "Received unused sequences in unused_sequences property for (%q / %q): %v", base.UD(docID), syncData.CurrentRev, syncData.UnusedSequences)
451451
}
@@ -491,7 +491,8 @@ func (c *changeCache) DocChanged(event sgbucket.FeedEvent, docType DocumentType)
491491
}
492492

493493
changedChannels := c.processEntry(ctx, change)
494-
changedChannelsCombined = changedChannelsCombined.Update(changedChannels)
494+
channelSet := channels.SetFromArrayNoValidate(changedChannels)
495+
changedChannelsCombined = changedChannelsCombined.Update(channelSet)
495496
}
496497
}
497498
if len(seqsCached) > 0 {
@@ -523,11 +524,12 @@ func (c *changeCache) DocChanged(event sgbucket.FeedEvent, docType DocumentType)
523524
}
524525

525526
changedChannels := c.processEntry(ctx, change)
526-
changedChannelsCombined = changedChannelsCombined.Update(changedChannels)
527+
channelSet := channels.SetFromArrayNoValidate(changedChannels)
528+
changedChannelsCombined = changedChannelsCombined.Update(channelSet)
527529

528530
// Notify change listeners for all of the changed channels
529-
if c.notifyChange != nil && len(changedChannelsCombined) > 0 {
530-
c.notifyChange(ctx, changedChannelsCombined)
531+
if c.notifyChangeFunc != nil && len(changedChannelsCombined) > 0 {
532+
c.notifyChangeFunc(ctx, changedChannelsCombined)
531533
}
532534

533535
}
@@ -538,6 +540,13 @@ type cachePrincipal struct {
538540
Sequence uint64 `json:"sequence"`
539541
}
540542

543+
func (c *changeCache) notifyChange(ctx context.Context, chs []channels.ID) {
544+
if c.notifyChangeFunc == nil || len(chs) == 0 {
545+
return
546+
}
547+
c.notifyChangeFunc(ctx, channels.SetFromArrayNoValidate(chs))
548+
}
549+
541550
func (c *changeCache) Remove(ctx context.Context, collectionID uint32, docIDs []string, startTime time.Time) (count int) {
542551
return c.channelCache.Remove(ctx, collectionID, docIDs, startTime)
543552
}
@@ -571,14 +580,16 @@ func (c *changeCache) releaseUnusedSequence(ctx context.Context, sequence uint64
571580

572581
// Since processEntry may unblock pending sequences, if there were any changed channels we need
573582
// to notify any change listeners that are working changes feeds for these channels
583+
var channelSet channels.Set
574584
changedChannels := c.processEntry(ctx, change)
575585
if changedChannels == nil {
576-
changedChannels = channels.SetOfNoValidate(unusedSeqChannelID)
586+
channelSet = channels.SetOfNoValidate(unusedSeqChannelID)
577587
} else {
578-
changedChannels.Add(unusedSeqChannelID)
588+
channelSet = channels.SetFromArrayNoValidate(changedChannels)
589+
channelSet.Add(unusedSeqChannelID)
579590
}
580-
if c.notifyChange != nil && len(changedChannels) > 0 {
581-
c.notifyChange(ctx, changedChannels)
591+
if c.notifyChangeFunc != nil && len(channelSet) > 0 {
592+
c.notifyChangeFunc(ctx, channelSet)
582593
}
583594
}
584595

@@ -598,36 +609,38 @@ func (c *changeCache) releaseUnusedSequenceRange(ctx context.Context, fromSequen
598609
UnusedSequence: true,
599610
}
600611
changedChannels := c.processEntry(ctx, change)
601-
allChangedChannels = allChangedChannels.Update(changedChannels)
602-
if c.notifyChange != nil {
603-
c.notifyChange(ctx, allChangedChannels)
612+
channelSet := channels.SetFromArrayNoValidate(changedChannels)
613+
allChangedChannels = allChangedChannels.Update(channelSet)
614+
if c.notifyChangeFunc != nil {
615+
c.notifyChangeFunc(ctx, allChangedChannels)
604616
}
605617
return
606618
}
607619

608620
// push unused range to either pending or skipped lists based on current state of the change cache
609-
allChangedChannels = c.processUnusedRange(ctx, fromSequence, toSequence, allChangedChannels, timeReceived)
621+
changedChannels := c.processUnusedRange(ctx, fromSequence, toSequence, timeReceived)
622+
allChangedChannels.Update(channels.SetFromArrayNoValidate(changedChannels))
610623

611-
if c.notifyChange != nil {
612-
c.notifyChange(ctx, allChangedChannels)
624+
if c.notifyChangeFunc != nil {
625+
c.notifyChangeFunc(ctx, allChangedChannels)
613626
}
614627
}
615628

616629
// processUnusedRange handles pushing unused range to pending or skipped lists
617-
func (c *changeCache) processUnusedRange(ctx context.Context, fromSequence, toSequence uint64, allChangedChannels channels.Set, timeReceived channels.FeedTimestamp) channels.Set {
630+
func (c *changeCache) processUnusedRange(ctx context.Context, fromSequence, toSequence uint64, timeReceived channels.FeedTimestamp) []channels.ID {
618631
c.lock.Lock()
619632
defer c.lock.Unlock()
620633

621634
var numSkipped int64
635+
var changedChannels []channels.ID
622636
if toSequence < c.nextSequence {
623637
// batch remove from skipped
624638
numSkipped = c.skippedSeqs.processUnusedSequenceRangeAtSkipped(ctx, fromSequence, toSequence)
625639
} else if fromSequence >= c.nextSequence {
626640
// whole range to pending
627641
c._pushRangeToPending(fromSequence, toSequence, timeReceived)
628642
// unblock any pending sequences we can after new range(s) have been pushed to pending
629-
changedChannels := c._addPendingLogs(ctx)
630-
allChangedChannels = allChangedChannels.Update(changedChannels)
643+
changedChannels = append(changedChannels, c._addPendingLogs(ctx)...)
631644
c.internalStats.pendingSeqLen = len(c.pendingLogs)
632645
} else {
633646
// An unused sequence range than includes c.nextSequence in the middle of the range
@@ -641,7 +654,7 @@ func (c *changeCache) processUnusedRange(ctx context.Context, fromSequence, toSe
641654
if numSkipped == 0 {
642655
c.db.BroadcastSlowMode.CompareAndSwap(true, false)
643656
}
644-
return allChangedChannels
657+
return changedChannels
645658
}
646659

647660
// _pushRangeToPending will push an unused sequence range to pendingLogs
@@ -711,13 +724,14 @@ func (c *changeCache) processPrincipalDoc(ctx context.Context, docID string, doc
711724
base.InfofCtx(ctx, base.KeyChanges, "Received #%d (%q)", change.Sequence, base.UD(change.DocID))
712725

713726
changedChannels := c.processEntry(ctx, change)
714-
if c.notifyChange != nil && len(changedChannels) > 0 {
715-
c.notifyChange(ctx, changedChannels)
716-
}
727+
728+
c.notifyChange(ctx, changedChannels)
717729
}
718730

719-
// Handles a newly-arrived LogEntry.
720-
func (c *changeCache) processEntry(ctx context.Context, change *LogEntry) channels.Set {
731+
// processEntry handles a newly-arrived LogEntry and returns the changes channels from this revision.
732+
// This can be any existing, removed or newly added channels. Its possible for channels slice returned to have duplicates
733+
// in it. It is the callers responsibility to de-duplicate before notifying any changes.
734+
func (c *changeCache) processEntry(ctx context.Context, change *LogEntry) []channels.ID {
721735
c.lock.Lock()
722736
defer c.lock.Unlock()
723737
if c.logsDisabled {
@@ -752,12 +766,12 @@ func (c *changeCache) processEntry(ctx context.Context, change *LogEntry) channe
752766
}
753767
c.receivedSeqs[sequence] = struct{}{}
754768

755-
var changedChannels channels.Set
769+
var changedChannels []channels.ID
756770
if sequence == c.nextSequence || c.nextSequence == 0 {
757771
// This is the expected next sequence so we can add it now:
758772
changedChannels = c._addToCache(ctx, change)
759773
// Also add any pending sequences that are now contiguous:
760-
changedChannels = changedChannels.Update(c._addPendingLogs(ctx))
774+
changedChannels = append(changedChannels, c._addPendingLogs(ctx)...)
761775
} else if sequence > c.nextSequence {
762776
// There's a missing sequence (or several), so put this one on ice until it arrives:
763777
heap.Push(&c.pendingLogs, change)
@@ -774,7 +788,7 @@ func (c *changeCache) processEntry(ctx context.Context, change *LogEntry) channe
774788

775789
if numPending > c.options.CachePendingSeqMaxNum {
776790
// Too many pending; add the oldest one:
777-
changedChannels = c._addPendingLogs(ctx)
791+
changedChannels = append(changedChannels, c._addPendingLogs(ctx)...)
778792
}
779793
} else if sequence > c.initialSequence {
780794
// Out-of-order sequence received!
@@ -786,7 +800,7 @@ func (c *changeCache) processEntry(ctx context.Context, change *LogEntry) channe
786800
base.DebugfCtx(ctx, base.KeyCache, " Received previously skipped out-of-order change (seq %d, expecting %d) doc %q / %q ", sequence, c.nextSequence, base.UD(change.DocID), change.RevID)
787801
}
788802

789-
changedChannels = changedChannels.Update(c._addToCache(ctx, change))
803+
changedChannels = append(changedChannels, c._addToCache(ctx, change)...)
790804
// Add to cache before removing from skipped, to ensure lowSequence doesn't get incremented until results are available
791805
// in cache
792806
err := c.RemoveSkipped(sequence)
@@ -799,7 +813,7 @@ func (c *changeCache) processEntry(ctx context.Context, change *LogEntry) channe
799813

800814
// Adds an entry to the appropriate channels' caches, returning the affected channels. lateSequence
801815
// flag indicates whether it was a change arriving out of sequence
802-
func (c *changeCache) _addToCache(ctx context.Context, change *LogEntry) channels.Set {
816+
func (c *changeCache) _addToCache(ctx context.Context, change *LogEntry) []channels.ID {
803817

804818
if change.Sequence >= c.nextSequence {
805819
c.nextSequence = change.Sequence + 1
@@ -836,11 +850,12 @@ func (c *changeCache) _addToCache(ctx context.Context, change *LogEntry) channel
836850
return updatedChannels
837851
}
838852

839-
// Add the first change(s) from pendingLogs if they're the next sequence. If not, and we've been
853+
// _addPendingLogs Add the first change(s) from pendingLogs if they're the next sequence. If not, and we've been
840854
// waiting too long for nextSequence, move nextSequence to skipped queue.
841-
// Returns the channels that changed.
842-
func (c *changeCache) _addPendingLogs(ctx context.Context) channels.Set {
843-
var changedChannels channels.Set
855+
// Returns the channels that changed. This may return the same channel more than once, channels should be deduplicated
856+
// before notifying the changes.
857+
func (c *changeCache) _addPendingLogs(ctx context.Context) []channels.ID {
858+
var changedChannels []channels.ID
844859
var isNext bool
845860

846861
for len(c.pendingLogs) > 0 {
@@ -849,7 +864,7 @@ func (c *changeCache) _addPendingLogs(ctx context.Context) channels.Set {
849864

850865
if isNext {
851866
oldestPending = c._popPendingLog(ctx)
852-
changedChannels = changedChannels.Update(c._addToCache(ctx, oldestPending))
867+
changedChannels = append(changedChannels, c._addToCache(ctx, oldestPending)...)
853868
} else if oldestPending.Sequence < c.nextSequence {
854869
// oldest pending is lower than next sequence, should be ignored
855870
base.InfofCtx(ctx, base.KeyCache, "Oldest entry in pending logs %v (%d, %d) is earlier than cache next sequence (%d), ignoring as sequence has already been cached", base.UD(oldestPending.DocID), oldestPending.Sequence, oldestPending.EndSequence, c.nextSequence)

db/change_cache_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,10 +1314,10 @@ func readNextFromFeed(feed <-chan (*ChangeEntry), timeout time.Duration) (*Chang
13141314
//
13151315
// Create doc1 w/ unused sequences 1, actual sequence 3.
13161316
// Create doc2 w/ sequence 2, channel ABC
1317-
// Send feed event for doc2. This won't trigger notifyChange, as buffering is waiting for seq 1
1318-
// Send feed event for doc1. This should trigger caching for doc2, and trigger notifyChange for channel ABC.
1317+
// Send feed event for doc2. This won't trigger notifyChangeFunc, as buffering is waiting for seq 1
1318+
// Send feed event for doc1. This should trigger caching for doc2, and trigger notifyChangeFunc for channel ABC.
13191319
//
1320-
// Verify that notifyChange for channel ABC was sent.
1320+
// Verify that notifyChangeFunc for channel ABC was sent.
13211321
func TestLateArrivingSequenceTriggersOnChange(t *testing.T) {
13221322

13231323
// Enable relevant logging
@@ -1334,12 +1334,12 @@ func TestLateArrivingSequenceTriggersOnChange(t *testing.T) {
13341334
collection := GetSingleDatabaseCollection(t, db.DatabaseContext)
13351335
collectionID := collection.GetCollectionID()
13361336

1337-
// -------- Setup notifyChange callback ----------------
1337+
// -------- Setup notifyChangeFunc callback ----------------
13381338

1339-
// Detect whether the 2nd was ignored using an notifyChange listener callback and make sure it was not added to the ABC channel
1339+
// Detect whether the 2nd was ignored using an notifyChangeFunc listener callback and make sure it was not added to the ABC channel
13401340
waitForOnChangeCallback := sync.WaitGroup{}
13411341
waitForOnChangeCallback.Add(1)
1342-
db.changeCache.notifyChange = func(_ context.Context, chans channels.Set) {
1342+
db.changeCache.notifyChangeFunc = func(_ context.Context, chans channels.Set) {
13431343
expectedChan := channels.NewID("ABC", collectionID)
13441344
for ch := range chans {
13451345
if ch == expectedChan {
@@ -1416,7 +1416,7 @@ func TestLateArrivingSequenceTriggersOnChange(t *testing.T) {
14161416
require.NoError(t, err)
14171417
}
14181418

1419-
// Send feed event for doc2. This won't trigger notifyChange, as buffering is waiting for seq 1
1419+
// Send feed event for doc2. This won't trigger notifyChangeFunc, as buffering is waiting for seq 1
14201420
feedEventDoc2 := sgbucket.FeedEvent{
14211421
Synchronous: true,
14221422
Key: []byte(doc2Id),
@@ -1426,7 +1426,7 @@ func TestLateArrivingSequenceTriggersOnChange(t *testing.T) {
14261426
}
14271427
db.changeCache.DocChanged(feedEventDoc2, DocTypeDocument)
14281428

1429-
// Send feed event for doc1. This should trigger caching for doc2, and trigger notifyChange for channel ABC.
1429+
// Send feed event for doc1. This should trigger caching for doc2, and trigger notifyChangeFunc for channel ABC.
14301430
feedEventDoc1 := sgbucket.FeedEvent{
14311431
Synchronous: true,
14321432
Key: []byte(doc1Id),
@@ -1437,7 +1437,7 @@ func TestLateArrivingSequenceTriggersOnChange(t *testing.T) {
14371437

14381438
// -------- Wait for waitgroup ----------------
14391439

1440-
// Block until the notifyChange callback was invoked with the expected channels.
1440+
// Block until the notifyChangeFunc callback was invoked with the expected channels.
14411441
// If the callback is never called back with expected, will block forever.
14421442
waitForOnChangeCallback.Wait()
14431443

@@ -1591,7 +1591,7 @@ func TestInitializeCacheUnderLoad(t *testing.T) {
15911591

15921592
}
15931593

1594-
// Verify that notifyChange for channel zero is sent even when the channel isn't active in the cache.
1594+
// Verify that notifyChangeFunc for channel zero is sent even when the channel isn't active in the cache.
15951595
func TestNotifyForInactiveChannel(t *testing.T) {
15961596

15971597
// Enable relevant logging
@@ -1604,10 +1604,10 @@ func TestNotifyForInactiveChannel(t *testing.T) {
16041604
collection.ChannelMapper = channels.NewChannelMapper(ctx, channels.DocChannelsSyncFunction, db.Options.JavascriptTimeout)
16051605
collectionID := collection.GetCollectionID()
16061606

1607-
// -------- Setup notifyChange callback ----------------
1607+
// -------- Setup notifyChangeFunc callback ----------------
16081608

16091609
notifyChannel := make(chan struct{})
1610-
db.changeCache.notifyChange = func(_ context.Context, chans channels.Set) {
1610+
db.changeCache.notifyChangeFunc = func(_ context.Context, chans channels.Set) {
16111611
expectedChan := channels.NewID("zero", collectionID)
16121612
if chans.Contains(expectedChan) {
16131613
notifyChannel <- struct{}{}

db/channel_cache.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type ChannelCache interface {
3939
Init(initialSequence uint64)
4040

4141
// Adds an entry to the cache, returns set of channels it was added to
42-
AddToCache(ctx context.Context, change *LogEntry) channels.Set
42+
AddToCache(ctx context.Context, change *LogEntry) []channels.ID
4343

4444
// Notifies the cache of a principal update. Updates the cache's high sequence
4545
AddPrincipal(change *LogEntry)
@@ -197,14 +197,14 @@ func (c *channelCacheImpl) AddUnusedSequence(change *LogEntry) {
197197

198198
// Adds an entry to the appropriate channels' caches, returning the affected channels. lateSequence
199199
// flag indicates whether it was a change arriving out of sequence
200-
func (c *channelCacheImpl) AddToCache(ctx context.Context, change *LogEntry) channels.Set {
200+
func (c *channelCacheImpl) AddToCache(ctx context.Context, change *LogEntry) []channels.ID {
201201

202202
ch := change.Channels
203203
change.Channels = nil // not needed anymore, so free some memory
204204

205205
// updatedChannels tracks the set of channels that should be notified of the change. This includes
206206
// the change's active channels, as well as any channel removals for the active revision.
207-
updatedChannels := make(channels.Set, len(ch)+1) // +1 for the star channel
207+
updatedChannels := make([]channels.ID, 0, len(ch)+1) // +1 for the star channel
208208

209209
// If it's a late sequence, we want to add to all channel late queues within a single write lock,
210210
// to avoid a changes feed seeing the same late sequence in different iteration loops (and sending
@@ -234,7 +234,7 @@ func (c *channelCacheImpl) AddToCache(ctx context.Context, change *LogEntry) cha
234234
}
235235
}
236236
// Need to notify even if channel isn't active, for case where number of connected changes channels exceeds cache capacity
237-
updatedChannels.Add(channelID)
237+
updatedChannels = append(updatedChannels, channelID)
238238
}
239239
}
240240

@@ -247,7 +247,7 @@ func (c *channelCacheImpl) AddToCache(ctx context.Context, change *LogEntry) cha
247247
channelCache.AddLateSequence(change)
248248
}
249249
}
250-
updatedChannels.Add(starChannelID)
250+
updatedChannels = append(updatedChannels, starChannelID)
251251
}
252252

253253
c.updateHighCacheSequence(change.Sequence)

0 commit comments

Comments
 (0)