mirror of https://github.com/apache/cassandra
Add accord journal standalone dump tool
Patch by Alex Petrov; reviewed by Benedict Elliott Smith for CASSANDRA-20738
This commit is contained in:
parent
674b8d5100
commit
ccf12ef845
|
|
@ -1,4 +1,4 @@
|
||||||
[submodule "modules/accord"]
|
[submodule "modules/accord"]
|
||||||
path = modules/accord
|
path = modules/accord
|
||||||
url = https://github.com/apache/cassandra-accord.git
|
url = https://github.com/apache/cassandra-accord.git
|
||||||
branch = trunk
|
branch = trunk
|
||||||
|
|
@ -196,10 +196,11 @@ public class AccordSpec
|
||||||
private volatile long flushCombinedBlockPeriod = Long.MIN_VALUE;
|
private volatile long flushCombinedBlockPeriod = Long.MIN_VALUE;
|
||||||
public Version version = Version.DOWNGRADE_SAFE_VERSION;
|
public Version version = Version.DOWNGRADE_SAFE_VERSION;
|
||||||
|
|
||||||
public void setFlushPeriod(DurationSpec newFlushPeriod)
|
public JournalSpec setFlushPeriod(DurationSpec newFlushPeriod)
|
||||||
{
|
{
|
||||||
flushPeriod = newFlushPeriod;
|
flushPeriod = newFlushPeriod;
|
||||||
flushCombinedBlockPeriod = Long.MIN_VALUE;
|
flushCombinedBlockPeriod = Long.MIN_VALUE;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPeriodicFlushLagBlock(DurationSpec newPeriodicFlushLagBlock)
|
public void setPeriodicFlushLagBlock(DurationSpec newPeriodicFlushLagBlock)
|
||||||
|
|
|
||||||
|
|
@ -2234,7 +2234,7 @@ public class DatabaseDescriptor
|
||||||
}
|
}
|
||||||
catch (FSWriteError e)
|
catch (FSWriteError e)
|
||||||
{
|
{
|
||||||
throw new IllegalStateException(e.getCause().getMessage() + "; unable to start server");
|
throw new IllegalStateException(e.getCause().getMessage() + "; unable to start server", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ public final class Descriptor implements Comparable<Descriptor>
|
||||||
return new Descriptor(directory, timestamp, generation, journalVersion, userVersion);
|
return new Descriptor(directory, timestamp, generation, journalVersion, userVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Descriptor fromFile(File file)
|
public static Descriptor fromFile(File file)
|
||||||
{
|
{
|
||||||
return fromName(file.parent(), file.name());
|
return fromName(file.parent(), file.name());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* 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.journal;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper file to avoid exposing components outside their package-local visibility scope
|
||||||
|
*/
|
||||||
|
public class DumpUtil
|
||||||
|
{
|
||||||
|
public static void dumpMetadata(Descriptor descriptor, Consumer<String> out)
|
||||||
|
{
|
||||||
|
if (Component.METADATA.existsFor(descriptor))
|
||||||
|
{
|
||||||
|
out.accept(Metadata.load(descriptor).toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
out.accept("Metadata absent for " + descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <K, V> StaticSegment<K, V> open(Descriptor descriptor, KeySupport<K> keySupport)
|
||||||
|
{
|
||||||
|
return StaticSegment.open(descriptor, keySupport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -154,4 +154,13 @@ final class Metadata
|
||||||
metadata.persist(descriptor);
|
metadata.persist(descriptor);
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "Metadata{" +
|
||||||
|
"fsyncLimit=" + fsyncLimit +
|
||||||
|
", recordsCount=" + recordsCount +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ public final class StaticSegment<K, V> extends Segment<K, V>
|
||||||
/**
|
/**
|
||||||
* Iterate over and invoke the supplied callback on every record.
|
* Iterate over and invoke the supplied callback on every record.
|
||||||
*/
|
*/
|
||||||
void forEachRecord(RecordConsumer<K> consumer)
|
public void forEachRecord(RecordConsumer<K> consumer)
|
||||||
{
|
{
|
||||||
try (SequentialReader<K> reader = sequentialReader(descriptor, keySupport, fsyncLimit))
|
try (SequentialReader<K> reader = sequentialReader(descriptor, keySupport, fsyncLimit))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
@ -167,7 +168,14 @@ public class InboundMessageHandler extends AbstractMessageHandler
|
||||||
{
|
{
|
||||||
Message<?> m = serializer.deserialize(in, header, version);
|
Message<?> m = serializer.deserialize(in, header, version);
|
||||||
if (in.available() > 0) // bytes remaining after deser: deserializer is busted
|
if (in.available() > 0) // bytes remaining after deser: deserializer is busted
|
||||||
throw new InvalidSerializedSizeException(header.verb, size, size - in.available());
|
{
|
||||||
|
Throwable t = new InvalidSerializedSizeException(header.verb, size, size - in.available());
|
||||||
|
ByteBuffer clone = bytes.get();
|
||||||
|
clone.limit(clone.position() + size); // cap to expected message size
|
||||||
|
logger.error("Could not deserialize the message: {}", ByteBufferUtil.bytesToHex(clone), t);
|
||||||
|
throw t;
|
||||||
|
|
||||||
|
}
|
||||||
message = m;
|
message = m;
|
||||||
}
|
}
|
||||||
catch (IncompatibleSchemaException e)
|
catch (IncompatibleSchemaException e)
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,14 @@ public final class SchemaConstants
|
||||||
emptyVersion = UUID.nameUUIDFromBytes(Digest.forSchema().digest());
|
emptyVersion = UUID.nameUUIDFromBytes(Digest.forSchema().digest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the table is an Accord Journal tabe
|
||||||
|
*/
|
||||||
|
public static boolean isAccordJournal(String keyspaceName, String tableName)
|
||||||
|
{
|
||||||
|
return keyspaceName.equals(SchemaConstants.ACCORD_KEYSPACE_NAME) && tableName.equals(AccordKeyspace.JOURNAL);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether or not the keyspace is a really system one (w/ LocalStrategy, unmodifiable, hardcoded)
|
* @return whether or not the keyspace is a really system one (w/ LocalStrategy, unmodifiable, hardcoded)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -415,7 +415,7 @@ public class AccordJournal implements accord.api.Journal, RangeSearcher.Supplier
|
||||||
return loadDiffs(commandStoreId, txnId, Load.ALL);
|
return loadDiffs(commandStoreId, txnId, Load.ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <BUILDER extends FlyweightImage> BUILDER readAll(JournalKey key)
|
public <BUILDER extends FlyweightImage> BUILDER readAll(JournalKey key)
|
||||||
{
|
{
|
||||||
BUILDER builder = (BUILDER) key.type.serializer.mergerFor();
|
BUILDER builder = (BUILDER) key.type.serializer.mergerFor();
|
||||||
// TODO (expected): this can be further improved to avoid allocating lambdas
|
// TODO (expected): this can be further improved to avoid allocating lambdas
|
||||||
|
|
@ -425,6 +425,11 @@ public class AccordJournal implements accord.api.Journal, RangeSearcher.Supplier
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void forEachEntry(JournalKey key, AccordJournalTable.Reader reader)
|
||||||
|
{
|
||||||
|
journalTable.readAll(key, reader);
|
||||||
|
}
|
||||||
|
|
||||||
private <T> RecordPointer appendInternal(JournalKey key, T write)
|
private <T> RecordPointer appendInternal(JournalKey key, T write)
|
||||||
{
|
{
|
||||||
AccordJournalValueSerializers.FlyweightSerializer<T, ?> serializer = (AccordJournalValueSerializers.FlyweightSerializer<T, ?>) key.type.serializer;
|
AccordJournalValueSerializers.FlyweightSerializer<T, ?> serializer = (AccordJournalValueSerializers.FlyweightSerializer<T, ?>) key.type.serializer;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,13 @@ public class AccordJournalValueSerializers
|
||||||
void reserialize(JournalKey key, IMAGE from, DataOutputPlus out, Version userVersion) throws IOException;
|
void reserialize(JournalKey key, IMAGE from, DataOutputPlus out, Version userVersion) throws IOException;
|
||||||
|
|
||||||
void deserialize(JournalKey key, IMAGE into, DataInputPlus in, Version userVersion) throws IOException;
|
void deserialize(JournalKey key, IMAGE into, DataInputPlus in, Version userVersion) throws IOException;
|
||||||
|
|
||||||
|
default IMAGE deserialize(JournalKey key, DataInputPlus in, Version userVersion) throws IOException
|
||||||
|
{
|
||||||
|
IMAGE image = mergerFor();
|
||||||
|
deserialize(key, image, in, userVersion);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CommandDiffSerializer
|
public static class CommandDiffSerializer
|
||||||
|
|
@ -142,6 +149,14 @@ public class AccordJournalValueSerializers
|
||||||
hasRead = true;
|
hasRead = true;
|
||||||
return newValue;
|
return newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "IdentityAccumulator{" +
|
||||||
|
initial +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RedundantBeforeSerializer
|
public static class RedundantBeforeSerializer
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,14 @@ import java.util.function.Supplier;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import org.apache.cassandra.schema.*;
|
||||||
|
import org.apache.cassandra.service.accord.AccordFastPath;
|
||||||
|
import org.apache.cassandra.service.accord.AccordStaleReplicas;
|
||||||
|
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
|
||||||
|
import org.apache.cassandra.tcm.membership.Directory;
|
||||||
|
import org.apache.cassandra.tcm.ownership.DataPlacements;
|
||||||
|
import org.apache.cassandra.tcm.ownership.TokenMap;
|
||||||
|
import org.apache.cassandra.tcm.sequences.LockedRanges;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
@ -51,8 +59,6 @@ import org.apache.cassandra.metrics.TCMMetrics;
|
||||||
import org.apache.cassandra.net.IVerbHandler;
|
import org.apache.cassandra.net.IVerbHandler;
|
||||||
import org.apache.cassandra.net.Message;
|
import org.apache.cassandra.net.Message;
|
||||||
import org.apache.cassandra.net.MessageDelivery;
|
import org.apache.cassandra.net.MessageDelivery;
|
||||||
import org.apache.cassandra.schema.DistributedSchema;
|
|
||||||
import org.apache.cassandra.schema.ReplicationParams;
|
|
||||||
import org.apache.cassandra.tcm.listeners.SchemaListener;
|
import org.apache.cassandra.tcm.listeners.SchemaListener;
|
||||||
import org.apache.cassandra.tcm.log.Entry;
|
import org.apache.cassandra.tcm.log.Entry;
|
||||||
import org.apache.cassandra.tcm.log.LocalLog;
|
import org.apache.cassandra.tcm.log.LocalLog;
|
||||||
|
|
@ -288,6 +294,49 @@ public class ClusterMetadataService
|
||||||
ClusterMetadataService.setInstance(cms);
|
ClusterMetadataService.setInstance(cms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void empty(Keyspaces keyspaces)
|
||||||
|
{
|
||||||
|
if (instance != null)
|
||||||
|
return;
|
||||||
|
String localDC = DatabaseDescriptor.getLocalDataCenter();
|
||||||
|
ClusterMetadata empty = new ClusterMetadata(Epoch.EMPTY,
|
||||||
|
DatabaseDescriptor.getPartitioner(),
|
||||||
|
new DistributedSchema(keyspaces),
|
||||||
|
Directory.EMPTY,
|
||||||
|
new TokenMap(DatabaseDescriptor.getPartitioner()),
|
||||||
|
DataPlacements.empty(),
|
||||||
|
AccordFastPath.EMPTY,
|
||||||
|
LockedRanges.EMPTY,
|
||||||
|
InProgressSequences.EMPTY,
|
||||||
|
ConsensusMigrationState.EMPTY,
|
||||||
|
Collections.emptyMap(),
|
||||||
|
AccordStaleReplicas.EMPTY);
|
||||||
|
|
||||||
|
|
||||||
|
LocalLog.LogSpec logSpec = LocalLog.logSpec()
|
||||||
|
.withInitialState(empty)
|
||||||
|
.loadSSTables(false)
|
||||||
|
.withDefaultListeners(false)
|
||||||
|
.sync()
|
||||||
|
.withStorage(new AtomicLongBackedProcessor.InMemoryStorage());
|
||||||
|
LocalLog log = logSpec.createLog();
|
||||||
|
ClusterMetadataService cms = new ClusterMetadataService(new UniformRangePlacement(),
|
||||||
|
MetadataSnapshots.NO_OP,
|
||||||
|
log,
|
||||||
|
new AtomicLongBackedProcessor(log),
|
||||||
|
new LogState.ReplicationHandler(log),
|
||||||
|
new LogState.LogNotifyHandler(log),
|
||||||
|
new CurrentEpochRequestHandler(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
|
||||||
|
log.readyUnchecked();
|
||||||
|
log.bootstrap(FBUtilities.getBroadcastAddressAndPort(), localDC);
|
||||||
|
ClusterMetadataService.setInstance(cms);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public static void initializeForClients()
|
public static void initializeForClients()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,353 @@
|
||||||
|
/*
|
||||||
|
* 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.tools;
|
||||||
|
|
||||||
|
import accord.local.RedundantBefore;
|
||||||
|
import accord.primitives.Timestamp;
|
||||||
|
import accord.primitives.TxnId;
|
||||||
|
import io.airlift.airline.Cli;
|
||||||
|
import io.airlift.airline.Command;
|
||||||
|
import io.airlift.airline.Option;
|
||||||
|
import org.apache.cassandra.config.AccordSpec;
|
||||||
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
|
import org.apache.cassandra.config.DurationSpec;
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Keyspace;
|
||||||
|
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||||
|
import org.apache.cassandra.io.util.File;
|
||||||
|
import org.apache.cassandra.journal.Descriptor;
|
||||||
|
import org.apache.cassandra.journal.DumpUtil;
|
||||||
|
import org.apache.cassandra.journal.StaticSegment;
|
||||||
|
import org.apache.cassandra.schema.Keyspaces;
|
||||||
|
import org.apache.cassandra.schema.Schema;
|
||||||
|
import org.apache.cassandra.schema.SchemaConstants;
|
||||||
|
import org.apache.cassandra.schema.Tables;
|
||||||
|
import org.apache.cassandra.service.accord.AccordJournal;
|
||||||
|
import org.apache.cassandra.service.accord.AccordKeyspace;
|
||||||
|
import org.apache.cassandra.service.accord.JournalKey;
|
||||||
|
import org.apache.cassandra.service.accord.serializers.Version;
|
||||||
|
import org.apache.cassandra.tcm.ClusterMetadataService;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.PathMatcher;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static accord.api.Journal.Load.ALL;
|
||||||
|
import static com.google.common.base.Throwables.getStackTraceAsString;
|
||||||
|
import static org.apache.cassandra.config.DatabaseDescriptor.setAccordJournalDirectory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standalone Accord Journal Util. Useful for inspecting journals on remote nodes in cases of failures
|
||||||
|
* As a convenience, you can build the dtest-jar and scp it to the server, and run the tool:
|
||||||
|
|
||||||
|
java --add-exports java.base/jdk.internal.misc=ALL-UNNAMED \
|
||||||
|
--add-exports java.base/jdk.internal.ref=ALL-UNNAMED \
|
||||||
|
--add-exports java.base/sun.nio.ch=ALL-UNNAMED \
|
||||||
|
--add-exports java.management.rmi/com.sun.jmx.remote.internal.rmi=ALL-UNNAMED \
|
||||||
|
--add-exports java.rmi/sun.rmi.registry=ALL-UNNAMED \
|
||||||
|
--add-exports java.rmi/sun.rmi.server=ALL-UNNAMED \
|
||||||
|
--add-exports java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED \
|
||||||
|
--add-exports java.sql/java.sql=ALL-UNNAMED \
|
||||||
|
\
|
||||||
|
--add-opens java.base/java.lang.module=ALL-UNNAMED \
|
||||||
|
--add-opens java.base/java.net=ALL-UNNAMED \
|
||||||
|
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED \
|
||||||
|
--add-opens java.base/jdk.internal.ref=ALL-UNNAMED \
|
||||||
|
--add-opens java.base/jdk.internal.reflect=ALL-UNNAMED \
|
||||||
|
--add-opens java.base/jdk.internal.math=ALL-UNNAMED \
|
||||||
|
--add-opens java.base/jdk.internal.module=ALL-UNNAMED \
|
||||||
|
--add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED \
|
||||||
|
--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED \
|
||||||
|
--add-opens jdk.management/com.sun.beans.introspect=ALL-UNNAMED \
|
||||||
|
--add-opens jdk.management.jfr/jdk.management.jfr=ALL-UNNAMED \
|
||||||
|
--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED \
|
||||||
|
-cp ./dtest-5.1.jar \
|
||||||
|
org.apache.cassandra.tools.StandaloneJournalUtil \
|
||||||
|
dump_journal --construct --skip-errors --sstables /tmp/journals/journal-c3412dbac2923051b7914f7b0b79a166/ --journal-segments /tmp/journals/accord_journal/
|
||||||
|
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
public class StandaloneJournalUtil
|
||||||
|
{
|
||||||
|
private static final Output output = Output.CONSOLE;
|
||||||
|
|
||||||
|
public static void main(String... args)
|
||||||
|
{
|
||||||
|
Util.initDatabaseDescriptor();
|
||||||
|
DatabaseDescriptor.setPartitioner("org.apache.cassandra.dht.Murmur3Partitioner");
|
||||||
|
AccordKeyspace.TABLES = Tables.of(AccordKeyspace.journalMetadata("journal", false));
|
||||||
|
ClusterMetadataService.empty(Keyspaces.of(AccordKeyspace.metadata()));
|
||||||
|
Cli.CliBuilder<Runnable> builder = Cli.builder("util");
|
||||||
|
|
||||||
|
builder.withDescription("Dump journal").withCommand(DumpSegments.class).withCommand(DumpJournal.class);
|
||||||
|
|
||||||
|
Cli<Runnable> parser = builder.build();
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Runnable parse = parser.parse(args);
|
||||||
|
parse.run();
|
||||||
|
}
|
||||||
|
catch (Throwable throwable)
|
||||||
|
{
|
||||||
|
err(throwable);
|
||||||
|
status = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.exit(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void err(Throwable e)
|
||||||
|
{
|
||||||
|
output.err.println("error: " + e.getMessage());
|
||||||
|
output.err.println("-- StackTrace --");
|
||||||
|
output.err.println(getStackTraceAsString(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(name = "dump_segments", description = "Dump journal segments")
|
||||||
|
public static class DumpSegments implements Runnable
|
||||||
|
{
|
||||||
|
@Option(name = {"-d", "--dir"}, description = "Directory to find journal segments")
|
||||||
|
public String dir;
|
||||||
|
|
||||||
|
@Option(name = {"-p", "--pattern"}, description = "Kind to filter by")
|
||||||
|
public String pattern;
|
||||||
|
|
||||||
|
@Option(name = {"-k", "--kind"}, description = "Kind to filter by")
|
||||||
|
public String kind;
|
||||||
|
|
||||||
|
@Option(name = {"-t", "--txnid"}, description = "Transaction id to filter by")
|
||||||
|
public String txnId;
|
||||||
|
|
||||||
|
@Option(name = {"-m", "--metadata-only"}, description = "Only dump metadata file contents")
|
||||||
|
public boolean metadataOnly;
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Files.list(Path.of(dir)).filter(Files::isRegularFile).filter(p -> matcher.matches(p.getFileName())).sorted().forEach(path -> {
|
||||||
|
Descriptor descriptor = Descriptor.fromFile(new File(path));
|
||||||
|
DumpUtil.dumpMetadata(descriptor, output.out::println);
|
||||||
|
if (metadataOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StaticSegment<JournalKey, Object> segment = DumpUtil.open(descriptor, JournalKey.SUPPORT);
|
||||||
|
segment.forEachRecord((segment1, position, key, buffer, userVersion) -> {
|
||||||
|
if (kind != null && key.type != JournalKey.Type.valueOf(kind))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (txnId != null && !TxnId.fromString(txnId).equals(key.id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try (DataInputBuffer in = new DataInputBuffer(buffer, false))
|
||||||
|
{
|
||||||
|
Object v = key.type.serializer.deserialize(key, in, Version.V1);
|
||||||
|
output.out.printf("%s: %s%n", key, v);
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
t.printStackTrace(output.err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command(name = "dump_journal", description = "Dump journal")
|
||||||
|
public static class DumpJournal implements Runnable
|
||||||
|
{
|
||||||
|
@Option(name = {"-s", "--sstables"}, description = "Path to sstables")
|
||||||
|
public String sstables;
|
||||||
|
|
||||||
|
@Option(name = {"-j", "--journal-segments"}, description = "Path to journal segments")
|
||||||
|
public String journalSegments;
|
||||||
|
|
||||||
|
@Option(name = {"-k", "--kind"}, description = "Kind to filter by")
|
||||||
|
public String kind;
|
||||||
|
|
||||||
|
@Option(name = {"-t", "--txnid"}, description = "Transaction id to filter by")
|
||||||
|
public String txnId;
|
||||||
|
|
||||||
|
@Option(name = {"--since"}, description = "Filter transactions since this timestamp (inclusive)")
|
||||||
|
public String since;
|
||||||
|
|
||||||
|
@Option(name = {"--until"}, description = "Filter transactions until this timestamp (inclusive)")
|
||||||
|
public String until;
|
||||||
|
|
||||||
|
@Option(name = {"-e", "--skip-errors"}, description = "Skip errors: 'true' to skip all, or comma-separated exception class names to skip specific types")
|
||||||
|
public String skipErrors;
|
||||||
|
|
||||||
|
@Option(name = {"-c", "--construct"}, description = "Construct entry")
|
||||||
|
public boolean construct;
|
||||||
|
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
if (sstables == null && journalSegments == null)
|
||||||
|
throw new IllegalArgumentException("Either --sstables or --journal-segments must be provided");
|
||||||
|
|
||||||
|
Timestamp txnId = this.txnId != null ? TxnId.fromString(this.txnId) : null;
|
||||||
|
Timestamp sinceTimestamp = this.since != null ? TxnId.fromString(this.since) : null;
|
||||||
|
Timestamp untilTimestamp = this.until != null ? TxnId.fromString(this.until) : null;
|
||||||
|
|
||||||
|
boolean skipAllErrors;
|
||||||
|
Set<String> skipExceptionTypes = new HashSet<>();
|
||||||
|
|
||||||
|
if (skipErrors != null && !skipErrors.trim().isEmpty())
|
||||||
|
{
|
||||||
|
String trimmed = skipErrors.trim();
|
||||||
|
if ("true".equalsIgnoreCase(trimmed))
|
||||||
|
{
|
||||||
|
skipAllErrors = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skipAllErrors = false;
|
||||||
|
String[] types = trimmed.split(",");
|
||||||
|
for (String type : types)
|
||||||
|
{
|
||||||
|
skipExceptionTypes.add(type.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skipAllErrors = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (journalSegments == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
journalSegments = Files.createTempDirectory("dump_journal").getFileName().toString();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAccordJournalDirectory(journalSegments);
|
||||||
|
Keyspace.setInitialized();
|
||||||
|
AccordJournal journal = new AccordJournal(new AccordSpec.JournalSpec().setFlushPeriod(new DurationSpec.IntMillisecondsBound("1500ms")), new File(journalSegments).parent(), Keyspace.open(SchemaConstants.ACCORD_KEYSPACE_NAME).getColumnFamilyStore(AccordKeyspace.JOURNAL));
|
||||||
|
|
||||||
|
Keyspace ks = Schema.instance.getKeyspaceInstance("system_accord");
|
||||||
|
ColumnFamilyStore cfs = ks.getColumnFamilyStore("journal");
|
||||||
|
if (sstables != null)
|
||||||
|
cfs.importNewSSTables(Collections.singleton(sstables), false, false, false, false, false, false, true);
|
||||||
|
|
||||||
|
Map<Integer, RedundantBefore> cache = new HashMap<>();
|
||||||
|
journal.start(null);
|
||||||
|
journal.forEach(key -> processKey(cache, journal, key, txnId, sinceTimestamp, untilTimestamp, skipAllErrors, skipExceptionTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processKey(Map<Integer, RedundantBefore> redundantBeforeCache, AccordJournal journal, JournalKey key, Timestamp txnId, Timestamp minTimestamp, Timestamp maxTimestamp, boolean skipAllErrors, Set<String> skipExceptionTypes)
|
||||||
|
{
|
||||||
|
if (kind != null && key.type != JournalKey.Type.valueOf(kind))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (txnId != null && !txnId.equals(key.id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Apply since filtering (inclusive)
|
||||||
|
if ((minTimestamp != null && key.id.compareTo(minTimestamp) < 0) ||
|
||||||
|
(maxTimestamp != null && key.id.compareTo(maxTimestamp) > 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (key.type)
|
||||||
|
{
|
||||||
|
case COMMAND_DIFF:
|
||||||
|
{
|
||||||
|
AtomicInteger counter = new AtomicInteger();
|
||||||
|
output.out.println(key + " " + key.id.toStandardString());
|
||||||
|
|
||||||
|
output.out.println("Individual entries:");
|
||||||
|
journal.forEachEntry(key, (in, userVersion) -> {
|
||||||
|
AccordJournal.Builder builder = new AccordJournal.Builder(key.id, ALL);
|
||||||
|
builder.deserializeNext(in, userVersion);
|
||||||
|
output.out.println(String.format("\t%s", builder.toString("\n\t\t")));
|
||||||
|
counter.getAndIncrement();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (construct)
|
||||||
|
{
|
||||||
|
AccordJournal.Builder builder = new AccordJournal.Builder(key.id, ALL);
|
||||||
|
journal.forEachEntry(key, builder::deserializeNext);
|
||||||
|
output.out.println("Reconstructed\n\t\t" + builder.construct(redundantBeforeCache.computeIfAbsent(key.commandStoreId, k -> journal.loadRedundantBefore(key.commandStoreId))));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case REDUNDANT_BEFORE:
|
||||||
|
case DURABLE_BEFORE:
|
||||||
|
case SAFE_TO_READ:
|
||||||
|
case BOOTSTRAP_BEGAN_AT:
|
||||||
|
case RANGES_FOR_EPOCH:
|
||||||
|
case TOPOLOGY_UPDATE:
|
||||||
|
{
|
||||||
|
Object image = journal.readAll(key);
|
||||||
|
output.out.println(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
boolean shouldSkip = false;
|
||||||
|
|
||||||
|
if (skipAllErrors)
|
||||||
|
{
|
||||||
|
shouldSkip = true;
|
||||||
|
}
|
||||||
|
else if (!skipExceptionTypes.isEmpty())
|
||||||
|
{
|
||||||
|
String exceptionClassName = t.getClass().getSimpleName();
|
||||||
|
String fullExceptionClassName = t.getClass().getName();
|
||||||
|
|
||||||
|
shouldSkip = skipExceptionTypes.contains(exceptionClassName) ||
|
||||||
|
skipExceptionTypes.contains(fullExceptionClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldSkip)
|
||||||
|
throw t;
|
||||||
|
|
||||||
|
output.out.println(String.format("Got error reading key %s", key));
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,8 +22,6 @@ import static org.apache.cassandra.utils.btree.BTree.size;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
import accord.utils.Invariants;
|
|
||||||
|
|
||||||
public class FullBTreeSearchIterator<K, V> extends TreeCursor<K> implements BTreeSearchIterator<K, V>
|
public class FullBTreeSearchIterator<K, V> extends TreeCursor<K> implements BTreeSearchIterator<K, V>
|
||||||
{
|
{
|
||||||
private final boolean forwards;
|
private final boolean forwards;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import accord.utils.AccordGens;
|
||||||
import accord.utils.Gen;
|
import accord.utils.Gen;
|
||||||
import accord.utils.Gens;
|
import accord.utils.Gens;
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
import org.agrona.collections.Int2ObjectHashMap;
|
|
||||||
import org.agrona.collections.Long2LongHashMap;
|
import org.agrona.collections.Long2LongHashMap;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.dht.IPartitioner;
|
import org.apache.cassandra.dht.IPartitioner;
|
||||||
|
|
@ -105,4 +104,4 @@ public class WatermarkCollectorTest
|
||||||
return map;
|
return map;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue