+ {
+ static final OffsetRangesFactory INSTANCE = new OffsetRangesFactory();
+
+ @Override
+ public ActiveOffsetRanges create()
+ {
+ return new ActiveOffsetRanges();
+ }
+
+ @Override
+ public StaticOffsetRanges load(Descriptor descriptor)
+ {
+ File file = descriptor.fileFor(Component.KEYSTATS);
+ try (FileInputStreamPlus in = new FileInputStreamPlus(file))
+ {
+ return StaticOffsetRanges.read(in);
+ }
+ catch (IOException e)
+ {
+ throw new JournalReadError(descriptor, file, e);
+ }
+ }
+ }
+
+ /*
+ * Test helpers
+ */
+
+ @VisibleForTesting
+ public void closeCurrentSegmentForTestingIfNonEmpty()
+ {
+ journal.closeCurrentSegmentForTestingIfNonEmpty();
+ }
+
+ @VisibleForTesting
+ void clearNeedsReplayForTesting()
+ {
+ journal.clearNeedsReplayForTesting();
+ }
+
+ @VisibleForTesting
+ public int countStaticSegmentsForTesting()
+ {
+ return journal.countStaticSegmentsForTesting();
+ }
}
diff --git a/src/java/org/apache/cassandra/replication/MutationTrackingService.java b/src/java/org/apache/cassandra/replication/MutationTrackingService.java
index 535a4100f2c5..4009dbd39a88 100644
--- a/src/java/org/apache/cassandra/replication/MutationTrackingService.java
+++ b/src/java/org/apache/cassandra/replication/MutationTrackingService.java
@@ -87,7 +87,7 @@ public class MutationTrackingService
/**
* Split ranges into this many shards.
- *
+ *
* TODO (expected): ability to rebalance / change this constant
*/
private static final int SHARD_MULTIPLIER = 8;
@@ -547,7 +547,6 @@ private static boolean shardUpdateNeeded(Map current, @N
return false;
}
-
private static ConcurrentHashMap applyUpdatedMetadata(Map keyspaceShardsMap, @Nullable ClusterMetadata prev, ClusterMetadata next, LongSupplier logIdProvider, BiConsumer onNewLog)
{
Preconditions.checkNotNull(next);
@@ -619,6 +618,23 @@ private void onNewLog(Shard shard, CoordinatorLog log)
}
}
+ private void truncateMutationJournal()
+ {
+ Log2OffsetsMap.Mutable reconciledOffsets = new Log2OffsetsMap.Mutable();
+ collectDurablyReconciledOffsets(reconciledOffsets);
+ MutationJournal.instance.dropReconciledSegments(reconciledOffsets);
+ }
+
+ /**
+ * Collect every log's durably reconciled offsets. Every mutation covered
+ * by these offsets can be compacted away by the journal, assuming that all
+ * relevant memtables had been flushed to disk.
+ */
+ private void collectDurablyReconciledOffsets(Log2OffsetsMap.Mutable into)
+ {
+ forEachKeyspace(keyspace -> keyspace.collectDurablyReconciledOffsets(into));
+ }
+
public static class KeyspaceShards
{
private enum UpdateDecision
@@ -847,6 +863,11 @@ void recordFullyReconciledOffsets(ReconciledKeyspaceOffsets keyspaceOffsets)
});
}
+ void collectDurablyReconciledOffsets(Log2OffsetsMap.Mutable into)
+ {
+ forEachShard(shard -> shard.collectDurablyReconciledOffsets(into));
+ }
+
void forEachShard(Consumer consumer)
{
for (Shard shard : shards.values())
@@ -979,8 +1000,15 @@ void start()
@Override
public void run()
+ {
+ run(true);
+ }
+
+ private void run(boolean dropSegments)
{
MutationTrackingService.instance.forEachKeyspace(this::run);
+ if (dropSegments)
+ MutationTrackingService.instance.truncateMutationJournal();
}
private void run(KeyspaceShards shards)
@@ -1000,6 +1028,12 @@ public void persistLogStateForTesting()
offsetsPersister.run();
}
+ @VisibleForTesting
+ public void persistLogStateForTesting(boolean dropSegments)
+ {
+ offsetsPersister.run(dropSegments);
+ }
+
@VisibleForTesting
public void broadcastOffsetsForTesting()
{
diff --git a/src/java/org/apache/cassandra/replication/Offsets.java b/src/java/org/apache/cassandra/replication/Offsets.java
index dfb4f3f404e3..8724c02d4ddd 100644
--- a/src/java/org/apache/cassandra/replication/Offsets.java
+++ b/src/java/org/apache/cassandra/replication/Offsets.java
@@ -186,12 +186,29 @@ public boolean contains(int offset)
{
if (size == 0)
return false;
-
int pos = Arrays.binarySearch(bounds, 0, size, offset);
if (pos >= 0) return true; // matches one of the bounds
+ return (-pos - 1) % 2 != 0;
+ }
+
+ /**
+ * @return whether the entire [minOffset, maxOffset] range is contained in these Offsets
+ */
+ public boolean containsRange(int minOffset, int maxOffset)
+ {
+ if (size == 0)
+ return false;
+
+ // find the range the min offset falls under
+ int pos = Arrays.binarySearch(bounds, 0, size, minOffset);
+ if (pos < 0 && (-pos - 1) % 2 == 0) // min offset is outside any existing range
+ return false;
+
+ int end = pos >= 0
+ ? (pos % 2 == 0 ? pos + 1 : pos)
+ : -pos - 1;
- pos = -pos - 1;
- return (pos - 1) % 2 == 0; // offset falls within bounds of an existing range if the bound to the left is an open one
+ return maxOffset <= bounds[end];
}
public void digest(Digest digest)
diff --git a/src/java/org/apache/cassandra/replication/Shard.java b/src/java/org/apache/cassandra/replication/Shard.java
index 0717c228178e..7274127fcb74 100644
--- a/src/java/org/apache/cassandra/replication/Shard.java
+++ b/src/java/org/apache/cassandra/replication/Shard.java
@@ -276,6 +276,11 @@ BroadcastLogOffsets collectReplicatedOffsets(boolean durable)
return new BroadcastLogOffsets(keyspace, range, offsets, durable);
}
+ void collectDurablyReconciledOffsets(Log2OffsetsMap.Mutable into)
+ {
+ logs.values().forEach(log -> log.collectDurablyReconciledOffsets(into));
+ }
+
private CoordinatorLog getOrCreate(Mutation mutation)
{
return getOrCreate(mutation.id());
diff --git a/src/java/org/apache/cassandra/service/accord/AccordJournal.java b/src/java/org/apache/cassandra/service/accord/AccordJournal.java
index aa568104a2fe..8669586642f4 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordJournal.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordJournal.java
@@ -78,7 +78,6 @@
import org.apache.cassandra.journal.RecordPointer;
import org.apache.cassandra.journal.SegmentCompactor;
import org.apache.cassandra.journal.StaticSegment;
-import org.apache.cassandra.journal.ValueSerializer;
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.FlyweightImage;
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.IdentityAccumulator;
import org.apache.cassandra.service.accord.JournalKey.JournalKeySupport;
@@ -134,23 +133,10 @@ public AccordJournal(Params params)
public AccordJournal(Params params, File directory, ColumnFamilyStore cfs)
{
Version userVersion = Version.fromVersion(params.userVersion());
- this.journal = new Journal<>("AccordJournal", directory, params, JournalKey.SUPPORT,
- // In Accord, we are using streaming serialization, i.e. Reader/Writer interfaces instead of materializing objects
- new ValueSerializer<>()
- {
- @Override
- public void serialize(JournalKey key, Object value, DataOutputPlus out, int userVersion)
- {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object deserialize(JournalKey key, DataInputPlus in, int userVersion)
- {
- throw new UnsupportedOperationException();
- }
- },
- compactor(cfs, userVersion));
+ this.journal =
+ Journal.builder("AccordJournal", directory, params, JournalKey.SUPPORT)
+ .segmentCompactor(compactor(cfs, userVersion))
+ .build();
this.journalTable = new AccordJournalTable<>(journal, JournalKey.SUPPORT, cfs, userVersion);
this.params = params;
}
diff --git a/src/java/org/apache/cassandra/service/accord/JournalKey.java b/src/java/org/apache/cassandra/service/accord/JournalKey.java
index 9bd42ebaf155..4bb3bf19e288 100644
--- a/src/java/org/apache/cassandra/service/accord/JournalKey.java
+++ b/src/java/org/apache/cassandra/service/accord/JournalKey.java
@@ -21,7 +21,7 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
-import java.util.zip.Checksum;
+import java.util.zip.CRC32;
import accord.local.Node.Id;
import accord.primitives.Timestamp;
@@ -177,7 +177,7 @@ private TxnId deserializeTxnId(ByteBuffer buffer, int position)
}
@Override
- public void updateChecksum(Checksum crc, JournalKey key, int userVersion)
+ public void updateChecksum(CRC32 crc, JournalKey key, int userVersion)
{
byte[] out = AccordJournal.keyCRCBytes.get();
serialize(key, out);
diff --git a/src/java/org/apache/cassandra/service/reads/tracked/ReadReconciliations.java b/src/java/org/apache/cassandra/service/reads/tracked/ReadReconciliations.java
index 1c57781c00cd..13e1e69d1074 100644
--- a/src/java/org/apache/cassandra/service/reads/tracked/ReadReconciliations.java
+++ b/src/java/org/apache/cassandra/service/reads/tracked/ReadReconciliations.java
@@ -263,19 +263,19 @@ boolean acceptMutation(ShortMutationId ignoredMutationId)
/**
* Remote summaries minus data node summary offsets
- *
+ *
* This calculation combines BOTH reconciled and unreconciled mutations reported by other nodes, and
* then subtracts mutations reported locally for correctness
- *
+ *
* If we subtracted reconciled ids from the unreconciled ids, we could violate read monotonicity in this scenario:
* 1. Read starts locally and doesn't see mutation M.
* 2. During reconciliation, mutation M arrives and is marked reconciled, other replicas report mutation M as reconciled
* 3. If we filtered out reconciled mutations, this read wouldn't augment with M
* 4. A concurrent read could see M in its initial data
* 5. This read returns without M
- *
+ *
* Instead, we include all mutations and rely on token range filtering during actual mutation
- * retrieval (in PartialTrackedRead.augment) to ensure we only augment with mutations
+ * retrieval (in PartialTrackedRead.augment()) to ensure we only augment with mutations
* relevant to this read's range/key
*/
private Log2OffsetsMap.Mutable augmentingOffsets()
diff --git a/src/java/org/apache/cassandra/service/reads/tracked/TrackedLocalReads.java b/src/java/org/apache/cassandra/service/reads/tracked/TrackedLocalReads.java
index 066ebc22e068..fa6c3193b377 100644
--- a/src/java/org/apache/cassandra/service/reads/tracked/TrackedLocalReads.java
+++ b/src/java/org/apache/cassandra/service/reads/tracked/TrackedLocalReads.java
@@ -25,7 +25,6 @@
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.db.*;
-import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.service.reads.ReadCoordinator;
@@ -36,16 +35,14 @@
import org.apache.cassandra.replication.ShortMutationId;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.Future;
import org.jctools.maps.NonBlockingHashMap;
-import org.apache.cassandra.transport.Dispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.function.Consumer;
-
/**
* Since the read reconciliations don't use 2 way callbacks, maps of active reads and reconciliations
* are maintained and expired here.
@@ -69,8 +66,7 @@ public AsyncPromise beginRead(
ReadCommand command,
ConsistencyLevel consistencyLevel,
int[] summaryNodes,
- Dispatcher.RequestTime requestTime,
- Consumer partialReadConsumer)
+ Dispatcher.RequestTime requestTime)
{
Keyspace keyspace = Keyspace.open(command.metadata().keyspace);
ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(command.metadata().id);
@@ -100,7 +96,7 @@ public AsyncPromise beginRead(
}
// TODO: confirm all summaryNodes are present in the replica plan
AsyncPromise promise = new AsyncPromise<>();
- beginReadInternal(readId, command, replicaPlan, summaryNodes, requestTime, partialReadConsumer, promise);
+ beginReadInternal(readId, command, replicaPlan, summaryNodes, requestTime, promise);
return promise;
}
@@ -111,7 +107,6 @@ private void beginReadInternal(
ReplicaPlan.AbstractForRead, ?> replicaPlan,
int[] summaryNodes,
Dispatcher.RequestTime requestTime,
- Consumer partialReadConsumer,
AsyncPromise promise)
{
PartialTrackedRead read = null;
@@ -137,7 +132,7 @@ private void beginReadInternal(
}
Coordinator coordinator =
- new Coordinator(readId, promise, command.columnFilter(), read, replicaPlan.consistencyLevel(), requestTime);
+ new Coordinator(readId, promise, read, replicaPlan.consistencyLevel(), requestTime);
coordinators.put(readId, coordinator);
// TODO (expected): reconsider the approach to tracked mutation metrics
@@ -187,7 +182,6 @@ private static class Coordinator
{
private final TrackedRead.Id readId;
private final AsyncPromise promise;
- private final ColumnFilter selection;
private final PartialTrackedRead read;
private final ConsistencyLevel consistencyLevel;
private final Dispatcher.RequestTime requestTime;
@@ -195,14 +189,12 @@ private static class Coordinator
Coordinator(
TrackedRead.Id readId,
AsyncPromise promise,
- ColumnFilter selection,
PartialTrackedRead read,
ConsistencyLevel consistencyLevel,
Dispatcher.RequestTime requestTime)
{
this.readId = readId;
this.promise = promise;
- this.selection = selection;
this.read = Preconditions.checkNotNull(read);
this.consistencyLevel = consistencyLevel;
this.requestTime = requestTime;
@@ -246,7 +238,6 @@ private void complete()
if (followUp != null)
{
- ReadCommand command = read.command();
followUp.addCallback((newResponse, error) -> {
if (error != null)
{
diff --git a/src/java/org/apache/cassandra/service/reads/tracked/TrackedRead.java b/src/java/org/apache/cassandra/service/reads/tracked/TrackedRead.java
index 267670a63ebb..975c2742fc1d 100644
--- a/src/java/org/apache/cassandra/service/reads/tracked/TrackedRead.java
+++ b/src/java/org/apache/cassandra/service/reads/tracked/TrackedRead.java
@@ -36,7 +36,6 @@
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.locator.*;
import org.apache.cassandra.net.*;
-import org.apache.cassandra.replication.MutationSummary;
import org.apache.cassandra.replication.MutationTrackingService;
import org.apache.cassandra.service.reads.ReadCoordinator;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
@@ -280,7 +279,7 @@ private void start(Dispatcher.RequestTime requestTime, Consumer {
AsyncPromise promise =
- MutationTrackingService.instance.localReads().beginRead(readId, ClusterMetadata.current(), command, consistencyLevel, summaryNodes, requestTime, partialReadConsumer);
+ MutationTrackingService.instance.localReads().beginRead(readId, ClusterMetadata.current(), command, consistencyLevel, summaryNodes, requestTime);
promise.addCallback((response, error) -> {
if (error != null)
{
@@ -451,7 +450,7 @@ public void executeLocally(Message extends Request> message, ClusterMetadata m
AsyncPromise promise =
MutationTrackingService.instance
.localReads()
- .beginRead(readId, metadata, command, consistencyLevel, summaryNodes, requestTime, null);
+ .beginRead(readId, metadata, command, consistencyLevel, summaryNodes, requestTime);
promise.addCallback((response, error) -> {
if (error != null)
{
@@ -516,11 +515,6 @@ public void executeLocally(Message extends Request> message, ClusterMetadata m
ReadReconciliations.instance.handleSummaryRequest((SummaryRequest) message.payload);
}
- public TrackedSummaryResponse makeResponse(MutationSummary summary)
- {
- return new TrackedSummaryResponse(readId, summary, dataNode, summaryNodes);
- }
-
public static final IVersionedSerializer serializer = new IVersionedSerializer<>()
{
@Override
diff --git a/src/java/org/apache/cassandra/utils/Crc.java b/src/java/org/apache/cassandra/utils/Crc.java
index f1a31584f364..63b0055f2bed 100644
--- a/src/java/org/apache/cassandra/utils/Crc.java
+++ b/src/java/org/apache/cassandra/utils/Crc.java
@@ -83,6 +83,26 @@ public static void updateCrc32(CRC32 crc, ByteBuffer buffer, int start, int end)
buffer.position(savePosition);
}
+ public static void updateWithInt(CRC32 crc, int v)
+ {
+ crc.update((v >>> 24) & 0xFF);
+ crc.update((v >>> 16) & 0xFF);
+ crc.update((v >>> 8) & 0xFF);
+ crc.update((v >>> 0) & 0xFF);
+ }
+
+ public static void updateWithLong(CRC32 crc, long v)
+ {
+ updateWithInt(crc, (int) (v >>> 32));
+ updateWithInt(crc, (int) (v & 0xFFFFFFFFL));
+ }
+
+ public static void validate(CRC32 crc, int expectedCRC) throws InvalidCrc
+ {
+ if (expectedCRC != (int) crc.getValue())
+ throw new InvalidCrc(expectedCRC, (int) crc.getValue());
+ }
+
private static final int CRC24_INIT = 0x875060;
/**
* Polynomial chosen from https://users.ece.cmu.edu/~koopman/crc/index.html, by Philip Koopman
diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java b/src/java/org/apache/cassandra/utils/FBUtilities.java
index a4493f30a201..3a3a96285d9d 100644
--- a/src/java/org/apache/cassandra/utils/FBUtilities.java
+++ b/src/java/org/apache/cassandra/utils/FBUtilities.java
@@ -1184,6 +1184,7 @@ public static void updateChecksumShort(Checksum checksum, short v)
checksum.update((v >>> 0) & 0xFF);
}
+ // TODO: migrate users to Crc class
public static void updateChecksumInt(Checksum checksum, int v)
{
checksum.update((v >>> 24) & 0xFF);
@@ -1192,6 +1193,7 @@ public static void updateChecksumInt(Checksum checksum, int v)
checksum.update((v >>> 0) & 0xFF);
}
+ // TODO: migrate users to Crc class
public static void updateChecksumLong(Checksum checksum, long v)
{
updateChecksumInt(checksum, (int) (v >>> 32));
diff --git a/test/simulator/test/org/apache/cassandra/simulator/test/AccordJournalSimulationTest.java b/test/simulator/test/org/apache/cassandra/simulator/test/AccordJournalSimulationTest.java
index 36040e115c1e..3ff5f8e8b037 100644
--- a/test/simulator/test/org/apache/cassandra/simulator/test/AccordJournalSimulationTest.java
+++ b/test/simulator/test/org/apache/cassandra/simulator/test/AccordJournalSimulationTest.java
@@ -21,7 +21,7 @@
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
-import java.util.zip.Checksum;
+import java.util.zip.CRC32;
import com.google.common.collect.ImmutableMap;
import com.google.common.jimfs.Jimfs;
@@ -47,7 +47,6 @@
import org.apache.cassandra.journal.Journal;
import org.apache.cassandra.journal.KeySupport;
import org.apache.cassandra.journal.RecordPointer;
-import org.apache.cassandra.journal.SegmentCompactor;
import org.apache.cassandra.journal.ValueSerializer;
import org.apache.cassandra.utils.Isolated;
import org.apache.cassandra.utils.concurrent.CountDownLatch;
@@ -75,14 +74,12 @@ public void simpleRWTest()
AccordSpec.JournalSpec spec = new AccordSpec.JournalSpec();
spec.flushPeriod = new DurationSpec.IntSecondsBound(1);
- State.journal = new Journal<>("AccordJournal",
- new File("/journal"),
- spec,
- new IdentityKeySerializer(),
- new IdentityValueSerializer(),
- SegmentCompactor.noop());
+ State.journal =
+ Journal.builder("AccordJournal", new File("/journal"), spec, new IdentityKeySerializer())
+ .valueSerializer(new IdentityValueSerializer())
+ .build();
}),
- () -> check());
+ AccordJournalSimulationTest::check);
}
public static void check()
@@ -237,7 +234,7 @@ public String deserialize(ByteBuffer buffer, int position, int userVersion)
}
@Override
- public void updateChecksum(Checksum crc, String key, int userVersion)
+ public void updateChecksum(CRC32 crc, String key, int userVersion)
{
crc.update(key.getBytes());
}
diff --git a/test/unit/org/apache/cassandra/journal/JournalTest.java b/test/unit/org/apache/cassandra/journal/JournalTest.java
index 90b394518cb1..7cdb7edabbfb 100644
--- a/test/unit/org/apache/cassandra/journal/JournalTest.java
+++ b/test/unit/org/apache/cassandra/journal/JournalTest.java
@@ -52,7 +52,9 @@ public void testSimpleReadWrite() throws IOException
directory.deleteRecursiveOnExit();
Journal journal =
- new Journal<>("TestJournal", directory, TestParams.ACCORD, TimeUUIDKeySupport.INSTANCE, LongSerializer.INSTANCE, SegmentCompactor.noop());
+ Journal.builder("TestJournal", directory, TestParams.ACCORD, TimeUUIDKeySupport.INSTANCE)
+ .valueSerializer(LongSerializer.INSTANCE)
+ .build();
journal.start();
@@ -73,7 +75,10 @@ public void testSimpleReadWrite() throws IOException
journal.shutdown();
- journal = new Journal<>("TestJournal", directory, TestParams.ACCORD, TimeUUIDKeySupport.INSTANCE, LongSerializer.INSTANCE, SegmentCompactor.noop());
+ journal =
+ Journal.builder("TestJournal", directory, TestParams.ACCORD, TimeUUIDKeySupport.INSTANCE)
+ .valueSerializer(LongSerializer.INSTANCE)
+ .build();
journal.start();
assertEquals(1L, (long) journal.readLast(id1));
@@ -91,7 +96,9 @@ public void testReadAll() throws IOException
directory.deleteRecursiveOnExit();
Journal journal =
- new Journal<>("TestJournalReadAll", directory, TestParams.ACCORD, TimeUUIDKeySupport.INSTANCE, LongSerializer.INSTANCE, SegmentCompactor.noop());
+ Journal.builder("TestJournalReadAll", directory, TestParams.ACCORD, TimeUUIDKeySupport.INSTANCE)
+ .valueSerializer(LongSerializer.INSTANCE)
+ .build();
journal.start();
@@ -127,11 +134,6 @@ static class LongSerializer implements ValueSerializer
{
static final LongSerializer INSTANCE = new LongSerializer();
- public int serializedSize(TimeUUID key, Long value, int userVersion)
- {
- return Long.BYTES;
- }
-
public void serialize(TimeUUID key, Long value, DataOutputPlus out, int userVersion) throws IOException
{
out.writeLong(value);
diff --git a/test/unit/org/apache/cassandra/journal/SegmentTest.java b/test/unit/org/apache/cassandra/journal/SegmentTest.java
index 55c54870d8f0..05a0f43f54ff 100644
--- a/test/unit/org/apache/cassandra/journal/SegmentTest.java
+++ b/test/unit/org/apache/cassandra/journal/SegmentTest.java
@@ -54,7 +54,8 @@ public void testWriteReadActiveSegment() throws IOException
Descriptor descriptor = Descriptor.create(directory, System.currentTimeMillis(), 1);
- ActiveSegment segment = ActiveSegment.create(descriptor, params(), TimeUUIDKeySupport.INSTANCE);
+ ActiveSegment segment =
+ ActiveSegment.create(descriptor, params(), TimeUUIDKeySupport.INSTANCE, KeyStats.Factory.noop());
segment.allocate(record1.remaining()).write(id1, record1);
segment.allocate(record2.remaining()).write(id2, record2);
@@ -101,7 +102,8 @@ public void testReadClosedSegmentByID() throws IOException
Descriptor descriptor = Descriptor.create(directory, System.currentTimeMillis(), 1);
- ActiveSegment activeSegment = ActiveSegment.create(descriptor, params(), TimeUUIDKeySupport.INSTANCE);
+ ActiveSegment activeSegment =
+ ActiveSegment.create(descriptor, params(), TimeUUIDKeySupport.INSTANCE, KeyStats.Factory.noop());
activeSegment.allocate(record1.remaining()).write(id1, record1);
activeSegment.allocate(record2.remaining()).write(id2, record2);
@@ -110,7 +112,8 @@ public void testReadClosedSegmentByID() throws IOException
activeSegment.close(null);
- StaticSegment staticSegment = StaticSegment.open(descriptor, TimeUUIDKeySupport.INSTANCE);
+ StaticSegment staticSegment =
+ StaticSegment.open(descriptor, TimeUUIDKeySupport.INSTANCE, KeyStats.Factory.noop());
// read all 4 entries by id and compare with originals
EntrySerializer.EntryHolder holder = new EntrySerializer.EntryHolder<>();
@@ -150,7 +153,8 @@ public void testReadClosedSegmentSequentially() throws IOException
Descriptor descriptor = Descriptor.create(directory, System.currentTimeMillis(), 1);
- ActiveSegment activeSegment = ActiveSegment.create(descriptor, params(), TimeUUIDKeySupport.INSTANCE);
+ ActiveSegment activeSegment =
+ ActiveSegment.create(descriptor, params(), TimeUUIDKeySupport.INSTANCE, KeyStats.Factory.noop());
activeSegment.allocate(record1.remaining()).write(id1, record1);
activeSegment.allocate(record2.remaining()).write(id2, record2);
diff --git a/test/unit/org/apache/cassandra/journal/SegmentsTest.java b/test/unit/org/apache/cassandra/journal/SegmentsTest.java
index ecacb5e197d1..2e029f5edda0 100644
--- a/test/unit/org/apache/cassandra/journal/SegmentsTest.java
+++ b/test/unit/org/apache/cassandra/journal/SegmentsTest.java
@@ -35,7 +35,7 @@ public class SegmentsTest
@Test
public void testSelect()
{
- withRandom(0l, rng -> {
+ withRandom(0L, rng -> {
// Create mock segments with different timestamps
java.io.File file = File.createTempFile("segments", "test");
List> segmentList = new ArrayList<>();
@@ -108,6 +108,13 @@ public boolean isActive()
}
@Override Index index() { throw new UnsupportedOperationException(); }
+
+ @Override
+ public KeyStats keyStats()
+ {
+ return KeyStats.noop();
+ }
+
@Override boolean isFlushed(long position) { throw new UnsupportedOperationException(); }
@Override public void persistMetadata() { throw new UnsupportedOperationException(); }
@Override boolean read(int offset, int size, EntrySerializer.EntryHolder into) { throw new UnsupportedOperationException(); }
diff --git a/test/unit/org/apache/cassandra/journal/TimeUUIDKeySupport.java b/test/unit/org/apache/cassandra/journal/TimeUUIDKeySupport.java
index 5694a29e7ab4..c75197511ecf 100644
--- a/test/unit/org/apache/cassandra/journal/TimeUUIDKeySupport.java
+++ b/test/unit/org/apache/cassandra/journal/TimeUUIDKeySupport.java
@@ -19,14 +19,13 @@
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.util.zip.Checksum;
+import java.util.zip.CRC32;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.utils.Crc;
import org.apache.cassandra.utils.TimeUUID;
-import static org.apache.cassandra.utils.FBUtilities.updateChecksumLong;
-
class TimeUUIDKeySupport implements KeySupport
{
static final TimeUUIDKeySupport INSTANCE = new TimeUUIDKeySupport();
@@ -76,10 +75,10 @@ public TimeUUID deserialize(ByteBuffer buffer, int userVersion)
}
@Override
- public void updateChecksum(Checksum crc, TimeUUID key, int userVersion)
+ public void updateChecksum(CRC32 crc, TimeUUID key, int userVersion)
{
- updateChecksumLong(crc, key.uuidTimestamp());
- updateChecksumLong(crc, key.lsb());
+ Crc.updateWithLong(crc, key.uuidTimestamp());
+ Crc.updateWithLong(crc, key.lsb());
}
@Override
diff --git a/test/unit/org/apache/cassandra/replication/MutationJournalTest.java b/test/unit/org/apache/cassandra/replication/MutationJournalTest.java
index ba741799aa1c..72017bc8a1e0 100644
--- a/test/unit/org/apache/cassandra/replication/MutationJournalTest.java
+++ b/test/unit/org/apache/cassandra/replication/MutationJournalTest.java
@@ -33,13 +33,19 @@
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.io.util.File;
+import org.apache.cassandra.journal.Descriptor;
import org.apache.cassandra.journal.TestParams;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.replication.MutationJournal.ActiveOffsetRanges;
+import org.apache.cassandra.replication.MutationJournal.StaticOffsetRanges;
+import org.apache.cassandra.replication.MutationJournal.OffsetRangesFactory;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* Tests to sanity-check the integration points with Journal
@@ -51,6 +57,7 @@ public class MutationJournalTest
private static final String TABLE = "mjtt";
private static MutationJournal journal;
+ private static File directory;
@BeforeClass
public static void setUp() throws IOException
@@ -63,7 +70,7 @@ public static void setUp() throws IOException
.addRegularColumn("value", UTF8Type.instance)
.build());
- File directory = new File(Files.createTempDirectory("mutation-journal-test-simple"));
+ directory = new File(Files.createTempDirectory("mutation-journal-test-simple"));
directory.deleteRecursiveOnExit();
journal = new MutationJournal(directory, TestParams.MUTATION_JOURNAL);
@@ -79,13 +86,8 @@ public static void tearDown()
@Test
public void testWriteOneReadOne()
{
- Mutation expected =
- new RowUpdateBuilder(Schema.instance.getTableMetadata(KEYSPACE, TABLE), 0, "key")
- .clustering("ck")
- .add("value", "value")
- .build();
-
- ShortMutationId id = new ShortMutationId(100L, 0);
+ ShortMutationId id = id(100L, 0);
+ Mutation expected = mutation("key", "ck", "value");
journal.write(id, expected);
// regular read
@@ -103,22 +105,14 @@ public void testWriteOneReadOne()
@Test
public void testWriteManyReadMany()
{
- Mutation expected1 =
- new RowUpdateBuilder(Schema.instance.getTableMetadata(KEYSPACE, TABLE), 0, "key1")
- .clustering("ck1")
- .add("value", "value1")
- .build();
- Mutation expected2 =
- new RowUpdateBuilder(Schema.instance.getTableMetadata(KEYSPACE, TABLE), 0, "key2")
- .clustering("ck2")
- .add("value", "value2")
- .build();
- List expected = List.of(expected1, expected2);
-
- ShortMutationId id1 = new ShortMutationId(100L, 1);
- ShortMutationId id2 = new ShortMutationId(100L, 2);
+ ShortMutationId id1 = id(100L, 1);
+ ShortMutationId id2 = id(100L, 2);
List ids = List.of(id1, id2);
+ Mutation expected1 = mutation("key1", "ck1", "value1");
+ Mutation expected2 = mutation("key2", "ck2", "value2");
+ List expected = List.of(expected1, expected2);
+
journal.write(id1, expected1);
journal.write(id2, expected2);
@@ -127,6 +121,149 @@ public void testWriteManyReadMany()
assertMutationsEqual(expected, actual);
}
+ @Test
+ public void testActiveOffsetRanges()
+ {
+ {
+ ActiveOffsetRanges ranges = new ActiveOffsetRanges();
+ assertFalse(ranges.mayContain(id(0, 0)));
+ }
+
+ {
+ ActiveOffsetRanges ranges = new ActiveOffsetRanges();
+ ranges.update(id(0, 1));
+ ranges.update(id(0, 9));
+ assertFalse(ranges.mayContain(id(0, 0)));
+ assertFalse(ranges.mayContain(id(0, 10)));
+ for (int i = 1; i < 10; i++)
+ assertTrue(ranges.mayContain(id(0, i)));
+ }
+ }
+
+ @Test
+ public void testStaticOffsetRanges()
+ {
+ Descriptor descriptor = Descriptor.create(directory, 0, 1);
+
+ ActiveOffsetRanges active = new ActiveOffsetRanges();
+ for (int l = 1; l < 11; l++)
+ {
+ for (int o = 5; o > 0 ; o--) active.update(id(l, o));
+ for (int o = 6; o < 11; o++) active.update(id(l, o));
+ }
+
+ active.persist(descriptor);
+ StaticOffsetRanges loaded = OffsetRangesFactory.INSTANCE.load(descriptor);
+ assertEquals(active.asMap(), loaded.asMap());
+
+ // absent log ids
+ for (int o = 0; o < 11; o++)
+ {
+ assertFalse(active.mayContain(id(0, o)));
+ assertFalse(loaded.mayContain(id(0, o)));
+ assertFalse(active.mayContain(id(11, o)));
+ assertFalse(loaded.mayContain(id(11, o)));
+ }
+
+ // present log ids
+ for (int l = 1; l < 11; l++)
+ {
+ assertFalse(active.mayContain(id(l, 0)));
+ assertFalse(loaded.mayContain(id(l, 0)));
+ assertFalse(active.mayContain(id(l, 11)));
+ assertFalse(loaded.mayContain(id(l, 11)));
+ for (int o = 1; o < 11; o++)
+ {
+ assertTrue(active.mayContain(id(l, o)));
+ assertTrue(loaded.mayContain(id(l, o)));
+ }
+ }
+ }
+
+ @Test
+ public void testDropReconcileSegments()
+ {
+ ShortMutationId id1 = id(100L, 0);
+ ShortMutationId id2 = id(100L, 1);
+ ShortMutationId id3 = id(200L, 2);
+ ShortMutationId id4 = id(200L, 3);
+
+ Mutation mutation1 = mutation("key1", "ck1", "value1");
+ Mutation mutation2 = mutation("key2", "ck2", "value2");
+ Mutation mutation3 = mutation("key3", "ck3", "value3");
+ Mutation mutation4 = mutation("key4", "ck4", "value4");
+
+ // write two mutations to the first segment and flush it to make static
+ journal.write(id1, mutation1);
+ journal.write(id2, mutation2);
+ journal.closeCurrentSegmentForTestingIfNonEmpty();
+
+ // write two mutations to the second segment and flush it to make static
+ journal.write(id3, mutation3);
+ journal.write(id4, mutation4);
+ journal.closeCurrentSegmentForTestingIfNonEmpty();
+
+ {
+ // call dropReconciledSegments() with a log2offsets map that covers both segments fully
+ // *BUT* with the segments still marked as needing replay nothing should be dropped
+ Log2OffsetsMap.Immutable.Builder builder = new Log2OffsetsMap.Immutable.Builder();
+ builder.add(id1);
+ builder.add(id2);
+ builder.add(id3);
+ builder.add(id4);
+ assertEquals(0, journal.dropReconciledSegments(builder.build()));
+ // confirm that no static segments have been dropped
+ assertEquals(2, journal.countStaticSegmentsForTesting());
+ }
+
+ // mark both segments as not needing replay
+ journal.clearNeedsReplayForTesting();
+
+ {
+ // call dropReconciledSegments() with a log2offsets map that doesn't cover any segments fully
+ Log2OffsetsMap.Immutable.Builder builder = new Log2OffsetsMap.Immutable.Builder();
+ builder.add(id1);
+ assertEquals(0, journal.dropReconciledSegments(builder.build()));
+ // confirm that no static segments got dropped
+ assertEquals(2, journal.countStaticSegmentsForTesting());
+ }
+
+ {
+ // call dropReconciledSegments() with a log2offsets map that covers only the first segment fully
+ Log2OffsetsMap.Immutable.Builder builder = new Log2OffsetsMap.Immutable.Builder();
+ builder.add(id1);
+ builder.add(id2);
+ assertEquals(1, journal.dropReconciledSegments(builder.build()));
+ // confirm that only one static segment got dropped
+ assertEquals(1, journal.countStaticSegmentsForTesting());
+ }
+
+ {
+ // call dropReconciledSegments() with a log2offsets map that covers both segments fully
+ Log2OffsetsMap.Immutable.Builder builder = new Log2OffsetsMap.Immutable.Builder();
+ builder.add(id1);
+ builder.add(id2);
+ builder.add(id3);
+ builder.add(id4);
+ assertEquals(1, journal.dropReconciledSegments(builder.build()));
+ // confirm that all static segments have now been dropped
+ assertEquals(0, journal.countStaticSegmentsForTesting());
+ }
+ }
+
+ private ShortMutationId id(long logId, int offset)
+ {
+ return new ShortMutationId(logId, offset);
+ }
+
+ private Mutation mutation(String pk, String ck, String column)
+ {
+ return new RowUpdateBuilder(Schema.instance.getTableMetadata(KEYSPACE, TABLE), 0, pk)
+ .clustering(ck)
+ .add("value", column)
+ .build();
+ }
+
public static void assertMutationEquals(Mutation expected, Mutation actual)
{
if (!serialize(expected).equals(serialize(actual)))
diff --git a/test/unit/org/apache/cassandra/replication/OffsetsTest.java b/test/unit/org/apache/cassandra/replication/OffsetsTest.java
index b0d5edda0f4c..1b39ff000e0b 100644
--- a/test/unit/org/apache/cassandra/replication/OffsetsTest.java
+++ b/test/unit/org/apache/cassandra/replication/OffsetsTest.java
@@ -861,9 +861,51 @@ public void testRemoveWithExactSizedArray()
}
}
+ @Test
public void asListFromListRoundTripTest()
{
for (Offsets.Mutable offsets : new Offsets.Mutable[] { offsets(), offsets(1, 2), offsets(1, 3, 7, 9) })
assertOffsetsEqual(offsets, Offsets.fromList(LOG_ID, offsets.asList()));
}
+
+ @Test
+ public void testContainsRange()
+ {
+ {
+ Offsets.Mutable offsets = offsets();
+ assertFalse(offsets.containsRange(0, 1));
+ }
+
+ {
+ Offsets.Mutable offsets = offsets(2, 4);
+
+ assertTrue(offsets.containsRange(2, 4));
+ assertTrue(offsets.containsRange(3, 4));
+ assertTrue(offsets.containsRange(2, 3));
+
+ assertFalse(offsets.containsRange(1, 1));
+ assertFalse(offsets.containsRange(1, 2));
+ assertFalse(offsets.containsRange(1, 3));
+ assertFalse(offsets.containsRange(1, 4));
+
+ assertFalse(offsets.containsRange(2, 5));
+ assertFalse(offsets.containsRange(3, 5));
+ assertFalse(offsets.containsRange(4, 5));
+ assertFalse(offsets.containsRange(5, 5));
+ }
+
+ {
+ Offsets.Mutable offsets = offsets(2, 4, 6, 8);
+
+ assertTrue(offsets.containsRange(2, 4));
+ assertTrue(offsets.containsRange(6, 8));
+
+ assertFalse(offsets.containsRange(0, 2));
+ assertFalse(offsets.containsRange(3, 5));
+ assertFalse(offsets.containsRange(4, 6));
+ assertFalse(offsets.containsRange(5, 7));
+ assertFalse(offsets.containsRange(7, 9));
+ assertFalse(offsets.containsRange(9, 9));
+ }
+ }
}