Skip to content

feat: introduction to LeaseRead #1362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 13, 2025
Merged

Conversation

ariesdevil
Copy link
Contributor

@ariesdevil ariesdevil commented May 7, 2025

Checklist

  • Updated guide with pertinent info (may not always apply).
  • Squash down commits to one or two logical commits which clearly describe the work you've done.
  • Unittest is a friend:)

This change is Reviewable

@drmingdrmer drmingdrmer self-requested a review May 7, 2025 14:05
@ariesdevil ariesdevil force-pushed the lease_read branch 2 times, most recently from 904dc85 to 3d418b8 Compare May 9, 2025 08:09
Copy link
Member

@drmingdrmer drmingdrmer left a comment

Choose a reason for hiding this comment

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

Reviewed 14 of 16 files at r1, all commit messages.
Reviewable status: 14 of 16 files reviewed, 3 unresolved discussions


openraft/src/config/config.rs line 96 at r1 (raw file):

    LeaseRead,
    ReadIndex,
}

It should not be a config entry. Let user decide which policy to use for read.

None is useless and should be removed.

Both policies are read-index based. The difference is that one of them use heartbeat to ensure leadership and the other use leader lease to ensure leadership. Use terminologies that reflect the purpose, such as Heartbeat and LeaderLease.

While there is not yet another ReadWritePolicy, ReadPolicy would be good enough for this enum than ReadOnlyPolicy.

Code quote:

#[derive(Clone, Debug, Display, PartialEq, Eq, ValueEnum)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ReadOnlyPolicy {
    None,
    LeaseRead,
    ReadIndex,
}

openraft/src/config/config.rs line 146 at r1 (raw file):

    /// The range of `leader_lease_ratio` is (0, 100], default value is 90.
    #[clap(long, default_value = "90")]
    pub leader_lease_ratio: u64,

In OpenRaft, the leader_lease is set to the value of election_timeout_max.
A follower start to elect if it does not receive a heartbeat in leader_lease + election_timeout:

See:
https://github.com/datafuselabs/openraft/blob/50cb1cc3aa7e561361d018b8be17a93b7c57771a/openraft/src/core/raft_core.rs#L1509

There is no need to add such a leader_elase_ratio.


openraft/src/engine/engine_config.rs line 50 at r1 (raw file):

                election_timeout,
                smaller_log_timeout: Duration::from_millis(config.election_timeout_max * 2),
                leader_lease: config.leader_lease_timeout(rand_election_timeout),

The leader_lease can not be a random value on each node. Every node must agree on the same leader lease.

@ariesdevil ariesdevil force-pushed the lease_read branch 2 times, most recently from 8e15dd6 to b94f329 Compare May 12, 2025 15:20
@ariesdevil ariesdevil marked this pull request as ready for review May 12, 2025 15:21
@ariesdevil ariesdevil force-pushed the lease_read branch 8 times, most recently from aff410a to beef138 Compare May 13, 2025 06:19
Copy link
Member

@drmingdrmer drmingdrmer left a comment

Choose a reason for hiding this comment

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

Reviewed 2 of 11 files at r2, 6 of 12 files at r3, 14 of 15 files at r4, 1 of 1 files at r5, all commit messages.
Reviewable status: 17 of 18 files reviewed, 7 unresolved discussions (waiting on @ariesdevil)


openraft/src/config/config.rs line 96 at r3 (raw file):

#[derive(Clone, Debug, Display, PartialEq, Eq, ValueEnum)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ReadOnlyPolicy {

This type does not need ValueEnum or Serialize

Code quote:

#[derive(Clone, Debug, Display, PartialEq, Eq, ValueEnum)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ReadOnlyPolicy {

tests/tests/client_api/t11_client_reads.rs line 265 at r5 (raw file):

    }

    // test LeaseRead policy

Split these two test cases into two function.


tests/tests/client_api/t11_client_reads.rs line 297 at r5 (raw file):

            .ensure_linearizable(leader, ReadPolicy::LeaseRead)
            .await
            .unwrap_or_else(|_| panic!("ensure_linearizable with `LeaseRead` failed for leader {}", leader));

This works because the append entries request during setting up the cluster behave as heartbeat.

The test itself should also assert that after a short while, LeaseRead will return an error.

And then trigger a round of heartbeat, with raft.trigger().heartbeat(). Then LeaseRead should return Ok again.

Code quote:

        router
            .ensure_linearizable(leader, ReadPolicy::LeaseRead)
            .await
            .unwrap_or_else(|_| panic!("ensure_linearizable with `LeaseRead` failed for leader {}", leader));

openraft/src/core/raft_core.rs line 280 at r3 (raw file):

            // Check if the lease is expired.
            if let Some(last_quorum_acked_time) = self.last_quorum_acked_time() {
                if now - last_quorum_acked_time < self.engine.config.timer_config.leader_lease {

last_quorum_acked_time may be greater than now and may underflow with the subtraction.
use now < last_quorum_acked_time + leader_lease instead.

Code quote:

            if let Some(last_quorum_acked_time) = self.last_quorum_acked_time() {
                if now - last_quorum_acked_time < self.engine.config.timer_config.leader_lease {

openraft/src/config/mod.rs line 9 at r5 (raw file):

pub use config::Config;
pub use config::ReadPolicy;

ReadPolicy is not config related. It's the argument of read APIs.
It should be in the raft mod.

@ariesdevil ariesdevil force-pushed the lease_read branch 2 times, most recently from aad023e to c39754c Compare May 13, 2025 11:59
…_log_id

This commit adds a `read_policy` parameter to the `ensure_linearizable` and `get_read_log_id` method, allowing users to explicitly specify whether to use `ReadIndex` or `LeaseRead` policy for linearizable reads.

This change enables fine-grained control over read consistency vs performance trade-offs on a per-request basis, rather than being limited to the global configuration.

- `ReadIndex` provides strongest consistency guarantees by confirming leadership with a quorum but incurs higher latency.

- `LeaseRead` uses leadership lease to avoid network round-trips for better performance but slightly weaker consistency guarantees.

Upgrade tip:

This is a breaking change. To update your code, you need to provide the `read_policy` parameter when calling `ensure_linearizable()` and `get_read_log_id()`.
Copy link
Member

@drmingdrmer drmingdrmer left a comment

Choose a reason for hiding this comment

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

LGTM

Reviewed 12 of 13 files at r6, 1 of 1 files at r7, 2 of 2 files at r8, all commit messages.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @ariesdevil)

Copy link
Member

@drmingdrmer drmingdrmer left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @ariesdevil)

@drmingdrmer drmingdrmer added this pull request to the merge queue May 13, 2025
Merged via the queue into databendlabs:main with commit 1517792 May 13, 2025
34 checks passed
@ariesdevil ariesdevil deleted the lease_read branch May 13, 2025 12:32
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