Skip to content
This repository has been archived by the owner on Nov 14, 2024. It is now read-only.

[PDS-403847] [III] Part 3: Accept Nodes Presenting Plausible-Evolution IDs Even If Not In Quorum #6768

Merged
merged 23 commits into from
Oct 12, 2023

Conversation

jeremyk-91
Copy link
Contributor

General

Before this PR:
Suppose we discover three new nodes in a Cassandra cluster when a quorum is not available. We currently attempt to get a quorum from them and then check if the topology they agree on is a plausible evolution of what the cluster has agreed on in the past - if so, we admit the nodes in the cluster that provided one of the topologies agreed upon by the quorum of new nodes. But this is unnecessary: we should not need a quorum of new nodes to be up if they agree with what we already have.

After this PR:
==COMMIT_MSG==
We now admit new nodes that match the existing topology even in the absence of a quorum of new nodes.
==COMMIT_MSG==

Priority: P2

Concerns / possible downsides (what feedback would you like?):

  • There's some room for debate on dissent when the cluster also had no quorum, though I viewed it as fine to add the non-dissenting nodes since the dissenting nodes could have been down anyway.
  • This could admittedly have been written as two parts but it's not too large overall so I opted not to do that.

Is documentation needed?: No

Compatibility

Does this PR create any API breaks (e.g. at the Java or HTTP layers) - if so, do we have compatibility?: No

Does this PR change the persisted format of any data - if so, do we have forward and backward compatibility?: No

The code in this PR may be part of a blue-green deploy. Can upgrades from previous versions safely coexist? (Consider restarts of blue or green nodes.): Yes

Does this PR rely on statements being true about other products at a deployment - if so, do we have correct product dependencies on these products (or other ways of verifying that these statements are true)?: No

Does this PR need a schema migration? No

Testing and Correctness

What, if any, assumptions are made about the current state of the world? If they change over time, how will we find out?: Nothing much

What was existing testing like? What have you done to improve it?: Added new tests

If this PR contains complex concurrent or asynchronous code, is it correct? The onus is on the PR writer to demonstrate this.: Rollback

If this PR involves acquiring locks or other shared resources, how do we ensure that these are always released?: No

Execution

How would I tell this PR works in production? (Metrics, logs, etc.): Migrations work

Has the safety of all log arguments been decided correctly?: I believe so.

Will this change significantly affect our spending on metrics or logs?: No. There's a small risk in that we've noted before that logging CassandraServer is expensive, but we do it only once per refresh.

How would I tell that this PR does not work in production? (monitors, etc.): Migrations don't work

If this PR does not work as expected, how do I fix that state? Would rollback be straightforward?: Rollback

If the above plan is more complex than “recall and rollback”, please tag the support PoC here (if it is the end of the week, tag both the current and next PoC):

Scale

Would this PR be expected to pose a risk at scale? Think of the shopping product at our largest stack.: No

Would this PR be expected to perform a large number of database calls, and/or expensive database calls (e.g., row range scans, concurrent CAS)?: No

Would this PR ever, with time and scale, become the wrong thing to do - and if so, how would we know that we need to do something differently?: Don't think so

Development Process

Where should we start reviewing?: CTV

If this PR is in excess of 500 lines excluding versions lock-files, why does it not make sense to split it?:

Please tag any other people who should be aware of this PR:
@jeremyk-91
@sverma30
@raiju

Copy link
Contributor

@mdaudali mdaudali left a comment

Choose a reason for hiding this comment

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

looks fine, although one flag about hard failures

import org.immutables.value.Value;

@Value.Immutable
public interface IdSupportingHostIdResult {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a confusing name, imo, but I don't have a good one offhand (it reads as if the host id supports IDs which is... odd, even though, from the exception message in check, I gathered that you mean that this includes results from a host that supports returning host ids)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Discussed offline: Let's go with NonSoftFailureHostIdResult. Your inference is correct, but we should make it explicit.

return newServersWithoutSoftFailures.keySet();
}
ConsistentClusterTopologies newNodesAgreedTopologies = maybeTopologies.get();
.filterValues(result -> result.result().type() == HostIdResult.Type.SUCCESS
Copy link
Contributor

Choose a reason for hiding this comment

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

consider: consider having IdSupporting... have derived type and result fields so you don't have result.result().type() e.t.c?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah let's do this! I thought about it - was a bit hesitant to include the Type field because it was on HostIdResult, but these are pretty coupled anyway

.filterValues(result -> result.result().type() == HostIdResult.Type.SUCCESS
&& pastTopologies.sharesAtLeastOneHostId(
result.result().hostIds()))
.toMap();
ConsistentClusterTopologies pastTopologySnapshot = pastConsistentTopologies.get();
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is now unnecessary (replaced with a hoist to L243)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

womp - bad merge conflict resolution on my part. Thanks

@Value.Check
default void check() {
Preconditions.checkArgument(
result().type() != HostIdResult.Type.SOFT_FAILURE,
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need to exclude hard failures too? Since they too will have an empty set of hostIds

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed: this was more NonSoftFailureHostIdResult as opposed to a host ID result with IDs, so hard failures are allowed here. We could extract another class for the Success case, but given that that's pretty much just before we extract the host IDs and manipulate the Set<String> I think the marginal value is a bit lower (whereas the "we got rid of soft failures" spans a bigger section of the code).

// connected by non-empty intersections.
return false;
}
Preconditions.checkArgument(!sets.contains(ImmutableSet.of()), "Empty sets of host ids are not allowed");
Copy link
Contributor

Choose a reason for hiding this comment

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

non-actionable: The existence of a type NonemptySet would be nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, it would :)

Copy link
Contributor

@mdaudali mdaudali left a comment

Choose a reason for hiding this comment

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

👍

@Value.Check
default void check() {
Preconditions.checkArgument(
result().type() != HostIdResult.Type.SOFT_FAILURE,
Copy link
Contributor

Choose a reason for hiding this comment

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

the nittiest of nits, and I don't really care if you don't action this: you can just use type!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

gah! this is my fault for not pushing changes all the way down

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and, as per recent discussions, you can be very sure this will be actioned 😉

@@ -389,8 +389,8 @@ public void returnsAllNewHostsIfNoConsensusOnNewHostsWhenNoQuorumAndCurrentServe
assertThat(hostsOffline).hasSizeGreaterThanOrEqualTo((ALL_HOSTS.size() + 1) / 2);
assertThat(validator.getNewHostsWithInconsistentTopologies(
mapToTokenRangeOrigin(newCassandraServers), allHosts))
.as("rejects all servers when no quorum from all hosts and no quorum on new hosts")
.containsExactlyInAnyOrderElementsOf(newCassandraServers);
.as("accepts servers with plausible evolution when no quorum from all hosts and no quorum on new hosts")
Copy link
Contributor

Choose a reason for hiding this comment

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

the test name doesn't match the tested behaviour anymore

@@ -537,8 +537,9 @@ public void returnsAllNewHostsIfNoConsensusOnConfigHostsWhenNoQuorumAndNoCurrent
splitHostOriginBetweenLastKnownAndConfig(
oldCassandraServers, Sets.difference(allCassandraServers, oldCassandraServers)),
allHosts))
.as("rejects all servers when no quorum from all hosts and no quorum on available hosts")
.containsExactlyInAnyOrderElementsOf(allCassandraServers);
.as("accepts new servers with a plausible evolution when no quorum from all hosts and no quorum on"
Copy link
Contributor

Choose a reason for hiding this comment

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

likewise here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catches! Will fix.

@bulldozer-bot bulldozer-bot bot merged commit 995bae6 into develop Oct 12, 2023
@bulldozer-bot bulldozer-bot bot deleted the jkong/403847-one-direction branch October 12, 2023 10:00
@svc-autorelease
Copy link
Collaborator

Released 0.954.0

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants