Skip to content

perf: send one AppendEntries RPC per matching-point probe - #2077

Merged
drmingdrmer merged 1 commit into
databendlabs:mainfrom
zhixinwen:perf/one-rpc-probe
Sep 20, 2026
Merged

drmingdrmer merged 1 commit into
databendlabs:mainfrom
zhixinwen:perf/one-rpc-probe

Conversation

@zhixinwen

@zhixinwen zhixinwen commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Matching-point discovery drains every storage-limited chunk of a probe range before choosing the next midpoint. For example, a 500-entry probe split into ten storage reads can require ten RPCs even though the first response provides enough information to continue discovery.

This change sends one entry-carrying AppendEntries request per probe. Storage may return a prefix of the candidate range; once the follower acknowledges an entry beyond prev, the engine finishes the probe and recomputes the next midpoint. The existing max_payload_entries cap still applies.

The engine reuses Inflight::Logs: both probes and the leader's local progress update complete on the first acknowledgement beyond prev (the local update acknowledges the entire range immediately). Payload::Probe gives the replication task the probe-specific sending and retry rules, separate from commit-only appends. Completion is handled centrally by Payload::update_matching(), so next_request needs no probe-specific branch. Pipeline replication is unchanged, and all changed types are internal.

The engine and replication task share LogIdRange::probe_completed_by(). PartialSuccess(prev) verifies prev but acknowledges no additional entry, so the same probe is retried. A session with no response also leaves it pending. This decision uses the current session's maximum acknowledgement, since remote_matched can contain progress from earlier sessions. Keeping the maximum prevents a later, lower response from undoing the session's completion decision.

Tests cover:

  • A storage reader returning entry 53 for (52, 60]: the request stream ends without reading or sending the suffix. An initial empty storage read leaves the probe pending.
  • A prefix acknowledgement completing the inflight probe and causing the engine to choose a new midpoint.
  • A fresh-learner probe (prev = None) remaining pending without an acknowledgement and completing when the first entry is acknowledged.
  • Zero-entry partial responses: both followers must issue a second entry-carrying request before the test lifts the quota, then catch up. Synchronization uses per-follower retry notifications.

A transport closing its response stream without answering is not directly covered by a network mock.

Validation: formatting and all three workspace Clippy configurations pass. make verify encounters a local C++ standard-header error while building the unchanged RocksDB example. make -o fmt_check -o clippy verify passes documentation checks, library tests, integration tests, and the remaining macro checks.


This change is Reviewable

@zhixinwen

zhixinwen commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor Author

I am reopening this PR. As commented in #2073 (comment), changing size to 8 can cause regression to other users and it makes the binary search within small gap slower. Even with #2076, we would still need binary search so this optimization would still help.

@zhixinwen
zhixinwen marked this pull request as ready for review September 16, 2026 04:15
@drmingdrmer

Copy link
Copy Markdown
Member

Thank you, @zhixinwen, for identifying this issue, implementing the optimization, and asking us to help refine it.

The core idea remains unchanged: once a probe sends an entry-carrying request, its response contains enough information to choose the next midpoint. Sending the rest of the candidate range only delays matching-point discovery.

We made a follow-up refactor on foo to make this behavior easier to reason about:

  • Removed Payload::LogIdRange and renamed Inflight::Logs to Inflight::Probe.
  • Kept LogIdRange as a data type. Probe-specific completion logic now belongs to Payload and Inflight.
  • Made Payload::update_sent() and update_matching() return the remaining payload. A sent range is never returned for another send.
  • Defined probe completion as acked >= prev, including an empty acknowledgement for a fresh learner.
  • Changed SessionOutcome to contain only the acknowledgement and remaining payload. Option<Option<LogId>> distinguishes no response from an acknowledgement of None.
  • Simplified response handling to return the greatest acknowledgement and the reason for stopping.
  • Unified handling of Some(log_id) and None acknowledgements, and removed the duplicate remote_matched update.
  • Updated the unit and integration tests for limited storage reads, empty probes, partial success, and choosing the next midpoint.

This preserves the optimization while making the probe lifecycle and retry rules more explicit. make verify passes.

drmingdrmer
drmingdrmer previously approved these changes Sep 19, 2026

@drmingdrmer drmingdrmer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@drmingdrmer reviewed 23 files and all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on zhixinwen).

drmingdrmer
drmingdrmer previously approved these changes Sep 20, 2026

@drmingdrmer drmingdrmer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@drmingdrmer reviewed 5 files and all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on zhixinwen).

# Summary

Stop a matching-point probe after its first entry-carrying
AppendEntries request. A storage byte limit can no longer split one
probe into several entry-carrying RPCs before the next midpoint is
chosen.

# Details

Model bounded probes explicitly in `Payload` and `Inflight`, separate
from open-ended log streaming. Keep probe behavior out of `LogIdRange`
and let each payload update return only the work left to send:
`run_stream_session()` returns the remaining payload and
`ReplicationCore::main()` stores it as the next action.

Complete a probe when a response acknowledges `prev` or a later log.
Track acknowledgements as `Option<Option<LogId>>` so no response stays
distinct from an acknowledgement of the empty log position, and forward
`Ok(None)` to RaftCore so a probe that starts at `None` completes.

A storage read that returns no entry still sends the request with
`prev` and no entries; the engine's probe completes when the follower
acknowledges `prev`. An empty probe still sends one commit-only
request. RPC and conflict interruptions leave rescheduling to RaftCore.

The integration test `t11_probe_partial_success_without_entry` records
the `prev_log_id` of the first two entry-carrying probes to each
follower: with 40 writes they are index 15 and then 23, where the
previous code retried index 15; with 8 writes both are `None`.

@drmingdrmer drmingdrmer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@drmingdrmer reviewed 5 files and all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on zhixinwen).

@drmingdrmer
drmingdrmer merged commit 8c790e5 into databendlabs:main Sep 20, 2026
52 checks passed
@zhixinwen
zhixinwen deleted the perf/one-rpc-probe branch September 25, 2026 01:56
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