Skip to content

Consume the replication status topic from the earliest offset - #2788

Open
delthas wants to merge 1 commit into
development/9.4from
improvement/BB-826/status-consumer-from-offset-earliest
Open

Consume the replication status topic from the earliest offset#2788
delthas wants to merge 1 commit into
development/9.4from
improvement/BB-826/status-consumer-from-offset-earliest

Conversation

@delthas

@delthas delthas commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

The problem

The replication status processor consumes the backbeat-replication-status topic and writes each replication's outcome back to the source object's metadata (COMPLETED/FAILED per site); FAILED entries also feed the failed-CRR listing so the object can be retried.

Its Kafka consumer never specified where to start reading when a partition has no saved position (no committed offset). In that case the client default applies: start at the end of the log, skipping whatever is already there. That default is never noticed in normal operation, because the group commits as it consumes and always finds its saved position. But it turns one specific race into permanent data loss:

  1. the status processor restarts (rolling update, crash) — for a few seconds the consumer group has no member;
  2. during that gap, a status message is produced onto a partition that never had a saved position (typically its first message ever — common on fresh deployments where every partition starts empty);
  3. the new pod comes up, finds no saved position for that partition, starts at the end of the log — after the message.

Nobody ever reads that status, and nothing re-sends it: the data processor produced it, got its delivery acknowledgment, and moved on. The object's metadata says PENDING forever, and since the failure never reached the failed-CRR listing, it cannot even be retried.

Impact on Zenko CI

This is the second half of the top CTST Replication flake, alongside the populator freeze fixed by #2787. In run 29589236290 attempt 1: the status-processor pod was rolled at 15:37:36; the FAILED status for the test's object landed on partition 4, offset 0 — first message ever on that partition — at 15:38:36, during the member gap; the replacement pod joined at 15:38:57 and started at offset 1. The "Re-replicate objects that failed to replicate" scenario (23 of 257 runs failed over two months) then waited its full 500s for a FAILED status that no longer existed anywhere — no test timeout can save this case, which is what makes it the unrecoverable variant of the flake.

The two fixes are complementary: #2787 removes the freezes and zombie pods that cause most of the red runs — and most of the restarts that open these gaps in the first place; this PR ensures that when a restart does happen (deploys never stop), a status produced during the gap is processed late instead of never. Together they should close out the Replication cluster (49 of 257 runs). Outside CI, the same race exists on any production rolling update, with the same silent consequence.

The fix

Set fromOffset: 'earliest' on the status consumer: with no saved position, read the partition from the beginning. Steady state is unchanged (a saved position always wins over this setting), and the oplog LogConsumer already pins earliest for the same reason. In the worst case — a group that lost all its saved positions, e.g. after sitting empty past the offset retention — the consumer replays what the topic still retains (bounded by topic retention, broker default 7 days), and UpdateReplicationStatus re-reads the object's current metadata and skips anything already in a final state, so a replay mostly heals stuck-PENDING objects at the cost of temporary consumer lag.

The consumer options move to a _getConsumerOptions() method so a unit test can pin the option on this component, and two functional tests pin the semantics against a real kafka: an 'earliest' consumer picks up a message produced before its group existed, and a default-configured consumer provably skips it — the exact loss mode this PR removes.

Issue: BB-826

@bert-e

bert-e commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Hello delthas,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request TBA
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.59%. Comparing base (b58b9cf) to head (7ffd0cc).
⚠️ Report is 8 commits behind head on development/9.4.

Additional details and impacted files

Impacted file tree graph

Files with missing lines Coverage Δ
...ationStatusProcessor/ReplicationStatusProcessor.js 68.51% <100.00%> (+0.19%) ⬆️

... and 6 files with indirect coverage changes

Components Coverage Δ
Bucket Notification 80.22% <ø> (ø)
Core Library 80.80% <ø> (-0.57%) ⬇️
Ingestion 70.63% <ø> (-0.61%) ⬇️
Lifecycle 79.06% <ø> (ø)
Oplog Populator 85.83% <ø> (ø)
Replication 59.65% <100.00%> (-0.03%) ⬇️
Bucket Scanner 85.76% <ø> (ø)
@@                 Coverage Diff                 @@
##           development/9.4    #2788      +/-   ##
===================================================
- Coverage            74.88%   74.59%   -0.29%     
===================================================
  Files                  201      201              
  Lines                13754    13755       +1     
===================================================
- Hits                 10300    10261      -39     
- Misses                3444     3484      +40     
  Partials                10       10              
Flag Coverage Δ
api:retry 9.09% <0.00%> (-0.01%) ⬇️
api:routes 8.91% <0.00%> (-0.01%) ⬇️
bucket-scanner 85.76% <ø> (ø)
ft_test:queuepopulator 9.11% <0.00%> (-1.87%) ⬇️
ingestion 12.54% <0.00%> (-0.06%) ⬇️
lib 7.86% <0.00%> (+<0.01%) ⬆️
lifecycle 19.03% <0.00%> (+0.09%) ⬆️
notification 1.01% <0.00%> (-0.01%) ⬇️
oplogPopulator 0.14% <0.00%> (-0.01%) ⬇️
replication 18.65% <100.00%> (+0.02%) ⬆️
unit 51.60% <50.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@scality scality deleted a comment from bert-e Jul 28, 2026
@bert-e

bert-e commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Request integration branches

Waiting for integration branch creation to be requested by the user.

To request integration branches, please comment on this pull request with the following command:

/create_integration_branches

Alternatively, the /approve and /create_pull_requests commands will automatically
create the integration branches.

@delthas
delthas force-pushed the improvement/BB-826/status-consumer-from-offset-earliest branch from 78e34fd to 068abe5 Compare July 28, 2026 14:56
@delthas
delthas requested review from a team, SylvainSenechal and benzekrimaha July 28, 2026 14:59
@delthas
delthas force-pushed the improvement/BB-826/status-consumer-from-offset-earliest branch 2 times, most recently from 4839979 to 5ca65f9 Compare July 29, 2026 08:05
Comment thread tests/functional/lib/BackbeatConsumer.js Outdated
Comment thread tests/functional/lib/BackbeatConsumer.js Outdated
@benzekrimaha

Copy link
Copy Markdown
Contributor

Does the same gap exist in the other backbeat consumers (queue processor, lifecycle, GC, notification)? Whichever ones don't pin fromOffset have the same latent behavior on a fresh group. If out of scope here, can we please have a followup ticket for the audit?

The replication status consumer did not set fromOffset, so
auto.offset.reset fell back to librdkafka's default `latest`. A status
message produced onto a partition with no committed offset while the
consumer group had no member (e.g. during a rolling update) was then
skipped forever once the new consumer resolved its start position: the
source object's metadata stayed PENDING permanently and the failed-CRR
machinery never learned about the failure.

Set fromOffset to 'earliest' so such messages are processed late
instead of never. The reset policy is only consulted when a partition
has no committed offset, so steady state is unaffected; worst-case
replay after a total offset loss is bounded by the topic retention,
and UpdateReplicationStatus skips entries whose object already reached
a final state.

Extract the consumer options into _getConsumerOptions() for unit
coverage, and add functional tests pinning the fromOffset semantics on
a partition that predates the consumer group.

Issue: BB-826
@delthas
delthas force-pushed the improvement/BB-826/status-consumer-from-offset-earliest branch from 5ca65f9 to 7ffd0cc Compare July 30, 2026 08:35
@delthas

delthas commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Good question — the audit is done, results are in a table on the BB-826 ticket. Summary: no extension consumer pins fromOffset today; by loss class, the notification queue processor (lost message = notification never delivered) and the GC service consumer (lost entry = orphaned storage) share this bug's failure mode, while the lifecycle processors are self-healing (the conductor re-enumerates every cycle) and the replication data processor needs its own assessment. Follow-up filed as BB-831 for the notification + GC consumers, reusing this PR's pattern.

@delthas
delthas requested a review from benzekrimaha July 30, 2026 08:42
@SylvainSenechal

SylvainSenechal commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Good question — the audit is done, results are in a table on the BB-826 ticket. Summary: no extension consumer pins fromOffset today; by loss class, the notification queue processor (lost message = notification never delivered) and the GC service consumer (lost entry = orphaned storage) share this bug's failure mode, while the lifecycle processors are self-healing (the conductor re-enumerates every cycle) and the replication data processor needs its own assessment. Follow-up filed as BB-831 for the notification + GC consumers, reusing this PR's pattern.

interesting that you mention notification queue processor has the same issue, as this is one of the test that's really often flaky

@SylvainSenechal SylvainSenechal 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.

when all related pr are merged, try to release a backbeat version so we can check if this was really the reason for the flaky tests, it should be visible quickly by doing 2/3 zenko ci run.

Imo this is probably it, as I previously saw the backbeat pods were restarting very often, so i figured some messages were dropped/mishandled during these events

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