Add Cassandra 6.0 support - #236
Conversation
a6243ba to
443b3fb
Compare
sarankk
left a comment
There was a problem hiding this comment.
Thanks for this Mick! changes look good to me, left for few comments
| Consumer<Writer> consumer) | ||
| { | ||
| File cassFile = new File(directory.toFile()); | ||
| try (SSTableTombstoneWriter writer = SSTableTombstoneWriter.builder() |
There was a problem hiding this comment.
Shall we use CQLSSTableWriter here, since 6.0 version's CQLSSTableWriter takes any modification statement. Changes in SSTableTombstoneWriter are very similar to one in CQLSSTableWriter. Also with this, we can remove SSTableTombstoneWriter in 6.0 bridge.
| { | ||
| return partitioner.toLowerCase().contains("random") | ||
| ? RandomPartitioner.instance | ||
| : Murmur3Partitioner.instance; |
There was a problem hiding this comment.
Murmur3Partitioner.instance variable seems to be available in 4.0 version as well. Shall we update this in 4.0 bridge and remove the class override here.
| /** | ||
| * Unit tests for configuring {@link SSTableWriterImplementation} | ||
| */ | ||
| class SSTableWriterImplementationTest |
There was a problem hiding this comment.
We can remove this test class as well if we update in 4.0 version
There was a problem hiding this comment.
not sure i understand.
the six-zero copy replaces the five-zero one in the source set. Deleting it compiles the five-zero copy against 6.0, which asserts da/oa sstable versions and then calls the removed getRawConfig().sstable.selected_format.
| rowBuilder.addCell(CqlType.expiring(cd, timestamp, ttl, now, type().serialize(o), | ||
| CellPath.create(TimeUUID.Generator.nextTimeUUID().toBytes()))); |
There was a problem hiding this comment.
How about we use randomCellPath() instead in 4.0 types module? we can remove override in 6.0 types
| rowBuilder.addCell(CqlType.expiring(cd, timestamp, ttl, now, type().serialize(o), | |
| CellPath.create(TimeUUID.Generator.nextTimeUUID().toBytes()))); | |
| rowBuilder.addCell(CqlType.expiring(cd, timestamp, ttl, now, type().serialize(o), randomCellPath())); |
There was a problem hiding this comment.
yes, and fixes a 5.0 defect
| import org.apache.cassandra.utils.TimeUUID; | ||
|
|
||
| /** | ||
| * A copy of the four-zero class. Cassandra 5.0 removed {@code UUIDGen.getTimeUUID()}, so the inherited |
There was a problem hiding this comment.
TimeUUID in 5.0 types already overrides randomValue method, we can remove this class in 6.0
9b9e1b1 to
fc607e7
Compare
Cassandra Analytics reads, bulk writes and consumes CDC through one bridge jar per major version. This adds the SIXZERO bridge, pinned to 6.0-alpha2 from Maven Central, in five new modules that copy and override the five-zero and four-zero sources. Five 6.0 changes needed work: - SSTable versions big-pa and bti-ea, both added for compression dictionary metadata in the CompressionInfo component. - A trailing dictionary section in CompressionInfo. The six-zero CompressionMetadata deserializes it, interns one dictionary per dictId so that readers of one SSTable share the zstd compressor that ZstdDictionaryCompressor keys by dictId, and releases the references it holds through close(). SSTableCache gained a removal listener to call close(), and SSTableReader holds a second reference for the life of a stream. - CEP-21 Transactional Cluster Metadata retypes Schema.instance to SchemaProvider and replaces transform() with submit(), which notifies no listener offline, so SchemaUpdater initializes the keyspace instances by hand after ClusterMetadataService.initializeForClients(). - Commit log version 9 and messaging version 14, read through the shaded 6.0 deserializers. - PartitionUpdate.PartitionUpdateSerializer.deserialize catches UnknownTableException and rethrows CoordinatorBehindException(message, exception), a plain RuntimeException. BufferingCommitLogReader skips a mutation whose table is absent from Schema.instance, because one commit log serves every table and a CDC reader is given only the tables it was supplied, and that skip never happened on 6.0: catch (UnknownTableException) did not fire, so the failure reached handleUnrecoverableError, which calls requestTermination(), and the scanner re-read the same segment for as long as it existed. Measured against 6.0-alpha2 with one table missing from the supplied schema, the reader re-read one segment for eight minutes and published nothing while cdc_raw grew from 76 segments to 125. The reader now looks through the cause chain rather than matching the declared type. Skipping is safe wherever the wrapping happens, because readSection has already read the mutation's bytes out of the file before calling this method, so the file pointer is past the mutation either way. Four classes the six-zero modules first copied are gone again, because the module below serves every version. The four-zero determineSupportedPartitioner takes Murmur3Partitioner.instance and RandomPartitioner.instance rather than a new instance, since 6.0 makes both constructors non-public, which leaves the six-zero SSTableWriterImplementation with nothing to change. writeTombstoneSSTable builds a CQLSSTableWriter, which on 6.0 accepts any modification statement and writes the range tombstones of a slice delete through the same DeleteStatement.createSlices path the copy used, so the 6.0 bridge needs no SSTableTombstoneWriter. AbstractCqlList.addCell built the cell path of an expiring list cell from UUIDGen.getTimeUUIDBytes(), which 5.0 removed, so both branches now call randomCellPath(), the per-version hook the other branch already used; that path is reached only from CdcBridge.log, a test utility, so no released bridge wrote a list cell through it. Five-zero's TimeUUID overrides randomValue, which left the six-zero AbstractTimeUUID override unreachable. Two defects in the existing bridges surfaced while testing the copies and are fixed here. CassandraSchema.unregisterNonCdcTable called SchemaUpdater.load, which on 5.0 only adds a keyspace and throws AlreadyExistsException for one that exists, so the table stayed registered; removeTables replaces the keyspace metadata instead. On 5.0 that commit reaches Keyspace.dropCf, which needs machinery a client-mode process never started, so removeTables clears the initialized flag for the commit only. BridgeVersionConsistencyTest asserts that every type a bridge hands out reports the bridge's own version, which catches a six-zero module that silently inherits a version-bearing class from five-zero through the Gradle copy-forward. patch by Mick Semb Wever; reviewed by Saranya Krishnakumar for CASSANALYTICS-37 Assisted-by: Claude Code:claude-opus-5
fc607e7 to
5523671
Compare
|
squashed and rebased off trunk |
jyothsnakonisa
left a comment
There was a problem hiding this comment.
Hey Mick! looks good overall leaving a few comments, also please rebase this PR.
| * | ||
| * <p>Call this from {@link #beforeClusterProvisioning()}, which runs before the cluster starts.</p> | ||
| */ | ||
| protected void assumeTopologyChangeHooksSupported() |
There was a problem hiding this comment.
This will skip several topology change related tests for 6.0 version permanently. WDYT about re writing the tests with 6.0 equivalent way of removed methods.
If you think that is too involved, we should consider doing a followup PR to fix these tests. As a starting point, BootstrapAndJoin.bootstrap(...) and UnbootstrapStreams.execute(...) look like reasonably direct replacements for the old StorageService.bootstrap()/unbootstrap() hooks
There was a problem hiding this comment.
yes, see CASSANALYTICS-112 (i'll offer a separate PR for it)
| DeserializationHelper.Flag.LOCAL); | ||
| } | ||
| catch (UnknownTableException ex) | ||
| catch (Throwable t) |
There was a problem hiding this comment.
Thanks for fixing this, I saw an issue with this earlier.
| private static final Pattern VERSION_PATTERN_3 = Pattern.compile("(?:.+-)?([0-9]+)\\.([0-9]+)\\.([0-9]+)([a-zA-Z0-9-]*)"); | ||
| private static final Pattern VERSION_PATTERN_4 = Pattern.compile("(?:.+-)?([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)[a-zA-Z0-9-]*"); | ||
| private static final Pattern VERSION_PATTERN_SNAPSHOT = Pattern.compile("(?:.+-)?([0-9]+)\\.([0-9]+)-(SNAPSHOT)$"); | ||
| // A major version carries no patch number until its first release: cassandra-4.0-SNAPSHOT, cassandra-6.0-alpha2, |
There was a problem hiding this comment.
Since version pattern is changing can you please add tests for "major.minor-SNAPSHOT" with no alpha/rc tag " versions?
There was a problem hiding this comment.
added in CassandraVersionFeaturesTest
see 16d6c8f
There was a problem hiding this comment.
can you please add tests for this class? there are tests for this 5.0 class, may be you can copy them for 6.0 class
There was a problem hiding this comment.
is it needed…?
The five-zero test directory is included in the six-zero module.
see locally after a run build/test-results/cassandra-six-zero-bridge/testBti/TEST-org.apache.cassandra.io.sstable.format.bti.BtiReaderUtilsTest.xml
| .<SSTable, Optional<CompressionMetadata>>removalListener(notification -> { | ||
| Optional<CompressionMetadata> metadata = notification.getValue(); | ||
| if (metadata != null) | ||
| { | ||
| metadata.ifPresent(CompressionMetadata::close); | ||
| } | ||
| }) |
There was a problem hiding this comment.
nit: needs a test, can you force an entry out of this cache and verify the dictionary ref actually got released.
There was a problem hiding this comment.
added CompressionDictionaryTests.testCacheEvictionReleasesDictionaryReference()
it needed package-private evictCompressionMetadata(SSTable) (the cache field is private and nothing could force an entry out), and CompressionDictionaryTests.testCacheEvictionReleasesDictionaryReference.
see 16d6c8f
| @Nullable CompressionMetadata compressionMetadata = SSTableCache.INSTANCE.compressionMetadata(ssTable, | ||
| version.hasMaxCompressedLength(), | ||
| metadata.params.crcCheckChance); | ||
| dictionaryRef = compressionMetadata != null ? compressionMetadata.acquireDictionaryRef() : null; |
There was a problem hiding this comment.
dictionaryRef is acquired here, but openDataStream(), CompressedRawInputStream.from(...), and skipFully(...) can still throw after the reference is acquired, leaking it since no one calls close() on a constructor that never returned.
Since dictionaryRef is only used later in close(), can we move its acquisition to the end of the constructor, after everything that can throw has already succeeded?
There was a problem hiding this comment.
The dictionary itself is used twice before the constructor returns: CompressedRawInputStream.from(...) allocates through CompressionMetadata.compressor(), and skipFully(...) decompresses every chunk it skips.
see 16d6c8f
https://issues.apache.org/jira/browse/CASSANALYTICS-37
Cassandra Analytics reads, bulk writes and consumes CDC through one bridge jar per major version. This adds the SIXZERO bridge, pinned to 6.0-alpha2 from Maven Central, in five new modules that copy and override the five-zero and four-zero sources.
Four 6.0 changes needed work:
Two defects in the existing bridges surfaced while testing the copies and are fixed here. CassandraSchema.unregisterNonCdcTable called SchemaUpdater.load, which on 5.0 only adds a keyspace and throws AlreadyExistsException for one that exists, so the table stayed registered; removeTables replaces the keyspace metadata instead. On 5.0 that commit reaches Keyspace.dropCf, which needs machinery a client-mode process never started, so removeTables clears the initialized flag for the commit only.
BridgeVersionConsistencyTest asserts that every type a bridge hands out reports the bridge's own version, which catches a six-zero module that silently inherits a version-bearing class from five-zero through the Gradle copy-forward.