-
Notifications
You must be signed in to change notification settings - Fork 175
Improve: Ensure uniform membership config during config changes #1351
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
Improve: Ensure uniform membership config during config changes #1351
Conversation
That's a nice, minimal way to implement this flattening operation :) Now we just need to think of a way to make the |
27dbd28
to
1e5aee2
Compare
Oh, I just had a thought: I don't think this implementation works for removing |
Hmm... correct |
@tvsfx |
I've had some more time to think about this PR just now, and what I said above isn't correct. Imagine that we have a batch as follows (with
So unlike what my comment above said, this seems like it would "work", in the sense that we're not left in a bad state but error out. The reason I got confused is that I forgot that changes to Let me repeat an old example of mine:
So when changing cluster membership concurrently, the |
So what to do about We need to solve the issue of not knowing which change causes voters to be deleted. The idea is that if
Examples of how this works (see also 1):
Footnotes
|
Hmm... Can I just simplify the above logic to: Change membership from ver-1 to ver-2, by adding explicit
ver-1 has a constraint that each
ver-2 constraint: in each tuple in In short, learner ids is a config for each step config but is not for the entire joint config. |
I was trying to change the At first, I didn't really see what the advantage would be of tracking
Explicitly representing the learners in the above example, we can now have the transition: I would consider:
|
1e5aee2
to
f5b0161
Compare
You're right. The new structure provides much more flexible and complete state representation for membership. The API changes aren't a concern - we can deprecate the old variants and introduce new ones that better reflect the purpose of each operation. However, there's one important consideration: modifying the internal structure of Regarding the membership structure modifications, I'd appreciate your help reviewing this change to ensure it improves the membership change API capabilities. I plan to implement the internal modifications to use the new membership layout in a separate pull request. |
What is your policy on these types of breaking changes? Are they to be avoided at all costs?
Sure, I'll review your PRs :) Do you want me to review this PR in its current state, or are there changes you still want to make following our discussion above? |
There are two kinds of breaking changes: API changes and storage data format changes. API changes are easily handled. We can add deprecation hints and introduce new APIs, informing users to switch to the new ones. All warnings will be shown during compilation time, so users need to update their code to upgrade to a new version of OpenRaft. Data format changes are more complicated, such as the change to
I'm going to use the third approach. First, I'll make membership a trait and add another associated type Then I'll add a new implementation of the
In this scenario, if the user wants to upgrade the
The upgrading process needs to be phased. In the first phase, upgrade nodes one by one to operate with code that can load both old and new versions of serialized data. In the second phase, upgrade the nodes one by one to a version that starts using
This PR won't add other changes. I'll put the new format of Membership in a new issue. |
@tvsfx |
This commit improves the handling of membership configuration changes to ensure that the system reliably transitions to a uniform configuration in the second step of a joint membership change. ### Problem: During a membership config change, there are two steps: 1. Transition to a joint configuration containing both `Cold` (current config) and `Cnew` (new config). 2. Transition to a uniform configuration containing only `Cnew`. Previously, the second step attempted to apply the same change on the current membership state. If multiple membership change requests were processed in parallel, this approach could result in the system being left in a joint configuration. For example: - Initial config: `{a, b, c}`. - Task 1: `AddVoterIds(x)`. After the first step: `[{a, b, c}, {a, b, c, x}]`. - Task 2: `RemoveVoters(x)`. After the first step: `[{a, b, c, x}, {a, b, c}]` (applied on the last config `{a, b, c, x}`). - Task 1 proceeds to the second step, re-applies `AddVoterIds(x)`, and the result is `[{a, b, c}, {a, b, c, x}]`. - The system remains in a joint configuration, which is unintuitive and contradicts standard Raft expectations. ### Solution: The second step now applies an **empty change request** (`AddVoterIds({})`) to the last configuration in the current joint config. This ensures that the system always transitions to a uniform configuration in the second step. ### Impact: - No behavior changes occur if only one membership change request is in progress. - If multiple requests are processed concurrently, the application must still verify the result, and the new behavior ensures the system transitions to a uniform state. This fix prevents the system from being left in an unintended joint configuration, improving consistency and adherence to Raft principles. Thanks to @tvsfx for providing feedback on this issue and offering a detailed explanation of the solution.
f5b0161
to
37d6943
Compare
FWIW, this PR looked good to me :) Apart from the |
Changelog
Improve: Ensure uniform membership config during config changes
This commit improves the handling of membership configuration changes to
ensure that the system reliably transitions to a uniform configuration
in the second step of a joint membership change.
Problem:
During a membership config change, there are two steps:
Cold
(currentconfig) and
Cnew
(new config).Cnew
.Previously, the second step attempted to apply the same change on the
current membership state. If multiple membership change requests were
processed in parallel, this approach could result in the system being
left in a joint configuration. For example:
Initial config:
{a, b, c}
.Task 1:
AddVoterIds(x)
. After the first step:[{a, b, c}, {a, b, c, x}]
.Task 2:
RemoveVoters(x)
. After the first step:[{a, b, c, x}, {a, b, c}]
(applied on the last config{a, b, c, x}
).Task 1 proceeds to the second step, re-applies
AddVoterIds(x)
, andthe result is
[{a, b, c}, {a, b, c, x}]
.The system remains in a joint configuration, which is unintuitive and
contradicts standard Raft expectations.
Solution:
The second step now applies an empty change request
(
AddVoterIds({})
) to the last configuration in the current jointconfig. This ensures that the system always transitions to a uniform
configuration in the second step.
Impact:
No behavior changes occur if only one membership change request is in
progress.
If multiple requests are processed concurrently, the application must
still verify the result, and the new behavior ensures the system
transitions to a uniform state.
This fix prevents the system from being left in an unintended joint
configuration, improving consistency and adherence to Raft principles.
Thanks to @tvsfx for providing feedback on this issue and offering a
detailed explanation of the solution.
This change is