Skip to content

CBG-5905, CBG-6057: Improve DCPClient checkpoint persistence - #9071

Open
torcolvin wants to merge 12 commits into
mainfrom
dcp-checkpoints
Open

torcolvin wants to merge 12 commits into
mainfrom
dcp-checkpoints

Conversation

@torcolvin

@torcolvin torcolvin commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

CBG-5905, CBG-6057

  • CBG-5905: Persist in-memory checkpoints. An empty checkpoint prefix matched every key, so in-memory feeds never recorded seqs, and a reopened stream restarted from its initial metadata.
  • CBG-6057: Do not write checkpoints when the FeedEventCallback returns false, to match the behavior of cbgt. A false return still records the seq, and the next persist writes it.
  • lastMetaPersistTime is now set, so persistence is throttled to metaPersistFrequency instead of a write on every event. A deferred checkpoint is flushed when the worker closes.
  • One-shot checkpoint purge now runs after all workers stop, so a worker's flush on close cannot recreate a purged checkpoint.
  • seqnoAdvancedEvent records its seq but does not trigger a persist.

Pre-review checklist

  • Logging sensitive data? Make sure it's tagged (e.g. base.UD(docID), base.MD(dbName))
  • Updated relevant information in the API specifications (such as endpoint descriptions, schemas, ...) in docs/api

Dependencies (if applicable)

  • Link upstream PRs
  • Update Go module dependencies when merged

Integration Tests

🤖 Generated with Claude Code

- Persist in-memory checkpoints, so that a reopened stream resumes from
  the last processed seq.
- Do not write checkpoints if the FeedEventCallback returns false, to
  match the behavior of cbgt. Throttle writes to metaPersistFrequency
  and flush a deferred checkpoint when the worker closes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Non-sharded import shutdown can close the bucket before the deferred checkpoint flush runs, losing pending checkpoint progress and causing processed events to replay after restart.

2 open issues
What changed in this PR

Improves Sync Gateway’s DCP checkpoint handling so feeds retain sequence progress, respect callback decisions, and coordinate checkpoint cleanup with worker shutdown.

Changes:

  • Fixes sequence tracking when the checkpoint prefix is empty.
  • Honors callback-controlled persistence, throttles writes, and flushes deferred checkpoints on worker close.
  • Moves one-shot checkpoint cleanup after worker shutdown and adds persistence tests.
File Description
base/​gocb_dcp_client.go Delays one-shot checkpoint purge until all workers stop.
base/​dcp_client_worker.go Updates sequence tracking, persistence controls, throttling, and close-time flushing.
base/​dcp_client_worker_test.go Adds unit tests for callback decisions and deferred persistence.
base/​dcp_client_test.go Adds callback-controlled checkpoint integration coverage and updates resume-test behavior.

Review effort: Balanced

Comment thread base/dcp_client_worker.go Resolved
Comment thread base/dcp_client_worker.go Resolved
@torcolvin torcolvin self-assigned this Nov 3, 2026
@torcolvin torcolvin assigned gregns1 and unassigned torcolvin Nov 7, 2026
@torcolvin
torcolvin marked this pull request as ready for review November 7, 2026 01:03
@torcolvin
torcolvin requested review from a team November 7, 2026 01:03
@torcolvin
torcolvin requested a review from gregns1 as a code owner November 7, 2026 01:03
@factory-droid

factory-droid Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Droid finished @torcolvin's task —— View job


The DCP checkpoint persistence changes look correct overall, but there’s still a correctness risk if checkpoint writes fail yet the worker treats them as persisted (throttling retries and skipping the Close flush). Also, importListener.Stop’s doc comment says it’s async, but it can now block waiting for shutdown, so that contract should be clarified.

2 inline comments posted

Comment thread base/dcp_client_worker.go
SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleKvOps.Add(1)
}
w.sawNonSyncDoc = false
w.lastMetaPersistTime = time.Now()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Avoid treating failed checkpoint writes as persisted

DCPWorker.persist() always clears pendingPersist and updates lastMetaPersistTime after calling metadata.Persist(), but DCPMetadataCS.Persist() only logs KV write failures and returns no status. If a checkpoint write fails, this worker will still suppress retries for the throttle interval and Close() will not flush because pendingPersist was cleared, leaving persisted checkpoints behind in-memory seqs and potentially causing replay/backtracking after restart; consider changing the DCPMetadataStore Persist API to return an error (or success bool) so the worker only advances lastMetaPersistTime and clears pendingPersist on success.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think not updating lastMetaPersistTime on failure will be a good failure mode. This will mean each following event will request writing a checkpoint to a struggling cluster.

I wonder if we should not clear w.pendingPersist when we fail to write a checkpoint though? Then at least if Close is called it will attempt again to write?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Persist now returns an error.

On failure, the worker still updates lastMetaPersistTime, so a cluster with errors does not get a write on every event, but it keeps pendingPersist set, so Close retries the write.

The worker also keeps sawNonSyncDoc after a failed write, so the retry is not counted as an idle KV op. TestDCPWorkerRetriesFailedCheckpointOnClose covers this.

Comment thread db/import_listener.go Resolved

@gregns1 gregns1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to be but I think i'd like a regression test in place for the situation where we have to wait for workers to close before purging checkpoints or risk workers re-persisting post purge. The existing testing around attachment migration etc will depend on whether the workers that ends last having a deferred persist pending. I think a more deterministic test around this would be good to protect against regression.

Comment thread base/dcp_client_worker.go
SyncGatewayStats.GlobalStats.ResourceUtilizationStats().NumIdleKvOps.Add(1)
}
w.sawNonSyncDoc = false
w.lastMetaPersistTime = time.Now()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think not updating lastMetaPersistTime on failure will be a good failure mode. This will mean each following event will request writing a checkpoint to a struggling cluster.

I wonder if we should not clear w.pendingPersist when we fail to write a checkpoint though? Then at least if Close is called it will attempt again to write?

Comment thread base/dcp_client_worker.go Resolved
@gregns1 gregns1 assigned torcolvin and unassigned gregns1 Nov 10, 2026
@torcolvin torcolvin assigned gregns1 and unassigned torcolvin Nov 10, 2026
@torcolvin
torcolvin requested a review from bbrks as a code owner November 10, 2026 19:48

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants