Skip to content

remote_write: implement restart from segment-based savepoint#18485

Draft
x1unix wants to merge 18 commits intoprometheus:mainfrom
x1unix:x1unix/feat/rw-savepoint
Draft

remote_write: implement restart from segment-based savepoint#18485
x1unix wants to merge 18 commits intoprometheus:mainfrom
x1unix:x1unix/feat/rw-savepoint

Conversation

@x1unix
Copy link
Copy Markdown

@x1unix x1unix commented Apr 8, 2026

Which issue(s) does the PR fix:

This PR implements a mechanism for remote write to track and resume from last WAL segment.

PR is based on the proposal prometheus/proposals#72

Release notes for end users (ALL commits must be considered).

Reviewers should verify clarity and quality.

[ENHANCEMENT] Remote write: Add segment-based savepoint support behind the `remote-write-savepoint` feature flag. When enabled, remote write periodically persists the current WAL segment for each destination to a savepoint file, allowing replay from the last saved segment on restart instead of skipping undelivered samples.

Copy link
Copy Markdown
Contributor

@kgeckhart kgeckhart left a comment

Choose a reason for hiding this comment

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

You're on the right track, there's a few things to consider with the implementation.

Comment thread storage/remote/queue_manager.go Outdated
Comment on lines 545 to 548
if startSegment >= 0 {
t.watcher.SetStartSegment(startSegment)
}

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.

Is it necessary to do this as a separate check vs passing the param and letting the watcher do it?

Comment thread tsdb/wlog/watcher.go Outdated
@@ -513,6 +539,7 @@ func (w *Watcher) garbageCollectSeries(segmentNum int) error {
// Also used with readCheckpoint - implements segmentReadFn.
// TODO(bwplotka): Rename tail to !onlySeries; extremely confusing and easy to miss.
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 think we should do this TODO. I was also burned by this when reading this code in the past. I renamed it during my hacking (grafana/prometheus@staleness_disabling_v3.4.2...kgeckhart:prometheus:kgeckhart/replay-hacking#diff-0ab108a20060eb76d034c4fd1a9c5112cf981141f870d0211b1e73faead7f888L335) for very similar reasons.

Comment thread tsdb/wlog/watcher.go Outdated
// Also used with readCheckpoint - implements segmentReadFn.
// TODO(bwplotka): Rename tail to !onlySeries; extremely confusing and easy to miss.
func (w *Watcher) readSegment(r *LiveReader, segmentNum int, tail bool) error {
replay := w.startSegment >= 0
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.

Is this true? We would only be replaying if startSegment >=0 and currentSegment is < startSegment.

Comment thread tsdb/wlog/watcher.go Outdated
Comment on lines +340 to +348
var currentSegment int
if w.startSegment < 0 {
currentSegment, err = w.findSegmentForIndex(checkpointIndex)
} else {
// Respect checkpoint if it's ahead of the savepoint
// (segments before the checkpoint have been compacted away).
startIdx := max(w.startSegment, checkpointIndex)
currentSegment, err = w.findSegmentForIndex(startIdx)
}
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.

IIUC this is going to move the current segment ahead based on the starting segment which we don't want to do. When we startup we need to read everything to load the queue manager caches. We need to control the cut over point between reading to load the cache and reading to send data.

Comment thread storage/remote/write.go
Comment on lines +148 to +164
func (rws *WriteStorage) persistSavepoint() {
rws.mtx.Lock()
sp := rws.collectSavepoint()
rws.mtx.Unlock()

if err := sp.Save(rws.dir); err != nil {
rws.logger.Error("Failed to persist remote write savepoint", "err", err)
}
}

// persistSavepointLocked persists the savepoint while the mutex is already held.
func (rws *WriteStorage) persistSavepointLocked() {
sp := rws.collectSavepoint()
if err := sp.Save(rws.dir); err != nil {
rws.logger.Error("Failed to persist remote write savepoint", "err", err)
}
}
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.

Probably worth a comment about why these both exist. IIUC persistSavepointLocked is used for shutdown where the mutex is already held for the length of shutdown and persistSavepoint is more narrowly scoped lock wise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Simplified the part a bit in 555ef94

Comment thread storage/remote/write.go Outdated

// collectSavepoint updates the savepoint from current queue positions and returns a copy.
// Must be called with rws.mtx held.
func (rws *WriteStorage) collectSavepoint() Savepoint {
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'm not a fan of the side effect that we update rws and return the new Savepoint. I think we should decouple this because persistSavepointLocked doesn't need to update the rws value because it's being closed. persistSavepoint needs to do it but if the only place that it's done is there it can be done without holding the lock because it's only touched by persistSavepoint which won't be called in parallel.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed together with a previous item.

@x1unix x1unix force-pushed the x1unix/feat/rw-savepoint branch from e7f9a72 to eabd9de Compare April 13, 2026 14:08
@x1unix x1unix force-pushed the x1unix/feat/rw-savepoint branch from eabd9de to fe0e5e1 Compare April 13, 2026 15:43
@x1unix x1unix changed the title feat(remove_write): implement restart from segment-based savepoint feat(remote_write): implement restart from segment-based savepoint Apr 15, 2026
@x1unix x1unix changed the title feat(remote_write): implement restart from segment-based savepoint remote_write: implement restart from segment-based savepoint Apr 15, 2026
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.

2 participants