Skip to content

CASSANALYTICS-194: Parse <replicas>/<transient> replication factor fo… - #238

Open
mansikhara wants to merge 6 commits into
apache:mutation-tracking-supportfrom
mansikhara:mutation-tracking-support
Open

mansikhara wants to merge 6 commits into
apache:mutation-tracking-supportfrom
mansikhara:mutation-tracking-support

Conversation

@mansikhara

Copy link
Copy Markdown
Contributor

…r witness-enabled keyspaces

Cassandra accepts a replication factor of the form /, which witness replicas under mutation tracking reuse, so a witness-enabled keyspace declares 'datacenter1': '3/1' meaning three replicas of which one is a witness.

CqlUtils.extractReplicationFactor() passed each datacenter value to Integer.parseInt, so a bulk read against any such keyspace failed during job setup with an uncaught NumberFormatException.

Transient counts are now tracked per datacenter alongside the existing totals and exposed through getFullReplicationFactor(), getTransientReplicationFactor(), getFullReplicas(dc), getTransientReplicas(dc) and hasTransientReplicas(). getTotalReplicationFactor() keeps its existing meaning of all replicas including witnesses, so behaviour for untracked keyspaces is unchanged. Parsing applies the same constraints Cassandra enforces in locator.ReplicationFactor.validate. A new parseStrict factory reports an unparseable or empty replication map at parse time rather than dropping the datacenter and failing later with a misleading "DC not found in replication factor"; the lenient constructor is retained unchanged for CDC callers. The Kryo serializer and CassandraRing's hand-rolled JDK readObject/writeObject are updated so the new field survives serialization to Spark executors.

Prerequisite for CASSANALYTICS-164.

sarankk and others added 2 commits July 7, 2026 16:13
… keyspace for bulk writes (apache#214)

patch by Saranya Krishnakumar; reviewed by Shailaja Koppu, Yifan Cai for CASSANALYTICS-160
…r witness-enabled keyspaces

Cassandra accepts a replication factor of the form <replicas>/<transient>, which witness
replicas under mutation tracking reuse, so a witness-enabled keyspace declares
'datacenter1': '3/1' meaning three replicas of which one is a witness.

CqlUtils.extractReplicationFactor() passed each datacenter value to Integer.parseInt, so a
bulk read against any such keyspace failed during job setup with an uncaught
NumberFormatException.

Transient counts are now tracked per datacenter alongside the existing totals and exposed
through getFullReplicationFactor(), getTransientReplicationFactor(), getFullReplicas(dc),
getTransientReplicas(dc) and hasTransientReplicas(). getTotalReplicationFactor() keeps its
existing meaning of all replicas including witnesses, so behaviour for untracked keyspaces
is unchanged. Parsing applies the same constraints Cassandra enforces in
locator.ReplicationFactor.validate. A new parseStrict factory reports an unparseable or
empty replication map at parse time rather than dropping the datacenter and failing later
with a misleading "DC not found in replication factor"; the lenient constructor is retained
unchanged for CDC callers.

The Kryo serializer and CassandraRing's hand-rolled JDK readObject/writeObject are updated
so the new field survives serialization to Spark executors. CassandraRing gains a pinned
serialVersionUID and a format-version byte, because the class signature did not change and
an older stream would otherwise be silently misread rather than rejected.

Prerequisite for CASSANALYTICS-164.

patch by Mansi Khara; reviewed by TBD for CASSANALYTICS-194

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

Thanks Mansi, left few comments

…ictly

Combines the per-datacenter total and transient counts into a single Map<String, ReplicaCounts>
instead of two parallel maps. The maps could previously drift apart, since nothing tied
options={dc1=3} to transientOptions={dc1=1}; holding them together makes that unrepresentable.
ReplicaCounts becomes a public nested type exposing allReplicas, fullReplicas and
transientReplicas. getOptions() and getTransientOptions() are retained, now derived and
unmodifiable, so CassandraRing, ConsistencyLevel and PartitionedDataLayer are unaffected.

Removes the lenient parse. Its only two callers built hardcoded maps that cannot fail to parse,
so it was never needed, and silently dropping a datacenter that Cassandra itself would reject is
the wrong default: 3/3 and a negative replication factor now raise rather than being skipped.
parseStrict therefore disappears as a separate factory, since strict is the only behaviour.

Corrects the javadoc: transient replication predates mutation tracking, and it is witness
replicas (CEP-46) that reuse the form. Drops replication_type from the replication factor test
fixtures, since Sidecar builds its schema response from the driver's exportAsString() which does
not emit that property, and it is irrelevant to replication factor parsing.
@mansikhara
mansikhara force-pushed the mutation-tracking-support branch from e2d75d0 to 3a07399 Compare September 15, 2026 20:45

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

Thanks Mansi!, few more comments.

Restructures the constructors so each one resolves its input to per-datacenter counts and
delegates to a single canonical constructor, rather than repeating the class lookup, the
class-key skip and the empty check. Parsing raw strings and merging separate total and transient
maps move into named helpers.

Removes the validate flag from the private constructor. It was only ever passed false, so the
guarded branch was dead: the flag was left over from an earlier version of readResolve that
reconstructed the instance instead of rejecting it.

CassandraRing now writes the ReplicationFactor as an object rather than destructuring it into
strategy, options and transient options. ReplicationFactor is Serializable, so this is both
shorter and independent of how it represents per-datacenter counts, which is what made the
previous version need updating in the first place.

Drops the explanatory comment from the replication factor test fixture.

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

Changes look good

…center consistently

The aggregate counts and the two derived per-datacenter maps were recomputed on every call, and
getOptions in particular is called in a loop from CassandraRing.init. They are now computed once
in the canonical constructor, which this class can do because it is immutable.

The derived fields are transient, so they are absent from the serialized form rather than
duplicating what replication already holds. readResolve rebuilds through the canonical
constructor, and the Kryo serializer already went through it, so neither path can leave them out
of step. Tests cover both round trips.

getTransientReplicas returned 0 for a datacenter with no replication factor while getFullReplicas
threw for the same input. That conflated "no transient replicas" with "not a datacenter of this
keyspace" and hid the second case. It now throws, matching getFullReplicas. No production caller
passes an unknown datacenter.

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

+1 Thanks Mansi, LGTM

@sarankk
sarankk force-pushed the mutation-tracking-support branch from 6064e02 to b0c400b Compare September 18, 2026 22:46
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