This is an automated email from the ASF dual-hosted git repository.
bdeggleston pushed a commit to branch cep-45-mutation-tracking
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/cep-45-mutation-tracking by
this push:
new 38e01d0972 CEP-45: Last coordinator log id can decrement on startup
38e01d0972 is described below
commit 38e01d09729aa01ff262c6b9c1cd03df210d2b70
Author: Blake Eggleston <[email protected]>
AuthorDate: Mon Oct 19 15:24:06 2026 -0700
CEP-45: Last coordinator log id can decrement on startup
- prevents broadcast log offsets messages from being processed prior to MTS
startup
- removes eager shard creation
- fixes a few other concurrency / durability gaps
Patch by Blake Eggleston; Reviewed by Caleb Rackliffe for CASSANDRA-21790
---
.../db/commitlog/AbstractCommitLogService.java | 4 +-
.../apache/cassandra/db/commitlog/CommitLog.java | 8 +
.../replication/MutationTrackingService.java | 234 +++++++++++++++------
.../replication/MutationTrackingServiceTest.java | 171 ++++++++++++++-
.../apache/cassandra/db/CleanupTransientTest.java | 1 +
.../db/CoordinatorLogOffsetsLifecycleTest.java | 3 +
.../replication/CoordinatorLogOffsetsTest.java | 2 +-
.../replication/MutationTrackingServiceTest.java | 51 +++++
.../replication/PendingLocalTransferTest.java | 2 +
.../replication/TrackedCounterWriteTest.java | 2 +
10 files changed, 410 insertions(+), 68 deletions(-)
diff --git
a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogService.java
b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogService.java
index 1831ec866e..7359cc95a4 100644
--- a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogService.java
+++ b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogService.java
@@ -319,12 +319,12 @@ public abstract class AbstractCommitLogService
do
{
WaitQueue.Signal signal = context != null ?
syncComplete.register(context, Context::stop) : syncComplete.register();
- if (lastSyncedAt < syncTime)
+ if (lastSyncedAt - syncTime < 0)
signal.awaitUninterruptibly();
else
signal.cancel();
}
- while (lastSyncedAt < syncTime);
+ while (lastSyncedAt - syncTime < 0);
}
public void awaitTermination() throws InterruptedException
diff --git a/src/java/org/apache/cassandra/db/commitlog/CommitLog.java
b/src/java/org/apache/cassandra/db/commitlog/CommitLog.java
index b47bf7804b..7c8ad49c70 100644
--- a/src/java/org/apache/cassandra/db/commitlog/CommitLog.java
+++ b/src/java/org/apache/cassandra/db/commitlog/CommitLog.java
@@ -292,6 +292,14 @@ public class CommitLog implements CommitLogMBean
executor.requestExtraSync();
}
+ /**
+ * Request sync and wait until the current state is synced.
+ */
+ public void syncBlocking()
+ {
+ executor.syncBlocking();
+ }
+
/**
* Add a Mutation to the commit log. If CDC is enabled, this can fail.
*
diff --git
a/src/java/org/apache/cassandra/replication/MutationTrackingService.java
b/src/java/org/apache/cassandra/replication/MutationTrackingService.java
index 7cbc31fc3f..b4ec550b14 100644
--- a/src/java/org/apache/cassandra/replication/MutationTrackingService.java
+++ b/src/java/org/apache/cassandra/replication/MutationTrackingService.java
@@ -60,6 +60,7 @@ import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.SystemKeyspace;
+import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.dht.Range;
@@ -81,6 +82,7 @@ import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
+import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.reads.tracked.TrackedLocalReads;
import org.apache.cassandra.streaming.StreamOperation;
import org.apache.cassandra.tcm.ClusterMetadata;
@@ -224,6 +226,9 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
private final Map<String, Set<MutationTrackingSyncCoordinator>>
syncCoordinatorsByKeyspace = new ConcurrentHashMap<>();
+ private final Object hostLogIdLock = new Object();
+ private volatile boolean hostLogIdLoaded = false;
+
private volatile boolean started = false;
private MutationTrackingService()
@@ -243,11 +248,11 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
if (started)
return;
- prevHostLogId = loadHostLogIdFromSystemTable();
+ ClusterMetadata metadata = register.apply(tcmListener);
- logger.info("Starting mutation tracking service. Previous host log id:
{}", prevHostLogId);
+ initHostLogId(metadata);
- ClusterMetadata metadata = register.apply(tcmListener);
+ logger.info("Starting mutation tracking service. Previous host log id:
{}", prevHostLogId);
if (metadata.myNodeId() != null)
for (KeyspaceShards ks :
KeyspaceShards.loadFromSystemTables(metadata, this::nextLogId, this::onNewLog))
@@ -371,7 +376,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
shardLock.readLock().lock();
try
{
- MutationId id = getOrCreateShards(keyspace).nextMutationId(token);
+ MutationId id = getShards(keyspace).nextMutationId(token);
logger.trace("Created new mutation id {}", id);
return id;
}
@@ -387,7 +392,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
shardLock.readLock().lock();
try
{
- KeyspaceShards shards = getOrCreateShards(keyspace);
+ KeyspaceShards shards = getShards(keyspace);
Shard shard = null;
for (Range<Token> range : ranges)
{
@@ -477,7 +482,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
shardLock.readLock().lock();
try
{
- KeyspaceShards shards = maybeGetOrCreateShards(keyspace);
+ KeyspaceShards shards = keyspaceShards.get(keyspace);
if (shards != null)
shards.updateReplicatedOffsets(range, offsets, durable,
onHost);
else
@@ -508,7 +513,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
try
{
reconciledSnapshot.forEach((keyspace, keyspaceOffsets) -> {
- KeyspaceShards ksShards = maybeGetOrCreateShards(keyspace);
+ KeyspaceShards ksShards = keyspaceShards.get(keyspace);
if (ksShards != null)
ksShards.recordFullyReconciledOffsets(keyspaceOffsets);
else
@@ -527,7 +532,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
try
{
Preconditions.checkArgument(!mutation.id().isNone());
- boolean started =
getOrCreateShards(mutation.getKeyspaceName()).startWriting(mutation);
+ boolean started =
getShards(mutation.getKeyspaceName()).startWriting(mutation);
// we've already received this mutation, so we reject the write.
There may still be reconciliation
// listening for it though, so notify them that it's received
if (!started)
@@ -582,7 +587,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
try
{
Preconditions.checkArgument(!mutation.id().isNone());
-
getOrCreateShards(mutation.getKeyspaceName()).finishWriting(mutation);
+ getShards(mutation.getKeyspaceName()).finishWriting(mutation);
incomingMutations.invokeListeners(mutation.id());
}
finally
@@ -713,7 +718,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
shardLock.readLock().lock();
try
{
- MutationSummary summary =
getOrCreateShards(tableId).createSummaryForKey(key, tableId, includePending);
+ MutationSummary summary =
getShards(tableId).createSummaryForKey(key, tableId, includePending);
MutationTrackingMetrics.instance().readSummarySize.update(summary.size());
return summary;
}
@@ -728,7 +733,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
shardLock.readLock().lock();
try
{
- MutationSummary summary =
getOrCreateShards(tableId).createSummaryForRange(range, tableId,
includePending);
+ MutationSummary summary =
getShards(tableId).createSummaryForRange(range, tableId, includePending);
MutationTrackingMetrics.instance().readSummarySize.update(summary.size());
return summary;
}
@@ -913,33 +918,22 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
return Preconditions.checkNotNull(log2ShardMap.get(logId));
}
- private KeyspaceShards getOrCreateShards(TableId tableId)
+ private KeyspaceShards getShards(TableId tableId)
{
- //noinspection DataFlowIssue
- return
getOrCreateShards(Schema.instance.getTableMetadata(tableId).keyspace);
+ TableMetadata metadata = Schema.instance.getTableMetadata(tableId);
+ if (metadata == null)
+ throw new IllegalStateException(String.format("Unknown table %s",
tableId));
+ return getShards(metadata.keyspace);
}
- private KeyspaceShards getOrCreateShards(String keyspace)
+ private KeyspaceShards getShards(String keyspace)
{
- KeyspaceShards shards = maybeGetOrCreateShards(keyspace);
- Preconditions.checkArgument(shards != null, "Unknown keyspace %s",
keyspace);
+ KeyspaceShards shards = keyspaceShards.get(keyspace);
+ if (shards == null)
+ throw new IllegalStateException(String.format("No mutation
tracking shards found for keyspace %s", keyspace));
return shards;
}
- private KeyspaceShards maybeGetOrCreateShards(String keyspace)
- {
- KeyspaceShards ks = keyspaceShards.get(keyspace);
- if (ks != null)
- return ks;
-
- ClusterMetadata csm = ClusterMetadata.current();
- KeyspaceMetadata ksm =
csm.schema.maybeGetKeyspaceMetadata(keyspace).orElse(null);
- if (ksm == null)
- return null;
-
- return keyspaceShards.computeIfAbsent(keyspace, ignore ->
KeyspaceShards.make(ksm, csm, this::nextLogId, this::onNewLog));
- }
-
private long nextLogId()
{
NodeId nodeId = ClusterMetadata.current().myNodeId();
@@ -953,11 +947,15 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
*/
private int nextHostLogId()
{
- int nextHostLogId = ++prevHostLogId;
- persistHostLogIdToSystemTable(nextHostLogId);
- return nextHostLogId;
+ synchronized (hostLogIdLock)
+ {
+ Preconditions.checkState(hostLogIdLoaded, "Host log id counter has
not been loaded from system tables");
+ int nextHostLogId = ++prevHostLogId;
+ persistHostLogIdToSystemTable(nextHostLogId);
+ return nextHostLogId;
+ }
}
- private int prevHostLogId;
+ private volatile int prevHostLogId;
public boolean isDurablyReconciled(ShortMutationId id)
{
@@ -1199,39 +1197,47 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
{
Preconditions.checkArgument(keyspace.getMetadata().replicationStrategy.replicationType.isTracked(),
"Keyspace " + keyspace.getName() + " is not tracked");
- KeyspaceShards shards = keyspaceShards.get(keyspace.getName());
- Map<Shard, List<SyncTask>> tasksByShard = new HashMap<>();
-
- // Shard ranges do not wrap, so unwrap the task ranges before we start
comparing them.
- for (SyncTask task : unwrapped(tasks))
+ shardLock.readLock().lock();
+ try
{
- Set<Shard> intersectingShards = new HashSet<>();
- shards.forEachIntersectingShard(task.rangesToSync,
intersectingShards::add);
- for (Shard shard : intersectingShards)
+ KeyspaceShards shards = getShards(keyspace.getName());
+ Map<Shard, List<SyncTask>> tasksByShard = new HashMap<>();
+
+ // Shard ranges do not wrap, so unwrap the task ranges before we
start comparing them.
+ for (SyncTask task : unwrapped(tasks))
{
- // Ensure that we don't expand outside the ranges of the
original sync tasks.
- Set<Range<Token>> intersectingSyncRanges = new HashSet<>();
- for (Range<Token> syncRange : task.rangesToSync)
-
intersectingSyncRanges.addAll(syncRange.intersectionWith(shard.range));
+ Set<Shard> intersectingShards = new HashSet<>();
+ shards.forEachIntersectingShard(task.rangesToSync,
intersectingShards::add);
+ for (Shard shard : intersectingShards)
+ {
+ // Ensure that we don't expand outside the ranges of the
original sync tasks.
+ Set<Range<Token>> intersectingSyncRanges = new HashSet<>();
+ for (Range<Token> syncRange : task.rangesToSync)
+
intersectingSyncRanges.addAll(syncRange.intersectionWith(shard.range));
- if (!intersectingSyncRanges.isEmpty())
- tasksByShard.computeIfAbsent(shard, key -> new
ArrayList<>()).add(task.withRanges(intersectingSyncRanges));
+ if (!intersectingSyncRanges.isEmpty())
+ tasksByShard.computeIfAbsent(shard, key -> new
ArrayList<>()).add(task.withRanges(intersectingSyncRanges));
+ }
}
- }
- SyncTasks into = new SyncTasks();
+ SyncTasks into = new SyncTasks();
- for (Map.Entry<Shard, List<SyncTask>> entry : tasksByShard.entrySet())
- {
- Shard shard = entry.getKey();
- Collection<SyncTask> syncTasks = entry.getValue();
+ for (Map.Entry<Shard, List<SyncTask>> entry :
tasksByShard.entrySet())
+ {
+ Shard shard = entry.getKey();
+ Collection<SyncTask> syncTasks = entry.getValue();
- // Assign a new transfer ID to each sync task and add to the tasks
container
- for (SyncTask task : syncTasks)
- into.add(shard, task.withTransferId(shard.nextId()));
- }
+ // Assign a new transfer ID to each sync task and add to the
tasks container
+ for (SyncTask task : syncTasks)
+ into.add(shard, task.withTransferId(shard.nextId()));
+ }
- return into;
+ return into;
+ }
+ finally
+ {
+ shardLock.readLock().unlock();
+ }
}
private static List<SyncTask> unwrapped(Collection<SyncTask> tasks)
@@ -1446,7 +1452,10 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
MutationId nextMutationId(Token token)
{
- return lookUp(token).nextId();
+ Shard shard = lookUp(token);
+ if (shard == null)
+ throw new IllegalStateException(String.format("No mutation
tracking shard covers token %s in keyspace %s", token, keyspace));
+ return shard.nextId();
}
void updateReplicatedOffsets(Range<Token> range, List<? extends
Offsets> offsets, boolean durable, InetAddressAndPort onHost)
@@ -1459,7 +1468,10 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
boolean startWriting(Mutation mutation)
{
- return lookUp(mutation).startWriting(mutation);
+ Shard shard = lookUp(mutation);
+ if (shard == null)
+ throw new IllegalStateException(String.format("No mutation
tracking shard covers mutation %s in keyspace %s", mutation.key(), keyspace));
+ return shard.startWriting(mutation);
}
/**
@@ -1481,13 +1493,19 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
void finishWriting(Mutation mutation)
{
- lookUp(mutation).finishWriting(mutation);
+ Shard shard = lookUp(mutation);
+ if (shard == null)
+ throw new IllegalStateException(String.format("No mutation
tracking shard covers mutation %s in keyspace %s", mutation.key(), keyspace));
+ shard.finishWriting(mutation);
}
MutationSummary createSummaryForKey(DecoratedKey key, TableId tableId,
boolean includePending)
{
MutationSummary.Builder builder = new
MutationSummary.Builder(tableId);
- lookUp(key.getToken()).addSummaryForKey(key.getToken(),
includePending, builder);
+ Shard shard = lookUp(key.getToken());
+ if (shard == null)
+ throw new IllegalStateException(String.format("No mutation
tracking shard covers key %s in keyspace %s", key.getToken(), keyspace));
+ shard.addSummaryForKey(key.getToken(), includePending, builder);
return builder.build();
}
@@ -1605,6 +1623,7 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
static void persistHostLogIdToSystemTable(int hostLogId)
{
executeInternal(INSERT_QUERY, HOST_LOG_ID_KEY, hostLogId);
+ CommitLog.instance.syncBlocking();
}
private static final String SELECT_QUERY =
@@ -1618,6 +1637,46 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
return rows.one().getInt("host_log_id");
}
+ private static final String SELECT_COORDINATOR_LOGS_QUERY =
+ format("SELECT host_id, host_log_id FROM %s.%s",
+ SchemaConstants.SYSTEM_KEYSPACE_NAME,
SystemKeyspace.COORDINATOR_LOGS);
+
+ static int loadMaxCoordinatorLogIdFromSystemTable(int localHostId)
+ {
+ UntypedResultSet rows = executeInternal(SELECT_COORDINATOR_LOGS_QUERY);
+ int max = 0;
+ for (UntypedResultSet.Row row : rows)
+ {
+ if (row.has("host_id") && row.getInt("host_id") == localHostId)
+ {
+ if (row.has("host_log_id"))
+ max = Math.max(max, row.getInt("host_log_id"));
+ }
+ }
+ return max;
+ }
+
+ private void initHostLogId(ClusterMetadata metadata)
+ {
+ NodeId myNodeId = metadata.myNodeId();
+ synchronized (hostLogIdLock)
+ {
+ int storedHostLogId = loadHostLogIdFromSystemTable();
+ int maxCoordinatorLogId = myNodeId != null ?
loadMaxCoordinatorLogIdFromSystemTable(myNodeId.id()) : 0;
+ if (maxCoordinatorLogId > storedHostLogId)
+ {
+ logger.warn("Stored mutation tracking host log id {} in {}.{}
has regressed; highest log id found in {}.{} for local node {} is {}",
+ storedHostLogId,
SchemaConstants.SYSTEM_KEYSPACE_NAME, SystemKeyspace.HOST_LOG_ID,
+ SchemaConstants.SYSTEM_KEYSPACE_NAME,
SystemKeyspace.COORDINATOR_LOGS,
+ myNodeId, maxCoordinatorLogId);
+ }
+ prevHostLogId = Math.max(storedHostLogId, maxCoordinatorLogId);
+ if (prevHostLogId > storedHostLogId)
+ persistHostLogIdToSystemTable(prevHostLogId);
+ hostLogIdLoaded = true;
+ }
+ }
+
private static class BackgroundReconciler
{
void start()
@@ -1927,10 +1986,57 @@ public class MutationTrackingService implements
MutationTrackingServiceMBean
public static class TestAccess
{
public static MutationTrackingService create()
+ {
+ MutationTrackingService service = new MutationTrackingService();
+ service.hostLogIdLoaded = true;
+ return service;
+ }
+
+ public static MutationTrackingService createUnloaded()
{
return new MutationTrackingService();
}
+ public static int nextHostLogId(MutationTrackingService service)
+ {
+ return service.nextHostLogId();
+ }
+
+ public static void initHostLogId(MutationTrackingService service,
ClusterMetadata metadata)
+ {
+ service.initHostLogId(metadata);
+ }
+
+ public static int getPrevHostLogId(MutationTrackingService service)
+ {
+ return service.prevHostLogId;
+ }
+
+ public static List<Long> getPrimaryHostLogIds(MutationTrackingService
service, String keyspace)
+ {
+ KeyspaceShards shards = service.keyspaceShards.get(keyspace);
+ List<Long> ids = new ArrayList<>();
+ if (shards != null)
+ shards.forEachShard(s -> ids.add((long)
s.currentLocalLog().logId.hostLogId));
+ return ids;
+ }
+
+ public static void broadcastOffsetsTo(MutationTrackingService service,
String keyspace, InetAddressAndPort target)
+ {
+ KeyspaceShards shards = service.keyspaceShards.get(keyspace);
+ if (shards != null)
+ {
+ shards.forEachShard(shard -> {
+ BroadcastLogOffsets transientOffsets =
shard.collectReplicatedOffsets(false);
+ if (!transientOffsets.isEmpty())
+
MessagingService.instance().send(Message.out(Verb.MT_BROADCAST_LOG_OFFSETS,
transientOffsets), target);
+ BroadcastLogOffsets durableOffsets =
shard.collectReplicatedOffsets(true);
+ if (!durableOffsets.isEmpty())
+
MessagingService.instance().send(Message.out(Verb.MT_BROADCAST_LOG_OFFSETS,
durableOffsets), target);
+ });
+ }
+ }
+
public static KeyspaceShards getKeyspaceShards(MutationTrackingService
service, String keyspace)
{
return service.keyspaceShards.get(keyspace);
diff --git
a/test/distributed/org/apache/cassandra/distributed/test/replication/MutationTrackingServiceTest.java
b/test/distributed/org/apache/cassandra/distributed/test/replication/MutationTrackingServiceTest.java
index 055b1e4ead..6b77b2a0f7 100644
---
a/test/distributed/org/apache/cassandra/distributed/test/replication/MutationTrackingServiceTest.java
+++
b/test/distributed/org/apache/cassandra/distributed/test/replication/MutationTrackingServiceTest.java
@@ -18,12 +18,30 @@
package org.apache.cassandra.distributed.test.replication;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+
import org.junit.Test;
import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.test.TestBaseImpl;
+import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.replication.MutationTrackingService;
+import org.apache.cassandra.utils.Shared;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class MutationTrackingServiceTest extends TestBaseImpl
@@ -31,6 +49,56 @@ public class MutationTrackingServiceTest extends TestBaseImpl
private static final String KS_NAME = "ks";
private static final String TBL_NAME = "tbl";
+ @Shared
+ public static class BBState
+ {
+ public static volatile boolean enabled = false;
+ public static volatile CountDownLatch enteredLatch = new
CountDownLatch(1);
+ public static volatile CountDownLatch pauseLatch = new
CountDownLatch(1);
+
+ public static void reset()
+ {
+ enabled = false;
+ enteredLatch = new CountDownLatch(1);
+ pauseLatch = new CountDownLatch(1);
+ }
+ }
+
+ public static class BBInstaller
+ {
+ public static void install(ClassLoader cl, Integer num)
+ {
+ if (num == 1)
+ {
+ new ByteBuddy().rebase(MutationTrackingService.class)
+ .method(named("start").and(takesArguments(0)))
+
.intercept(MethodDelegation.to(BBInterceptor.class))
+ .make()
+ .load(cl,
ClassLoadingStrategy.Default.INJECTION);
+ }
+ }
+ }
+
+ public static class BBInterceptor
+ {
+ public static void start(@SuperCall Runnable zuper)
+ {
+ if (BBState.enabled)
+ {
+ BBState.enteredLatch.countDown();
+ try
+ {
+ BBState.pauseLatch.await(60, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ zuper.run();
+ }
+ }
+
@Test
public void testIrrelevantTopologyChange() throws Throwable
{
@@ -52,8 +120,109 @@ public class MutationTrackingServiceTest extends
TestBaseImpl
Object newShards =
MutationTrackingService.TestAccess.getKeyspaceShards(service, KS_NAME);
return initialShards == newShards; // Same object reference
});
-
+
assertTrue("Keyspace shards should not change for irrelevant
topology changes", shardsUnchanged);
}
}
+
+ @Test
+ public void testHostLogIdNotRegressedByBroadcastDuringStartup() throws
Throwable
+ {
+ BBState.reset();
+ try (Cluster cluster = builder().withNodes(2)
+
.withInstanceInitializer(BBInstaller::install)
+ .start())
+ {
+ cluster.schemaChange("CREATE KEYSPACE " + KS_NAME + " WITH
replication = " +
+ "{'class': 'SimpleStrategy',
'replication_factor': 2} AND replication_type='tracked'");
+ cluster.schemaChange("CREATE TABLE " + KS_NAME + "." + TBL_NAME +
" (k int PRIMARY KEY, v int)");
+
+ // Perform writes to generate mutations from both coordinators so
shards have active offsets
+ cluster.coordinator(1).execute("INSERT INTO " + KS_NAME + "." +
TBL_NAME + " (k, v) VALUES (1, 1)",
+ ConsistencyLevel.ALL);
+ cluster.coordinator(2).execute("INSERT INTO " + KS_NAME + "." +
TBL_NAME + " (k, v) VALUES (2, 2)",
+ ConsistencyLevel.ALL);
+
+ // Allocate additional log IDs on node 1 so counter is well above
1 (mirroring a node that ran for some time)
+ cluster.get(1).runOnInstance(() -> {
+ var service = MutationTrackingService.instance();
+ for (int i = 0; i < 10; i++)
+ MutationTrackingService.TestAccess.nextHostLogId(service);
+ });
+
+ // Read host_log_id before restart on node 1
+ Object[][] res = cluster.get(1).executeInternal("SELECT
host_log_id FROM system.host_log_id WHERE key='local'");
+ assertNotNull(res);
+ assertTrue(res.length > 0);
+ int initialHostLogId = (Integer) res[0][0];
+ assertTrue("Expected positive initial host log id, got " +
initialHostLogId, initialHostLogId > 0);
+
+ // Ensure node 2 has witnessed the mutations and can broadcast
offsets
+ cluster.get(2).runOnInstance(() -> {
+
MutationTrackingService.instance().broadcastOffsetsForTesting();
+ });
+
+ // Shut down node 1
+ cluster.get(1).shutdown().get();
+
+ // Enable startup pause for node 1
+ BBState.enabled = true;
+ BBState.enteredLatch = new CountDownLatch(1);
+ BBState.pauseLatch = new CountDownLatch(1);
+
+ // Start node 1 asynchronously
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ Future<?> startupFuture = executor.submit(() -> {
+ try
+ {
+ cluster.get(1).startup();
+ }
+ catch (Throwable t)
+ {
+ throw new RuntimeException(t);
+ }
+ });
+
+ try
+ {
+ // Wait for node 1 to reach MutationTrackingService.start()
+ assertTrue("Timed out waiting for node 1 to reach
MutationTrackingService.start()",
+ BBState.enteredLatch.await(60, TimeUnit.SECONDS));
+
+ InetAddressAndPort node1Addr =
InetAddressAndPort.getByNameUnchecked("127.0.0.1:7012");
+ cluster.get(2).runOnInstance(() -> {
+
MutationTrackingService.TestAccess.broadcastOffsetsTo(MutationTrackingService.instance(),
KS_NAME, node1Addr);
+ });
+ Thread.sleep(500);
+ }
+ finally
+ {
+ // Unpause node 1 so startup can finish
+ BBState.pauseLatch.countDown();
+ }
+
+ startupFuture.get(60, TimeUnit.SECONDS);
+ executor.shutdown();
+
+ // Verify post-restart state on node 1:
+ // 1. system.host_log_id must be strictly greater than
initialHostLogId (since boot allocates a new primary log)
+ Object[][] postRes = cluster.get(1).executeInternal("SELECT
host_log_id FROM system.host_log_id WHERE key='local'");
+ assertNotNull(postRes);
+ assertTrue(postRes.length > 0);
+ int postHostLogId = (Integer) postRes[0][0];
+ assertTrue("Post restart host log id " + postHostLogId + " should
be greater than initial " + initialHostLogId,
+ postHostLogId > initialHostLogId);
+
+ // 2. New primary log id on each shard must be strictly greater
than initialHostLogId
+ List<Long> newPrimaryLogIds = cluster.get(1).callOnInstance(() -> {
+ return
MutationTrackingService.TestAccess.getPrimaryHostLogIds(MutationTrackingService.instance(),
KS_NAME);
+ });
+
+ for (Long logId : newPrimaryLogIds)
+ {
+ assertTrue("New primary log id " + logId + " must be greater
than initial host log id " + initialHostLogId,
+ logId > initialHostLogId);
+ }
+ }
+ }
}
diff --git a/test/unit/org/apache/cassandra/db/CleanupTransientTest.java
b/test/unit/org/apache/cassandra/db/CleanupTransientTest.java
index b560f6a300..a2c58bd8d6 100644
--- a/test/unit/org/apache/cassandra/db/CleanupTransientTest.java
+++ b/test/unit/org/apache/cassandra/db/CleanupTransientTest.java
@@ -89,6 +89,7 @@ public class CleanupTransientTest extends CassandraTestBase
Util.createInitialRing(endpointTokens, keyTokens, hosts, hostIds,
RING_SIZE);
MutationJournal.start();
+ MutationTrackingService.start();
SchemaLoader.createKeyspace(KEYSPACE1,
KeyspaceParams.simpleWitness("2/1"),
SchemaLoader.standardCFMD(KEYSPACE1,
CF_STANDARD1),
diff --git
a/test/unit/org/apache/cassandra/db/CoordinatorLogOffsetsLifecycleTest.java
b/test/unit/org/apache/cassandra/db/CoordinatorLogOffsetsLifecycleTest.java
index bc6f2b2046..31a5ea32fc 100644
--- a/test/unit/org/apache/cassandra/db/CoordinatorLogOffsetsLifecycleTest.java
+++ b/test/unit/org/apache/cassandra/db/CoordinatorLogOffsetsLifecycleTest.java
@@ -45,6 +45,7 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.replication.ImmutableCoordinatorLogOffsets;
import org.apache.cassandra.replication.MutationId;
+import org.apache.cassandra.replication.MutationJournal;
import org.apache.cassandra.replication.MutationTrackingService;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.MemtableParams;
@@ -68,6 +69,8 @@ public class CoordinatorLogOffsetsLifecycleTest
public static void setupClass()
{
SchemaLoader.prepareServer();
+ MutationJournal.start();
+ MutationTrackingService.start();
}
@Parameter
diff --git
a/test/unit/org/apache/cassandra/replication/CoordinatorLogOffsetsTest.java
b/test/unit/org/apache/cassandra/replication/CoordinatorLogOffsetsTest.java
index ca6b63298f..3cc992f0b6 100644
--- a/test/unit/org/apache/cassandra/replication/CoordinatorLogOffsetsTest.java
+++ b/test/unit/org/apache/cassandra/replication/CoordinatorLogOffsetsTest.java
@@ -278,7 +278,7 @@ public class CoordinatorLogOffsetsTest
Keyspace.setInitialized();
ClusterMetadata.current().schema.initializeKeyspaceInstances(DistributedSchema.empty(),
false);
- MutationTrackingService.start(unused -> metadata);
+ MutationTrackingService.start(unused -> ClusterMetadata.current());
// Eventually, will also run perturbations before checking
isReconciled (like log truncation, durability, etc.)
// to ensure that we don't prune data required to check what's been
reconciled
diff --git
a/test/unit/org/apache/cassandra/replication/MutationTrackingServiceTest.java
b/test/unit/org/apache/cassandra/replication/MutationTrackingServiceTest.java
index ce9e61f40a..52f91b254f 100644
---
a/test/unit/org/apache/cassandra/replication/MutationTrackingServiceTest.java
+++
b/test/unit/org/apache/cassandra/replication/MutationTrackingServiceTest.java
@@ -35,8 +35,10 @@ import org.junit.Test;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.PartitionPosition;
+import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.ByteOrderedPartitioner;
import org.apache.cassandra.dht.Range;
@@ -50,6 +52,7 @@ import org.apache.cassandra.repair.SyncTask;
import org.apache.cassandra.repair.SyncTasks;
import org.apache.cassandra.replication.MutationTrackingService.KeyspaceShards;
import org.apache.cassandra.schema.KeyspaceParams;
+import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.ReplicationType;
import org.apache.cassandra.schema.SchemaTestUtil;
import org.apache.cassandra.schema.TableId;
@@ -443,6 +446,54 @@ public class MutationTrackingServiceTest
}
}
+ @Test
+ public void testNextHostLogIdFailsBeforeLoaded()
+ {
+ MutationTrackingService service =
MutationTrackingService.TestAccess.createUnloaded();
+ try
+ {
+ MutationTrackingService.TestAccess.nextHostLogId(service);
+ Assert.fail("Expected IllegalStateException when allocating log ID
before counter is loaded");
+ }
+ catch (IllegalStateException e)
+ {
+ assertTrue(e.getMessage().contains("Host log id counter has not
been loaded"));
+ }
+ }
+
+ @Test
+ public void testStartupGuardBumpsRegressedCounter()
+ {
+ QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s
(keyspace_name, range_start, range_end, host_id, host_log_id) VALUES (?, ?, ?,
?, ?)",
+
SchemaConstants.SYSTEM_KEYSPACE_NAME, SystemKeyspace.COORDINATOR_LOGS),
+ "test_ks", "a", "z", 1, 56);
+ QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s
(keyspace_name, range_start, range_end, host_id, host_log_id) VALUES (?, ?, ?,
?, ?)",
+
SchemaConstants.SYSTEM_KEYSPACE_NAME, SystemKeyspace.COORDINATOR_LOGS),
+ "test_ks", "a", "z", 2, 100);
+
+ assertEquals(56,
MutationTrackingService.loadMaxCoordinatorLogIdFromSystemTable(1));
+ assertEquals(100,
MutationTrackingService.loadMaxCoordinatorLogIdFromSystemTable(2));
+
+ // Stored counter is 6 (regressed compared to 56)
+ MutationTrackingService.persistHostLogIdToSystemTable(6);
+ assertEquals(6,
MutationTrackingService.loadHostLogIdFromSystemTable());
+
+ MutationTrackingService service =
MutationTrackingService.TestAccess.createUnloaded();
+ ClusterMetadata metadata = ClusterMetadata.current();
+ MutationTrackingService.TestAccess.initHostLogId(service, metadata);
+
+ // Counter bumped to 56 and persisted
+ assertEquals(56,
MutationTrackingService.TestAccess.getPrevHostLogId(service));
+ assertEquals(56,
MutationTrackingService.loadHostLogIdFromSystemTable());
+
+ // When stored is higher, counter does not regress
+ MutationTrackingService.persistHostLogIdToSystemTable(70);
+ MutationTrackingService service2 =
MutationTrackingService.TestAccess.createUnloaded();
+ MutationTrackingService.TestAccess.initHostLogId(service2, metadata);
+ assertEquals(70,
MutationTrackingService.TestAccess.getPrevHostLogId(service2));
+ assertEquals(70,
MutationTrackingService.loadHostLogIdFromSystemTable());
+ }
+
private static Token tk(String key)
{
return new
ByteOrderedPartitioner.BytesToken(ByteBufferUtil.bytes(key));
diff --git
a/test/unit/org/apache/cassandra/replication/PendingLocalTransferTest.java
b/test/unit/org/apache/cassandra/replication/PendingLocalTransferTest.java
index 751af68497..604dedfc77 100644
--- a/test/unit/org/apache/cassandra/replication/PendingLocalTransferTest.java
+++ b/test/unit/org/apache/cassandra/replication/PendingLocalTransferTest.java
@@ -78,6 +78,8 @@ public class PendingLocalTransferTest
public static void setUpClass()
{
SchemaLoader.prepareServer();
+ MutationJournal.start();
+ MutationTrackingService.start();
TableMetadata metadata = CreateTableStatement.parse(format("CREATE
TABLE %s.%s (k int PRIMARY KEY, v int)", KS, TBL), KS).build();
SchemaLoader.createKeyspace(KS, KeyspaceParams.simple(1,
ReplicationType.tracked), metadata);
cfs = Schema.instance.getColumnFamilyStoreInstance(metadata.id);
diff --git
a/test/unit/org/apache/cassandra/replication/TrackedCounterWriteTest.java
b/test/unit/org/apache/cassandra/replication/TrackedCounterWriteTest.java
index db0f3df2aa..a2ec95af40 100644
--- a/test/unit/org/apache/cassandra/replication/TrackedCounterWriteTest.java
+++ b/test/unit/org/apache/cassandra/replication/TrackedCounterWriteTest.java
@@ -59,6 +59,8 @@ public class TrackedCounterWriteTest
public static void defineSchema() throws ConfigurationException
{
SchemaLoader.prepareServer();
+ MutationJournal.start();
+ MutationTrackingService.start();
SchemaLoader.createKeyspace(KEYSPACE_TRACKED,
KeyspaceParams.simple(3,
ReplicationType.tracked),
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]