From 2607b968c627a170d16cb3e08ccc827d9ceb1f20 Mon Sep 17 00:00:00 2001 From: cbornet Date: Fri, 15 May 2026 19:54:36 +0200 Subject: [PATCH 1/2] Add a property to configure the Digest file checksum type --- .../cassandra/cache/AutoSavingCache.java | 2 +- .../cassandra/db/compaction/Verifier.java | 15 ++- .../db/streaming/ComponentManifest.java | 2 +- .../compress/CompressedSequentialWriter.java | 22 +++- .../compress/EncryptedSequentialWriter.java | 3 +- .../cassandra/io/sstable/Component.java | 8 ++ .../io/sstable/format/SortedTableWriter.java | 42 ++++++- .../io/sstable/format/big/BigFormat.java | 4 + .../io/sstable/format/big/BigTableWriter.java | 4 +- .../format/trieindex/TrieIndexFormat.java | 4 + .../cassandra/io/util/ChecksumWriter.java | 10 +- .../io/util/ChecksummedSequentialWriter.java | 11 +- .../io/util/DataIntegrityMetadata.java | 22 +++- .../org/apache/cassandra/utils/CRC64NVME.java | 112 +++++++++++++++++ .../apache/cassandra/utils/ChecksumType.java | 39 +++++- .../distributed/test/SSTableDigestTest.java | 117 ++++++++++++++++++ .../test/microbench/ChecksumBench.java | 73 +++++++---- .../org/apache/cassandra/db/ScrubTest.java | 2 +- .../org/apache/cassandra/db/VerifyTest.java | 52 ++++++++ .../cassandra/io/sstable/ComponentTest.java | 2 + .../ChecksummedRandomAccessReaderTest.java | 6 +- .../util/ChecksummedSequentialWriterTest.java | 2 +- .../apache/cassandra/utils/CRC64NVMETest.java | 97 +++++++++++++++ 23 files changed, 597 insertions(+), 54 deletions(-) create mode 100644 src/java/org/apache/cassandra/utils/CRC64NVME.java create mode 100644 test/distributed/org/apache/cassandra/distributed/test/SSTableDigestTest.java create mode 100644 test/unit/org/apache/cassandra/utils/CRC64NVMETest.java diff --git a/src/java/org/apache/cassandra/cache/AutoSavingCache.java b/src/java/org/apache/cassandra/cache/AutoSavingCache.java index 8037bd90b880..efe9b4944277 100644 --- a/src/java/org/apache/cassandra/cache/AutoSavingCache.java +++ b/src/java/org/apache/cassandra/cache/AutoSavingCache.java @@ -118,7 +118,7 @@ public InputStream getInputStream(File dataPath, File crcPath) throws IOExceptio public OutputStream getOutputStream(File dataPath, File crcPath) { - return new ChecksummedSequentialWriter(dataPath, crcPath, null, writerOption); + return new ChecksummedSequentialWriter(dataPath, crcPath, writerOption); } }; diff --git a/src/java/org/apache/cassandra/db/compaction/Verifier.java b/src/java/org/apache/cassandra/db/compaction/Verifier.java index fd6cba56d3c1..011f9010a8fa 100644 --- a/src/java/org/apache/cassandra/db/compaction/Verifier.java +++ b/src/java/org/apache/cassandra/db/compaction/Verifier.java @@ -80,6 +80,8 @@ public class Verifier implements Closeable { + private static final List DIGEST_COMPONENTS = ImmutableList.of(Component.DIGEST, Component.DIGEST_CRC32C, Component.DIGEST_CRC64NVME); + private final @Nullable CompactionRealm realm; private final SSTableReader sstable; @@ -250,12 +252,17 @@ public void verify() { validator = null; - if (sstable.descriptor.fileFor(Component.DIGEST).exists()) + for (Component component : DIGEST_COMPONENTS) { - validator = DataIntegrityMetadata.fileDigestValidator(sstable.descriptor); - validator.validate(); + if (sstable.descriptor.fileFor(component).exists()) + { + validator = DataIntegrityMetadata.fileDigestValidator(sstable.descriptor, component); + validator.validate(); + break; + } } - else + + if (validator == null) { outputHandler.output("Data digest missing, assuming extended verification of disk values"); extended = true; diff --git a/src/java/org/apache/cassandra/db/streaming/ComponentManifest.java b/src/java/org/apache/cassandra/db/streaming/ComponentManifest.java index 06cdf357613c..2aa157c1f905 100644 --- a/src/java/org/apache/cassandra/db/streaming/ComponentManifest.java +++ b/src/java/org/apache/cassandra/db/streaming/ComponentManifest.java @@ -39,7 +39,7 @@ public final class ComponentManifest implements Iterable { private static final List STREAM_COMPONENTS = ImmutableList.of(Component.DATA, Component.PRIMARY_INDEX, Component.PARTITION_INDEX, Component.ROW_INDEX, Component.STATS, Component.COMPRESSION_INFO, Component.FILTER, Component.SUMMARY, - Component.DIGEST, Component.CRC); + Component.DIGEST, Component.DIGEST_CRC32C, Component.DIGEST_CRC64NVME, Component.CRC); private final LinkedHashMap components; diff --git a/src/java/org/apache/cassandra/io/compress/CompressedSequentialWriter.java b/src/java/org/apache/cassandra/io/compress/CompressedSequentialWriter.java index e9f2e91dd280..3a58e2ef0dd9 100644 --- a/src/java/org/apache/cassandra/io/compress/CompressedSequentialWriter.java +++ b/src/java/org/apache/cassandra/io/compress/CompressedSequentialWriter.java @@ -27,7 +27,9 @@ import java.util.Optional; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; +import java.util.zip.Checksum; +import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +42,7 @@ import org.apache.cassandra.io.util.File; import org.apache.cassandra.schema.CompressionParams; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.ChecksumType; import static org.apache.cassandra.utils.Throwables.merge; @@ -70,6 +73,8 @@ public class CompressedSequentialWriter extends SequentialWriter private final int maxCompressedLength; + private final ChecksumType checksumType; + /** * When corruption is found, the file writer is reset to previous data point but we can't reset the CRC checksum. * So we have to recompute digest value. @@ -82,6 +87,7 @@ public class CompressedSequentialWriter extends SequentialWriter * @param file File to write * @param offsetsPath File name to write compression metadata * @param digestFile File to write digest + * @param checksumType The type of checksum in the digest file * @param option Write option (buffer size and type will be set the same as compression params) * @param parameters Compression mparameters * @param sstableMetadataCollector Metadata collector @@ -89,6 +95,7 @@ public class CompressedSequentialWriter extends SequentialWriter public CompressedSequentialWriter(File file, File offsetsPath, File digestFile, + ChecksumType checksumType, SequentialWriterOption option, CompressionParams parameters, MetadataCollector sstableMetadataCollector) @@ -112,7 +119,18 @@ public CompressedSequentialWriter(File file, metadataWriter = CompressionMetadata.Writer.open(parameters, offsetsPath); this.sstableMetadataCollector = sstableMetadataCollector; - crcMetadata = new ChecksumWriter(new DataOutputStream(Channels.newOutputStream(channel))); + this.checksumType = checksumType; + crcMetadata = new ChecksumWriter(new DataOutputStream(Channels.newOutputStream(channel)), checksumType); + } + + public CompressedSequentialWriter(File file, + File offsetsPath, + File digestFile, + SequentialWriterOption option, + CompressionParams parameters, + MetadataCollector sstableMetadataCollector) + { + this(file, offsetsPath, digestFile, ChecksumType.CRC32, option, parameters, sstableMetadataCollector); } @Override @@ -460,7 +478,7 @@ private void maybeWriteChecksum() try (FileChannel fileChannel = StorageProvider.instance.writeTimeReadFileChannelFor(file); InputStream stream = Channels.newInputStream(fileChannel)) { - CRC32 checksum = new CRC32(); + Checksum checksum = checksumType.newInstance(); try (CheckedInputStream checkedInputStream = new CheckedInputStream(stream, checksum)) { byte[] chunk = new byte[64 * 1024]; diff --git a/src/java/org/apache/cassandra/io/compress/EncryptedSequentialWriter.java b/src/java/org/apache/cassandra/io/compress/EncryptedSequentialWriter.java index 68f49b7b25cb..cd62c3cfba50 100644 --- a/src/java/org/apache/cassandra/io/compress/EncryptedSequentialWriter.java +++ b/src/java/org/apache/cassandra/io/compress/EncryptedSequentialWriter.java @@ -35,6 +35,7 @@ import org.apache.cassandra.io.util.SequentialWriter; import org.apache.cassandra.io.util.SequentialWriterOption; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.ChecksumType; import org.apache.cassandra.utils.PageAware; /** @@ -103,7 +104,7 @@ public EncryptedSequentialWriter(File file, this.encrypted = BufferType.preferredForCompression().allocate(CHUNK_SIZE); maxBytesInChunk = buffer.capacity(); - crcMetadata = new ChecksumWriter(new DataOutputStream(Channels.newOutputStream(channel))); + crcMetadata = new ChecksumWriter(new DataOutputStream(Channels.newOutputStream(channel)), ChecksumType.CRC32); } public static int maxBytesInPage(ICompressor encryptor) diff --git a/src/java/org/apache/cassandra/io/sstable/Component.java b/src/java/org/apache/cassandra/io/sstable/Component.java index ee9f34b221b4..8c7a201b3d83 100644 --- a/src/java/org/apache/cassandra/io/sstable/Component.java +++ b/src/java/org/apache/cassandra/io/sstable/Component.java @@ -61,6 +61,10 @@ public enum Type STATS("Statistics.db"), // holds CRC32 checksum of the data file DIGEST("Digest.crc32"), + // holds CRC32C checksum of the data file + DIGEST_CRC32C("Digest.crc32c"), + // holds CRC64-NVMe checksum of the data file + DIGEST_CRC64NMVE("Digest.crc64nvme"), // holds the CRC32 for chunks in an a uncompressed file. CRC("CRC.db"), // holds SSTable Index Summary (sampling of Index component) @@ -104,6 +108,8 @@ public static Type fromRepresentation(String repr) public final static Component COMPRESSION_INFO = new Component(Type.COMPRESSION_INFO); public final static Component STATS = new Component(Type.STATS); public final static Component DIGEST = new Component(Type.DIGEST); + public final static Component DIGEST_CRC32C = new Component(Type.DIGEST_CRC32C); + public final static Component DIGEST_CRC64NVME = new Component(Type.DIGEST_CRC64NMVE); public final static Component CRC = new Component(Type.CRC); public final static Component SUMMARY = new Component(Type.SUMMARY); public final static Component TOC = new Component(Type.TOC); @@ -156,6 +162,8 @@ public static Component parse(String name) case COMPRESSION_INFO: return Component.COMPRESSION_INFO; case STATS: return Component.STATS; case DIGEST: return Component.DIGEST; + case DIGEST_CRC32C: return Component.DIGEST_CRC32C; + case DIGEST_CRC64NMVE: return Component.DIGEST_CRC64NVME; case CRC: return Component.CRC; case SUMMARY: return Component.SUMMARY; case TOC: return Component.TOC; diff --git a/src/java/org/apache/cassandra/io/sstable/format/SortedTableWriter.java b/src/java/org/apache/cassandra/io/sstable/format/SortedTableWriter.java index 3ba16108fd41..906794f60b34 100644 --- a/src/java/org/apache/cassandra/io/sstable/format/SortedTableWriter.java +++ b/src/java/org/apache/cassandra/io/sstable/format/SortedTableWriter.java @@ -42,6 +42,7 @@ import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.db.rows.Rows; import org.apache.cassandra.db.rows.Unfiltered; +import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.guardrails.Guardrails; import org.apache.cassandra.io.FSWriteError; import org.apache.cassandra.io.compress.CompressedSequentialWriter; @@ -66,13 +67,18 @@ import org.apache.cassandra.schema.CompressionParams; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.TableMetadataRef; +import org.apache.cassandra.utils.ChecksumType; import org.apache.cassandra.utils.FBUtilities; -import org.apache.cassandra.utils.IFilter; import org.apache.cassandra.utils.Throwables; public abstract class SortedTableWriter extends SSTableWriter { protected static final Logger logger = LoggerFactory.getLogger(SortedTableWriter.class); + + private static final ChecksumType checksumType = getChecksumType(); + private static final Component digestComponent = getDigestComponent(); + public static final String SSTABLE_CHECKSUM_TYPE_PROPERTY = "cassandra.sstable_checksum_type"; + protected final FileHandle.Builder dbuilder; protected final SequentialWriter dataFile; protected DataPosition dataMark; @@ -104,6 +110,34 @@ protected SortedTableWriter(Descriptor descriptor, isInternalKeyspace = SchemaConstants.isInternalKeyspace(metadata.keyspace); } + private static ChecksumType getChecksumType() + { + String checksumTypeProp = System.getProperty(SSTABLE_CHECKSUM_TYPE_PROPERTY, ChecksumType.CRC32.name()); + try + { + return ChecksumType.valueOf(checksumTypeProp.toUpperCase()); + } + catch (IllegalArgumentException e) + { + throw new ConfigurationException(String.format("Invalid value for system property 'cassandra.sstable_checksum_type': %s", checksumTypeProp), e); + } + } + + private static Component getDigestComponent() + { + switch (checksumType) + { + case CRC32: + return Component.DIGEST; + case CRC32C: + return Component.DIGEST_CRC32C; + case CRC64NVME: + return Component.DIGEST_CRC64NVME; + default: + throw new IllegalStateException("Unexpected checksumType for digest file: " + checksumType); + } + } + protected static SequentialWriter constructDataFileWriter(Descriptor descriptor, TableMetadataRef metadata, MetadataCollector metadataCollector, @@ -116,7 +150,8 @@ protected static SequentialWriter constructDataFileWriter(Descriptor descriptor, return new CompressedSequentialWriter(descriptor.fileFor(Component.DATA), descriptor.fileFor(Component.COMPRESSION_INFO), - descriptor.fileFor(Component.DIGEST), + descriptor.fileFor(digestComponent), + checksumType, writerOption, compressionParams, metadataCollector); @@ -125,7 +160,8 @@ protected static SequentialWriter constructDataFileWriter(Descriptor descriptor, { return new ChecksummedSequentialWriter(descriptor.fileFor(Component.DATA), descriptor.fileFor(Component.CRC), - descriptor.fileFor(Component.DIGEST), + descriptor.fileFor(digestComponent), + checksumType, writerOption); } } diff --git a/src/java/org/apache/cassandra/io/sstable/format/big/BigFormat.java b/src/java/org/apache/cassandra/io/sstable/format/big/BigFormat.java index c2d18cb3cdc9..e3bf6609549e 100644 --- a/src/java/org/apache/cassandra/io/sstable/format/big/BigFormat.java +++ b/src/java/org/apache/cassandra/io/sstable/format/big/BigFormat.java @@ -65,6 +65,8 @@ public class BigFormat implements SSTableFormat Component.COMPRESSION_INFO, Component.STATS, Component.DIGEST, + Component.DIGEST_CRC32C, + Component.DIGEST_CRC64NVME, Component.CRC, Component.SUMMARY, Component.TOC); @@ -76,6 +78,8 @@ public class BigFormat implements SSTableFormat Component.COMPRESSION_INFO, Component.FILTER, Component.DIGEST, + Component.DIGEST_CRC32C, + Component.DIGEST_CRC64NVME, Component.CRC); private final static Set PRIMARY_INDEX_COMPONENTS = ImmutableSet.of(Component.PRIMARY_INDEX); diff --git a/src/java/org/apache/cassandra/io/sstable/format/big/BigTableWriter.java b/src/java/org/apache/cassandra/io/sstable/format/big/BigTableWriter.java index 120cb4663201..05c2c6746fa3 100644 --- a/src/java/org/apache/cassandra/io/sstable/format/big/BigTableWriter.java +++ b/src/java/org/apache/cassandra/io/sstable/format/big/BigTableWriter.java @@ -110,7 +110,9 @@ private static Set components(TableMetadata metadata, Collection PRIMARY_INDEX_COMPONENTS = ImmutableSet.of(Component.PARTITION_INDEX, diff --git a/src/java/org/apache/cassandra/io/util/ChecksumWriter.java b/src/java/org/apache/cassandra/io/util/ChecksumWriter.java index 76cd00ecbbdc..4f9ffab039c7 100644 --- a/src/java/org/apache/cassandra/io/util/ChecksumWriter.java +++ b/src/java/org/apache/cassandra/io/util/ChecksumWriter.java @@ -25,21 +25,23 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.zip.CRC32; +import java.util.zip.Checksum; import javax.annotation.Nonnull; import org.apache.cassandra.io.FSWriteError; import org.apache.cassandra.io.compress.CompressedSequentialWriter; +import org.apache.cassandra.utils.ChecksumType; public class ChecksumWriter { - private final CRC32 incrementalChecksum = new CRC32(); + private final Checksum incrementalChecksum = ChecksumType.CRC32.newInstance(); private final DataOutput incrementalOut; - private final CRC32 fullChecksum = new CRC32(); + private final Checksum fullChecksum; - public ChecksumWriter(DataOutput incrementalOut) + public ChecksumWriter(DataOutput incrementalOut, ChecksumType fullChecksumType) { this.incrementalOut = incrementalOut; + this.fullChecksum = fullChecksumType.newInstance(); } public void writeChunkSize(int length) diff --git a/src/java/org/apache/cassandra/io/util/ChecksummedSequentialWriter.java b/src/java/org/apache/cassandra/io/util/ChecksummedSequentialWriter.java index fa8fad7d5771..0a9614c5b6c2 100644 --- a/src/java/org/apache/cassandra/io/util/ChecksummedSequentialWriter.java +++ b/src/java/org/apache/cassandra/io/util/ChecksummedSequentialWriter.java @@ -20,6 +20,8 @@ import java.nio.ByteBuffer; import java.util.Optional; +import org.apache.cassandra.utils.ChecksumType; + public class ChecksummedSequentialWriter extends SequentialWriter { private static final SequentialWriterOption CRC_WRITER_OPTION = SequentialWriterOption.newBuilder() @@ -30,15 +32,20 @@ public class ChecksummedSequentialWriter extends SequentialWriter private final ChecksumWriter crcMetadata; private final Optional digestFile; - public ChecksummedSequentialWriter(File file, File crcPath, File digestFile, SequentialWriterOption option) + public ChecksummedSequentialWriter(File file, File crcPath, File digestFile, ChecksumType checksumType, SequentialWriterOption option) { super(file, option); crcWriter = new SequentialWriter(crcPath, CRC_WRITER_OPTION); - crcMetadata = new ChecksumWriter(crcWriter); + crcMetadata = new ChecksumWriter(crcWriter, checksumType); crcMetadata.writeChunkSize(buffer.capacity()); this.digestFile = Optional.ofNullable(digestFile); } + public ChecksummedSequentialWriter(File file, File crcPath, SequentialWriterOption option) + { + this(file, crcPath, null, ChecksumType.CRC32, option); + } + @Override protected void flushData() { diff --git a/src/java/org/apache/cassandra/io/util/DataIntegrityMetadata.java b/src/java/org/apache/cassandra/io/util/DataIntegrityMetadata.java index 4efc2cec8736..65c219c9f532 100644 --- a/src/java/org/apache/cassandra/io/util/DataIntegrityMetadata.java +++ b/src/java/org/apache/cassandra/io/util/DataIntegrityMetadata.java @@ -108,9 +108,9 @@ public void close() } } - public static FileDigestValidator fileDigestValidator(Descriptor desc) throws IOException + public static FileDigestValidator fileDigestValidator(Descriptor desc, Component digestComponent) throws IOException { - return new FileDigestValidator(desc); + return new FileDigestValidator(desc, digestComponent); } public static class FileDigestValidator implements Closeable @@ -121,11 +121,11 @@ public static class FileDigestValidator implements Closeable private final Descriptor descriptor; private long storedDigestValue; - public FileDigestValidator(Descriptor descriptor) throws IOException + public FileDigestValidator(Descriptor descriptor, Component digestComponent) throws IOException { this.descriptor = descriptor; - checksum = ChecksumType.CRC32.newInstance(); - digestReader = RandomAccessReader.open(descriptor.fileFor(Component.DIGEST)); + checksum = checksumFor(digestComponent).newInstance(); + digestReader = RandomAccessReader.open(descriptor.fileFor(digestComponent)); dataReader = RandomAccessReader.open(descriptor.fileFor(Component.DATA)); try { @@ -139,6 +139,18 @@ public FileDigestValidator(Descriptor descriptor) throws IOException } } + private static ChecksumType checksumFor(Component digestComponent) + { + if (digestComponent == Component.DIGEST) + return ChecksumType.CRC32; + else if (digestComponent == Component.DIGEST_CRC32C) + return ChecksumType.CRC32C; + else if (digestComponent == Component.DIGEST_CRC64NVME) + return ChecksumType.CRC64NVME; + else + throw new IllegalArgumentException("Unsupported digest component " + digestComponent); + } + // Validate the entire file public void validate() throws IOException { diff --git a/src/java/org/apache/cassandra/utils/CRC64NVME.java b/src/java/org/apache/cassandra/utils/CRC64NVME.java new file mode 100644 index 000000000000..a5fbe8127436 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/CRC64NVME.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.utils; + +import java.nio.ByteOrder; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.zip.Checksum; + +public final class CRC64NVME implements Checksum +{ + private static final long POLY = 0x9A6C9329AC4BC9B5L; + + private static final long[][] TABLE = new long[8][256]; + + private static final VarHandle LONG_LE = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN); + + static + { + for (int i = 0; i < 256; i++) + { + long crc = i; + + for (int j = 0; j < 8; j++) + { + crc = ((crc & 1) != 0) + ? (crc >>> 1) ^ POLY + : (crc >>> 1); + } + + TABLE[0][i] = crc; + } + + for (int t = 1; t < 8; t++) + { + for (int i = 0; i < 256; i++) + { + long crc = TABLE[t - 1][i]; + TABLE[t][i] = TABLE[0][(int) (crc & 0xFF)] ^ (crc >>> 8); + } + } + } + + private long crc; + + @Override + public void update(int b) + { + long c = crc ^ ~0L; + c = (c >>> 8) ^ TABLE[0][(int) ((c ^ b) & 0xFF)]; + crc = c ^ ~0L; + } + + @Override + public void update(byte[] b, int off, int len) + { + long c = crc ^ ~0L; + + int end = off + len; + + while (off + 8 <= end) + { + long x = (long) LONG_LE.get(b, off) ^ c; + + c = TABLE[7][(int) (x & 0xFF)] ^ + TABLE[6][(int) ((x >>> 8) & 0xFF)] ^ + TABLE[5][(int) ((x >>> 16) & 0xFF)] ^ + TABLE[4][(int) ((x >>> 24) & 0xFF)] ^ + TABLE[3][(int) ((x >>> 32) & 0xFF)] ^ + TABLE[2][(int) ((x >>> 40) & 0xFF)] ^ + TABLE[1][(int) ((x >>> 48) & 0xFF)] ^ + TABLE[0][(int) ((x >>> 56) & 0xFF)]; + + off += 8; + } + + while (off < end) + { + c = (c >>> 8) ^ TABLE[0][(int) ((c ^ b[off++]) & 0xFF)]; + } + + crc = c ^ ~0L; + } + + @Override + public long getValue() + { + return crc; + } + + @Override + public void reset() + { + crc = 0; + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/utils/ChecksumType.java b/src/java/org/apache/cassandra/utils/ChecksumType.java index fa920aaca8d2..e70db60b64a7 100644 --- a/src/java/org/apache/cassandra/utils/ChecksumType.java +++ b/src/java/org/apache/cassandra/utils/ChecksumType.java @@ -18,11 +18,14 @@ package org.apache.cassandra.utils; import java.nio.ByteBuffer; +import java.util.Properties; +import java.util.zip.CRC32C; import java.util.zip.Checksum; import java.util.zip.CRC32; import java.util.zip.Adler32; import io.netty.util.concurrent.FastThreadLocal; +import org.apache.cassandra.config.CassandraRelevantProperties; public enum ChecksumType { @@ -38,7 +41,7 @@ public Checksum newInstance() @Override public void update(Checksum checksum, ByteBuffer buf) { - ((Adler32)checksum).update(buf); + checksum.update(buf); } }, @@ -54,7 +57,39 @@ public Checksum newInstance() @Override public void update(Checksum checksum, ByteBuffer buf) { - ((CRC32)checksum).update(buf); + checksum.update(buf); + } + + }, + CRC32C + { + + @Override + public Checksum newInstance() + { + return new CRC32C(); + } + + @Override + public void update(Checksum checksum, ByteBuffer buf) + { + checksum.update(buf); + } + + }, + CRC64NVME + { + + @Override + public Checksum newInstance() + { + return new CRC64NVME(); + } + + @Override + public void update(Checksum checksum, ByteBuffer buf) + { + checksum.update(buf); } }; diff --git a/test/distributed/org/apache/cassandra/distributed/test/SSTableDigestTest.java b/test/distributed/org/apache/cassandra/distributed/test/SSTableDigestTest.java new file mode 100644 index 000000000000..8ebb8cb9bd0e --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/SSTableDigestTest.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.distributed.test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +import org.junit.Test; + +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.shared.WithProperties; +import org.apache.cassandra.io.sstable.Component; +import org.apache.cassandra.io.sstable.format.SSTableReader; +import org.apache.cassandra.utils.ChecksumType; + +import static java.lang.String.format; +import static org.apache.cassandra.io.sstable.format.SortedTableWriter.SSTABLE_CHECKSUM_TYPE_PROPERTY; +import static org.assertj.core.api.Assertions.assertThat; + +public class SSTableDigestTest extends TestBaseImpl +{ + public void testDigestFile(ChecksumType checksumType, Component digestComponent) throws IOException + { + try (WithProperties properties = new WithProperties()) + { + if (checksumType != null) + properties.setProperty(SSTABLE_CHECKSUM_TYPE_PROPERTY, checksumType.toString()); + + try (Cluster cluster = init(Cluster.build(1) + .withDataDirCount(1) + .start())) + { + cluster.disableAutoCompaction(KEYSPACE); + cluster.schemaChange(createTableStmt(KEYSPACE)); + cluster.get(1).executeInternal(format("INSERT INTO %s.%s (pk, ck, v) VALUES (?, ?, ?)", KEYSPACE, "tbl"), 1, 1, 1); + cluster.get(1).flush(KEYSPACE); + + String digestFileName = digestComponent.type.repr; + + cluster.get(1).runOnInstance(() -> { + try + { + SSTableReader ssTable = new ArrayList<>(Keyspace.open(KEYSPACE).getColumnFamilyStore("tbl").getLiveSSTables()).get(0); + + Path digestPath = ssTable.descriptor.pathFor(Component.parse(digestFileName)); + + assertThat(digestPath).exists(); + + long digest = Long.parseLong(Files.readString(digestPath)); + + Path data = ssTable.descriptor.pathFor(Component.DATA); + byte[] bytes = Files.readAllBytes(data); + + ChecksumType effectiveChecksumType = checksumType == null ? ChecksumType.CRC32 : checksumType; + long checksum = effectiveChecksumType.of(bytes, 0, bytes.length); + + assertThat(checksum).isEqualTo(digest); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + }); + } + } + } + + @Test + public void testDigestFileDefault() throws IOException + { + testDigestFile(null, Component.DIGEST); + } + + @Test + public void testDigestFileCRC32() throws IOException + { + testDigestFile(ChecksumType.CRC32, Component.DIGEST); + } + + @Test + public void testDigestFileCRC32C() throws IOException + { + testDigestFile(ChecksumType.CRC32C, Component.DIGEST_CRC32C); + } + + @Test + public void testDigestFileCRC64NVME() throws IOException + { + testDigestFile(ChecksumType.CRC64NVME, Component.DIGEST_CRC64NVME); + } + + private static String createTableStmt(String ks) + { + return format("CREATE TABLE %s.%s (pk int, ck int, v int, PRIMARY KEY (pk, ck)) " + + "WITH compaction = {'class':'SizeTieredCompactionStrategy', 'enabled':'false'}", + ks, "tbl"); + } +} diff --git a/test/microbench/org/apache/cassandra/test/microbench/ChecksumBench.java b/test/microbench/org/apache/cassandra/test/microbench/ChecksumBench.java index 5615f8c41f67..b1d0c91b33f0 100644 --- a/test/microbench/org/apache/cassandra/test/microbench/ChecksumBench.java +++ b/test/microbench/org/apache/cassandra/test/microbench/ChecksumBench.java @@ -21,7 +21,11 @@ import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.common.primitives.Longs; + +import org.apache.cassandra.utils.CRC64NVME; import org.apache.cassandra.utils.ChecksumType; +import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.MD5Digest; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -36,9 +40,11 @@ import org.openjdk.jmh.annotations.Warmup; import org.xerial.snappy.PureJavaCrc32C; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Random; import java.util.concurrent.TimeUnit; +import java.util.zip.CRC32C; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -104,27 +110,48 @@ public byte[] benchPureJavaCrc32c() return Longs.toByteArray(pureJavaCrc32C.getValue()); } - // Below benchmarks are commented because CRC32C is unavailable in Java 8. -// @Benchmark -// @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", -// "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", -// }) -// public byte[] benchCrc32c() -// { -// CRC32C crc32C = new CRC32C(); -// crc32C.update(array); -// return Longs.toByteArray(crc32C.getValue()); -// } -// -// @Benchmark -// @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", -// "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", -// "-XX:+UnlockDiagnosticVMOptions", "-XX:-UseCRC32CIntrinsics", -// }) -// public byte[] benchCrc32cNoIntrinsic() -// { -// CRC32C crc32C = new CRC32C(); -// crc32C.update(array); -// return Longs.toByteArray(crc32C.getValue()); -// } + @Benchmark + @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", + "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", + }) + public byte[] benchCrc32c() + { + CRC32C crc32C = new CRC32C(); + crc32C.update(array); + return Longs.toByteArray(crc32C.getValue()); + } + + @Benchmark + @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", + "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", + "-XX:+UnlockDiagnosticVMOptions", "-XX:-UseCRC32CIntrinsics", + }) + public byte[] benchCrc32cNoIntrinsic() + { + CRC32C crc32C = new CRC32C(); + crc32C.update(array); + return Longs.toByteArray(crc32C.getValue()); + } + + @Benchmark + @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", + "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", + }) + public byte[] benchCrc64nvme() + { + CRC64NVME crc64nvme = new CRC64NVME(); + crc64nvme.update(array, 0, array.length); + return Longs.toByteArray(crc64nvme.getValue()); + } + + @Benchmark + @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", + "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", + }) + public byte[] benchMd5() + { + MessageDigest md5 = FBUtilities.newMessageDigest("MD5"); + md5.update(array, 0, array.length); + return md5.digest(); + } } diff --git a/test/unit/org/apache/cassandra/db/ScrubTest.java b/test/unit/org/apache/cassandra/db/ScrubTest.java index 9b92867df05e..be8c9c2c3ee9 100644 --- a/test/unit/org/apache/cassandra/db/ScrubTest.java +++ b/test/unit/org/apache/cassandra/db/ScrubTest.java @@ -266,7 +266,7 @@ public void testScrubOneBrokenPartitionInTheMiddleOfCompressedFile() throws Exec SSTableReader outputSSTable = cfs.getLiveSSTables().iterator().next(); assertThat(outputSSTable.descriptor.fileFor(Component.DIGEST).exists()).isTrue(); - try (DataIntegrityMetadata.FileDigestValidator validator = DataIntegrityMetadata.fileDigestValidator(outputSSTable.descriptor)) + try (DataIntegrityMetadata.FileDigestValidator validator = DataIntegrityMetadata.fileDigestValidator(outputSSTable.descriptor, Component.DIGEST)) { validator.validate(); } diff --git a/test/unit/org/apache/cassandra/db/VerifyTest.java b/test/unit/org/apache/cassandra/db/VerifyTest.java index 7123727eef08..075ef156be72 100644 --- a/test/unit/org/apache/cassandra/db/VerifyTest.java +++ b/test/unit/org/apache/cassandra/db/VerifyTest.java @@ -77,6 +77,7 @@ import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.ChecksumType; import org.apache.cassandra.utils.OutputHandler; import org.apache.cassandra.utils.UUIDGen; @@ -370,6 +371,57 @@ public void testVerifyIncorrectDigest() throws IOException, WriteTimeoutExceptio catch (RuntimeException err) {} } + private void testVerifyDigest(ChecksumType checksumType, Component digestComponent) throws Exception + { + CompactionManager.instance.disableAutoCompaction(); + Keyspace keyspace = Keyspace.open(KEYSPACE); + ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF); + + fillCF(cfs, 2); + + SSTableReader sstable = cfs.getLiveSSTables().iterator().next(); + + // Delete default digest file + sstable.descriptor.fileFor(Component.DIGEST).delete(); + + File digestFile = sstable.descriptor.fileFor(digestComponent); + File dataFile = sstable.descriptor.fileFor(Component.DATA); + + byte[] bytes = Files.readAllBytes(dataFile.toPath()); + long correctChecksum = checksumType.of(bytes, 0, bytes.length); + writeChecksum(correctChecksum, digestFile); + + try (Verifier verifier = new Verifier(cfs, sstable, false, Verifier.options().invokeDiskFailurePolicy(true).build())) + { + verifier.verify(); + } + catch (CorruptSSTableException err) + { + fail("Unexpected CorruptSSTableException"); + } + + writeChecksum(++correctChecksum, digestFile); + + try (Verifier verifier = new Verifier(cfs, sstable, false, Verifier.options().invokeDiskFailurePolicy(true).build())) + { + verifier.verify(); + fail("Expected a CorruptSSTableException to be thrown"); + } + catch (CorruptSSTableException err) {} + + } + + @Test + public void testVerifyDigestCRC32C() throws Exception + { + testVerifyDigest(ChecksumType.CRC32C, Component.DIGEST_CRC32C); + } + + @Test + public void testVerifyDigestCRC64NVME() throws Exception + { + testVerifyDigest(ChecksumType.CRC64NVME, Component.DIGEST_CRC64NVME); + } @Test public void testVerifyCorruptRowCorrectDigest() throws IOException, WriteTimeoutException diff --git a/test/unit/org/apache/cassandra/io/sstable/ComponentTest.java b/test/unit/org/apache/cassandra/io/sstable/ComponentTest.java index 5900fefc7fc7..0ba22b5ff7ab 100644 --- a/test/unit/org/apache/cassandra/io/sstable/ComponentTest.java +++ b/test/unit/org/apache/cassandra/io/sstable/ComponentTest.java @@ -39,6 +39,8 @@ public void testFromRepresentationExactMatches() assertEquals(Component.Type.COMPRESSION_INFO, Component.Type.fromRepresentation("CompressionInfo.db")); assertEquals(Component.Type.STATS, Component.Type.fromRepresentation("Statistics.db")); assertEquals(Component.Type.DIGEST, Component.Type.fromRepresentation("Digest.crc32")); + assertEquals(Component.Type.DIGEST_CRC32C, Component.Type.fromRepresentation("Digest.crc32c")); + assertEquals(Component.Type.DIGEST_CRC64NMVE, Component.Type.fromRepresentation("Digest.crc64nmve")); assertEquals(Component.Type.CRC, Component.Type.fromRepresentation("CRC.db")); assertEquals(Component.Type.SUMMARY, Component.Type.fromRepresentation("Summary.db")); assertEquals(Component.Type.TOC, Component.Type.fromRepresentation("TOC.txt")); diff --git a/test/unit/org/apache/cassandra/io/util/ChecksummedRandomAccessReaderTest.java b/test/unit/org/apache/cassandra/io/util/ChecksummedRandomAccessReaderTest.java index 8eee977b5622..fc1285432d44 100644 --- a/test/unit/org/apache/cassandra/io/util/ChecksummedRandomAccessReaderTest.java +++ b/test/unit/org/apache/cassandra/io/util/ChecksummedRandomAccessReaderTest.java @@ -48,7 +48,7 @@ public void readFully() throws IOException final byte[] expected = new byte[70 * 1024]; // bit more than crc chunk size, so we can test rebuffering. ThreadLocalRandom.current().nextBytes(expected); - try (SequentialWriter writer = new ChecksummedSequentialWriter(data, crc, null, SequentialWriterOption.DEFAULT)) + try (SequentialWriter writer = new ChecksummedSequentialWriter(data, crc, SequentialWriterOption.DEFAULT)) { writer.write(expected); writer.finish(); @@ -76,7 +76,7 @@ public void seek() throws IOException final byte[] dataBytes = new byte[70 * 1024]; // bit more than crc chunk size ThreadLocalRandom.current().nextBytes(dataBytes); - try (SequentialWriter writer = new ChecksummedSequentialWriter(data, crc, null, SequentialWriterOption.DEFAULT)) + try (SequentialWriter writer = new ChecksummedSequentialWriter(data, crc, SequentialWriterOption.DEFAULT)) { writer.write(dataBytes); writer.finish(); @@ -110,7 +110,7 @@ public void corruptionDetection() throws IOException final byte[] expected = new byte[5 * 1024]; Arrays.fill(expected, (byte) 0); - try (SequentialWriter writer = new ChecksummedSequentialWriter(data, crc, null, SequentialWriterOption.DEFAULT)) + try (SequentialWriter writer = new ChecksummedSequentialWriter(data, crc, SequentialWriterOption.DEFAULT)) { writer.write(expected); writer.finish(); diff --git a/test/unit/org/apache/cassandra/io/util/ChecksummedSequentialWriterTest.java b/test/unit/org/apache/cassandra/io/util/ChecksummedSequentialWriterTest.java index 991d716e43ea..38236e3406bc 100644 --- a/test/unit/org/apache/cassandra/io/util/ChecksummedSequentialWriterTest.java +++ b/test/unit/org/apache/cassandra/io/util/ChecksummedSequentialWriterTest.java @@ -66,7 +66,7 @@ private TestableCSW() throws IOException private TestableCSW(File file, File crcFile) throws IOException { - this(file, crcFile, new ChecksummedSequentialWriter(file, crcFile, null, SequentialWriterOption.newBuilder() + this(file, crcFile, new ChecksummedSequentialWriter(file, crcFile, SequentialWriterOption.newBuilder() .bufferSize(DEFAULT_BUFFER_SIZE) .build())); } diff --git a/test/unit/org/apache/cassandra/utils/CRC64NVMETest.java b/test/unit/org/apache/cassandra/utils/CRC64NVMETest.java new file mode 100644 index 000000000000..1a95c82a1db2 --- /dev/null +++ b/test/unit/org/apache/cassandra/utils/CRC64NVMETest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.utils; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +// We use the same values to test as in AWS S3 SDK tests. +// See https://github.com/aws/aws-sdk-java-v2/blob/0c5e331bd2544e7d43fabd3db046a95b94d0a2dd/core/checksums/src/test/java/software/amazon/awssdk/checksums/internal/Crc64NvmeChecksumTest.java +public class CRC64NVMETest { + + private CRC64NVME crc64NVME; + private static final String TEST_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + @Before + public void setUp() + { + crc64NVME = new CRC64NVME(); + } + + @Test + public void validateCrcChecksumValues() + { + byte[] bytes = TEST_STRING.getBytes(StandardCharsets.UTF_8); + crc64NVME.update(bytes, 0, bytes.length); + assertThat(getAsString(getAsBytes(crc64NVME.getValue()))).isEqualTo("0000000000000000000000008b8f30cfc6f16409"); + } + + @Test + public void validateEncodedBase64ForCrc() + { + crc64NVME.update("abc".getBytes(StandardCharsets.UTF_8)); + String toBase64 = toBase64(crc64NVME.getValue()); + assertThat(toBase64).isEqualTo("BeXKuz/B+us="); + } + + @Test + public void validateEncodedBase64ForCrcSingleByte() + { + for (byte value : "abc".getBytes(StandardCharsets.UTF_8)) + { + crc64NVME.update(value); + } + String toBase64 = toBase64(crc64NVME.getValue()); + assertThat(toBase64).isEqualTo("BeXKuz/B+us="); + } + + @Test + public void validateResetForCrc() { + crc64NVME.update("beta".getBytes(StandardCharsets.UTF_8)); + crc64NVME.reset(); + crc64NVME.update("alpha".getBytes(StandardCharsets.UTF_8)); + String toBase64 = toBase64(crc64NVME.getValue()); + // Checksum of "alpha" + assertThat(toBase64).isEqualTo("Ehnh98TMQlQ="); + } + + private String toBase64(long value) + { + byte[] bytes = getAsBytes(value); + return Base64.getEncoder().encodeToString(bytes); + } + + private byte[] getAsBytes(long value) + { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(value); + return buffer.array(); + } + + private String getAsString(byte[] checksumBytes) { + return String.format("%040x", new BigInteger(1, checksumBytes)); + } +} From 2a3147b88f1a4c1b21d2c586062bf5c48a5a82c6 Mon Sep 17 00:00:00 2001 From: cbornet Date: Fri, 22 May 2026 10:39:27 +0200 Subject: [PATCH 2/2] Use AWS CRT CRC64 if available on the classpath --- build.xml | 2 ++ .../apache/cassandra/utils/ChecksumType.java | 25 ++++++++++++++++--- ...{CRC64NVME.java => PureJavaCRC64NVME.java} | 2 +- .../test/microbench/ChecksumBench.java | 17 ++++++++++--- ...METest.java => PureJavaCRC64NVMETest.java} | 7 +++--- 5 files changed, 43 insertions(+), 10 deletions(-) rename src/java/org/apache/cassandra/utils/{CRC64NVME.java => PureJavaCRC64NVME.java} (98%) rename test/unit/org/apache/cassandra/utils/{CRC64NVMETest.java => PureJavaCRC64NVMETest.java} (96%) diff --git a/build.xml b/build.xml index addc48b69fd0..061854d521b0 100644 --- a/build.xml +++ b/build.xml @@ -752,6 +752,7 @@ + @@ -946,6 +947,7 @@ + diff --git a/src/java/org/apache/cassandra/utils/ChecksumType.java b/src/java/org/apache/cassandra/utils/ChecksumType.java index e70db60b64a7..217a78193bd3 100644 --- a/src/java/org/apache/cassandra/utils/ChecksumType.java +++ b/src/java/org/apache/cassandra/utils/ChecksumType.java @@ -18,14 +18,16 @@ package org.apache.cassandra.utils; import java.nio.ByteBuffer; -import java.util.Properties; import java.util.zip.CRC32C; import java.util.zip.Checksum; import java.util.zip.CRC32; import java.util.zip.Adler32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import io.netty.util.concurrent.FastThreadLocal; -import org.apache.cassandra.config.CassandraRelevantProperties; +import software.amazon.awssdk.crt.checksums.CRC64NVME; public enum ChecksumType { @@ -83,7 +85,9 @@ public void update(Checksum checksum, ByteBuffer buf) @Override public Checksum newInstance() { - return new CRC64NVME(); + if (HAS_AWS_CRT_CRC64NVME) + return new CRC64NVME(); + return new PureJavaCRC64NVME(); } @Override @@ -94,6 +98,21 @@ public void update(Checksum checksum, ByteBuffer buf) }; + private static final Logger logger = LoggerFactory.getLogger(ChecksumType.class); + private static final boolean HAS_AWS_CRT_CRC64NVME; + + static { + boolean available = false; + try { + Class.forName("software.amazon.awssdk.crt.checksums.CRC64NVME"); + available = true; + } catch (ClassNotFoundException e) { + logger.debug("software.amazon.awssdk.crt.checksums.CRC64NVME not found, " + + "falling back to PureJavaCRC64NVME for CRC64NVME checksum"); + } + HAS_AWS_CRT_CRC64NVME = available; + } + public abstract Checksum newInstance(); public abstract void update(Checksum checksum, ByteBuffer buf); diff --git a/src/java/org/apache/cassandra/utils/CRC64NVME.java b/src/java/org/apache/cassandra/utils/PureJavaCRC64NVME.java similarity index 98% rename from src/java/org/apache/cassandra/utils/CRC64NVME.java rename to src/java/org/apache/cassandra/utils/PureJavaCRC64NVME.java index a5fbe8127436..c748740665ec 100644 --- a/src/java/org/apache/cassandra/utils/CRC64NVME.java +++ b/src/java/org/apache/cassandra/utils/PureJavaCRC64NVME.java @@ -23,7 +23,7 @@ import java.lang.invoke.VarHandle; import java.util.zip.Checksum; -public final class CRC64NVME implements Checksum +public final class PureJavaCRC64NVME implements Checksum { private static final long POLY = 0x9A6C9329AC4BC9B5L; diff --git a/test/microbench/org/apache/cassandra/test/microbench/ChecksumBench.java b/test/microbench/org/apache/cassandra/test/microbench/ChecksumBench.java index b1d0c91b33f0..78b0cd3ec43f 100644 --- a/test/microbench/org/apache/cassandra/test/microbench/ChecksumBench.java +++ b/test/microbench/org/apache/cassandra/test/microbench/ChecksumBench.java @@ -22,10 +22,10 @@ import com.google.common.hash.Hashing; import com.google.common.primitives.Longs; -import org.apache.cassandra.utils.CRC64NVME; +import org.apache.cassandra.utils.PureJavaCRC64NVME; +import software.amazon.awssdk.crt.checksums.CRC64NVME; import org.apache.cassandra.utils.ChecksumType; import org.apache.cassandra.utils.FBUtilities; -import org.apache.cassandra.utils.MD5Digest; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -137,13 +137,24 @@ public byte[] benchCrc32cNoIntrinsic() @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", }) - public byte[] benchCrc64nvme() + public byte[] benchAwsCrtCrc64nvme() { CRC64NVME crc64nvme = new CRC64NVME(); crc64nvme.update(array, 0, array.length); return Longs.toByteArray(crc64nvme.getValue()); } + @Benchmark + @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", + "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", + }) + public byte[] benchPureJavaCrc64nvme() + { + PureJavaCRC64NVME crc64nvme = new PureJavaCRC64NVME(); + crc64nvme.update(array, 0, array.length); + return Longs.toByteArray(crc64nvme.getValue()); + } + @Benchmark @Fork(value = 1, jvmArgsAppend = { "-Xmx512M", "-Djmh.executor=CUSTOM", "-Djmh.executor.class=org.apache.cassandra.test.microbench.FastThreadExecutor", diff --git a/test/unit/org/apache/cassandra/utils/CRC64NVMETest.java b/test/unit/org/apache/cassandra/utils/PureJavaCRC64NVMETest.java similarity index 96% rename from test/unit/org/apache/cassandra/utils/CRC64NVMETest.java rename to test/unit/org/apache/cassandra/utils/PureJavaCRC64NVMETest.java index 1a95c82a1db2..c11463fb6e2f 100644 --- a/test/unit/org/apache/cassandra/utils/CRC64NVMETest.java +++ b/test/unit/org/apache/cassandra/utils/PureJavaCRC64NVMETest.java @@ -30,15 +30,16 @@ // We use the same values to test as in AWS S3 SDK tests. // See https://github.com/aws/aws-sdk-java-v2/blob/0c5e331bd2544e7d43fabd3db046a95b94d0a2dd/core/checksums/src/test/java/software/amazon/awssdk/checksums/internal/Crc64NvmeChecksumTest.java -public class CRC64NVMETest { +public class PureJavaCRC64NVMETest +{ - private CRC64NVME crc64NVME; + private PureJavaCRC64NVME crc64NVME; private static final String TEST_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @Before public void setUp() { - crc64NVME = new CRC64NVME(); + crc64NVME = new PureJavaCRC64NVME(); } @Test