org.apache.cassandra.db.virtual.LocalRepairTables Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-all Show documentation
Show all versions of cassandra-all Show documentation
The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.
/*
* 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.db.virtual;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.TimeUUIDType;
import org.apache.cassandra.db.marshal.UUIDType;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.repair.CommonRange;
import org.apache.cassandra.repair.RepairJobDesc;
import org.apache.cassandra.repair.state.Completable;
import org.apache.cassandra.repair.state.CoordinatorState;
import org.apache.cassandra.repair.state.JobState;
import org.apache.cassandra.repair.state.ParticipateState;
import org.apache.cassandra.repair.state.SessionState;
import org.apache.cassandra.repair.state.State;
import org.apache.cassandra.repair.state.ValidationState;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.utils.TimeUUID;
public class LocalRepairTables
{
private LocalRepairTables()
{
}
public static Collection getAll(String keyspace)
{
return Arrays.asList(
new RepairTable(keyspace),
new SessionTable(keyspace),
new JobTable(keyspace),
new ParticipateTable(keyspace),
new ValidationTable(keyspace)
);
}
private static final String JOB_DESC_COLUMNS = " repair_id timeuuid,\n" +
" session_id timeuuid,\n" +
" keyspace_name text,\n" +
" table_name text,\n" +
" ranges frozen>,\n";
static final class RepairTable extends AbstractVirtualTable
{
protected RepairTable(String keyspace)
{
super(parse(keyspace, "Repair summary",
"CREATE TABLE repairs (\n" +
stdColumnsWithStatus(true) +
" command_id int,\n" +
" keyspace_name text,\n" +
// human readable definition of what the repair is doing
" type text,\n" +
// list of all sessions; this is lazy so only once the session is created will it be present, so this dynamically changes within the life of a repair
" sessions frozen>,\n" +
// options_ maps to RepairOption
" options_parallelism text,\n" +
" options_primary_range boolean,\n" +
" options_incremental boolean,\n" +
" options_trace boolean,\n" +
" options_job_threads int,\n" +
" options_subrange_repair boolean,\n" +
" options_pull_repair boolean,\n" +
" options_force_repair boolean,\n" +
" options_preview_kind text,\n" +
" options_optimise_streams boolean,\n" +
" options_ignore_unreplicated_keyspaces boolean,\n" +
" options_column_families frozen>,\n" +
" options_data_centers frozen>,\n" +
" options_hosts frozen>,\n" +
" options_ranges frozen>,\n" +
" table_names frozen>,\n" +
" ranges frozen>>,\n" +
" unfiltered_ranges frozen>>,\n" +
" participants frozen>,\n" +
stateColumns(CoordinatorState.State.class) +
"\n" +
" PRIMARY KEY ( (id) )\n" +
")"));
}
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
ActiveRepairService.instance.coordinators().forEach(s -> updateDataset(result, s));
return result;
}
public DataSet data(DecoratedKey partitionKey)
{
TimeUUID id = TimeUUIDType.instance.compose(partitionKey.getKey());
SimpleDataSet result = new SimpleDataSet(metadata());
CoordinatorState state = ActiveRepairService.instance.coordinator(id);
if (state != null)
updateDataset(result, state);
return result;
}
private void updateDataset(SimpleDataSet result, CoordinatorState state)
{
result.row(state.id);
addState(result, state);
result.column("type", getType(state));
result.column("keyspace_name", state.keyspace);
result.column("command_id", state.cmd);
result.column("options_parallelism", state.options.getParallelism().name());
result.column("options_primary_range", state.options.isPrimaryRange());
result.column("options_trace", state.options.isTraced());
result.column("options_job_threads", state.options.getJobThreads());
result.column("options_subrange_repair", state.options.isSubrangeRepair());
result.column("options_pull_repair", state.options.isPullRepair());
result.column("options_force_repair", state.options.isForcedRepair());
result.column("options_preview_kind", state.options.getPreviewKind().name());
result.column("options_optimise_streams", state.options.optimiseStreams());
result.column("options_ignore_unreplicated_keyspaces", state.options.ignoreUnreplicatedKeyspaces());
result.column("options_column_families", state.options.getColumnFamilies());
result.column("options_data_centers", state.options.getDataCenters());
result.column("options_hosts", state.options.getHosts());
result.column("options_ranges", toStringSet(state.options.getRanges()));
result.column("sessions", state.getSessionIds());
String[] columnFamilyNames = state.getColumnFamilyNames();
result.column("table_names", columnFamilyNames == null ? null : Arrays.asList(columnFamilyNames));
Set participants = state.getParticipants();
result.column("participants", participants == null ? null : toStringList(participants));
List ranges = state.getFilteredCommonRanges();
result.column("ranges", ranges == null ? null : ranges.stream().map(c -> c.ranges).map(LocalRepairTables::toStringList).collect(Collectors.toList()));
ranges = state.getCommonRanges();
result.column("unfiltered_ranges", ranges == null ? null : ranges.stream().map(c -> c.ranges).map(LocalRepairTables::toStringList).collect(Collectors.toList()));
}
private String getType(CoordinatorState state)
{
if (state.options.isPreview())
{
switch (state.options.getPreviewKind())
{
case ALL: return "preview full";
case REPAIRED: return "preview repaired";
case UNREPAIRED: return "preview unrepaired";
case NONE: throw new AssertionError("NONE preview kind not expected when preview repair is set");
default: throw new AssertionError("Unknown preview kind: " + state.options.getPreviewKind());
}
}
else if (state.options.isIncremental())
{
return "incremental";
}
return "full";
}
}
private static final class SessionTable extends AbstractVirtualTable
{
SessionTable(String keyspace)
{
super(parse(keyspace, "Repair session",
"CREATE TABLE repair_sessions (\n" +
stdColumnsWithStatus(true) +
" repair_id timeuuid,\n" +
" keyspace_name text,\n" +
" table_names frozen>,\n" +
" ranges frozen>,\n" +
" participants frozen>,\n" +
" jobs frozen>,\n" +
"\n" +
stateColumns(SessionState.State.class) +
"\n" +
" PRIMARY KEY ( (id) )\n" +
")"));
}
@Override
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
ActiveRepairService.instance.coordinators().stream()
.flatMap(s -> s.getSessions().stream())
.forEach(s -> updateDataset(result, s));
return result;
}
private void updateDataset(SimpleDataSet result, SessionState state)
{
result.row(state.id);
addState(result, state);
result.column("repair_id", state.parentRepairSession);
result.column("keyspace_name", state.keyspace);
result.column("table_names", Arrays.asList(state.cfnames));
result.column("ranges", toStringList(state.commonRange.ranges));
result.column("jobs", state.getJobIds());
result.column("participants", toStringList(state.getParticipants()));
}
}
private static final class JobTable extends AbstractVirtualTable
{
JobTable(String keyspace)
{
super(parse(keyspace, "Repair job",
"CREATE TABLE repair_jobs (\n" +
stdColumnsWithStatus(false) +
" participants frozen>,\n" +
JOB_DESC_COLUMNS +
"\n" +
stateColumns(JobState.State.class) +
"\n" +
" PRIMARY KEY ( (id) )\n" +
")"));
}
@Override
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
ActiveRepairService.instance.coordinators().stream()
.flatMap(s -> s.getSessions().stream())
.flatMap(s -> s.getJobs().stream())
.forEach(s -> updateDataset(result, s));
return result;
}
private void updateDataset(SimpleDataSet result, JobState state)
{
result.row(state.id);
addState(result, state);
addState(result, state.desc);
result.column("participants", toStringList(state.getParticipants()));
}
}
static final class ParticipateTable extends AbstractVirtualTable
{
protected ParticipateTable(String keyspace)
{
super(parse(keyspace, "Repair participate summary",
"CREATE TABLE repair_participates (" +
stdColumns(true) +
" initiator text,\n" +
" tables frozen>, \n" +
" ranges frozen>,\n" +
" incremental boolean,\n" +
" global boolean,\n" +
" preview_kind text,\n" +
" repaired_at timestamp,\n" +
" validations frozen>,\n" +
"\n" +
" PRIMARY KEY ( (id) )\n" +
")"));
}
@Override
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
ActiveRepairService.instance.participates().stream()
.forEach(s -> updateDataset(result, s));
return result;
}
@Override
public DataSet data(DecoratedKey partitionKey)
{
TimeUUID id = TimeUUIDType.instance.compose(partitionKey.getKey());
SimpleDataSet result = new SimpleDataSet(metadata());
ParticipateState state = ActiveRepairService.instance.participate(id);
if (state != null)
updateDataset(result, state);
return result;
}
private void updateDataset(SimpleDataSet result, ParticipateState state)
{
result.row(state.id);
addCompletableState(result, state);
result.column("initiator", state.initiator.toString());
result.column("tables", state.tableIds.stream()
.map(Schema.instance::getTableMetadata)
.filter(a -> a != null) // getTableMetadata returns null if id isn't know, most likely dropped
.map(Object::toString)
.collect(Collectors.toSet()));
result.column("incremental", state.incremental);
result.column("global", state.global);
result.column("preview_kind", state.previewKind.name());
if (state.repairedAt != 0)
result.column("repaired_at", new Date(state.repairedAt));
result.column("validations", state.validationIds());
result.column("ranges", toStringList(state.ranges));
}
}
private static final class ValidationTable extends AbstractVirtualTable
{
ValidationTable(String keyspace)
{
super(parse(keyspace, "Repair validation",
"CREATE TABLE repair_validations (\n" +
stdColumnsWithStatus(false) +
JOB_DESC_COLUMNS +
" initiator text,\n" +
" estimated_partitions bigint,\n" +
" estimated_total_bytes bigint,\n" +
" partitions_processed bigint,\n" +
" bytes_read bigint,\n" +
" progress_percentage float,\n" +
"\n" +
stateColumns(ValidationState.State.class) +
"\n" +
" PRIMARY KEY ( (id) )\n" +
")"));
}
@Override
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
ActiveRepairService.instance.validations().stream()
.forEach(s -> updateDataset(result, s));
return result;
}
@Override
public DataSet data(DecoratedKey partitionKey)
{
UUID id = UUIDType.instance.compose(partitionKey.getKey());
SimpleDataSet result = new SimpleDataSet(metadata());
ValidationState state = ActiveRepairService.instance.validation(id);
if (state != null)
updateDataset(result, state);
return result;
}
private void updateDataset(SimpleDataSet result, ValidationState state)
{
result.row(state.id);
addState(result, state);
addState(result, state.desc);
result.column("initiator", state.initiator.toString());
result.column("estimated_partitions", state.estimatedPartitions == 0 ? null : state.estimatedPartitions);
result.column("estimated_total_bytes", state.estimatedTotalBytes == 0 ? null : state.estimatedTotalBytes);
result.column("partitions_processed", state.partitionsProcessed == 0 ? null : state.partitionsProcessed);
result.column("progress_percentage", round(state.getProgress() * 100));
result.column("bytes_read", state.bytesRead);
}
}
private static String timestampColumnName(Enum> e)
{
return timestampColumnName(e.name().toLowerCase());
}
private static String timestampColumnName(String e)
{
return "state_" + e + "_timestamp";
}
private static String stateColumns(Class extends Enum>> klass)
{
StringBuilder sb = new StringBuilder();
for (Enum> e : klass.getEnumConstants())
sb.append(" ").append(timestampColumnName(e)).append(" timestamp, \n");
return sb.toString();
}
private static String stdStateColumns()
{
StringBuilder sb = new StringBuilder();
sb.append(" ").append(timestampColumnName("init")).append(" timestamp, \n");
for (State.Result.Kind kind : State.Result.Kind.values())
sb.append(" ").append(timestampColumnName(kind)).append(" timestamp, \n");
return sb.toString();
}
private static void addCompletableState(SimpleDataSet ds, Completable> state)
{
// read timestamp early to see latest data
ds.column("last_updated_at", new Date(state.getLastUpdatedAtMillis()));
ds.column("duration_millis", state.getDurationMillis());
State.Result result = state.getResult();
ds.column("failure_cause", state.getFailureCause());
ds.column("success_message", state.getSuccessMessage());
ds.column(timestampColumnName("init"), new Date(state.getInitializedAtMillis()));
ds.column("completed", result != null);
if (result != null)
ds.column(timestampColumnName(result.kind), new Date(state.getLastUpdatedAtMillis()));
}
private static > void addState(SimpleDataSet ds, State state)
{
addCompletableState(ds, state);
T currentState = state.getStatus();
State.Result result = state.getResult();
ds.column("status", result != null ? result.kind.name().toLowerCase() : currentState == null ? "init" : currentState.name().toLowerCase());
for (Map.Entry e : state.getStateTimesMillis().entrySet())
{
if (e.getValue().longValue() != 0)
ds.column(timestampColumnName(e.getKey()), new Date(e.getValue()));
}
}
@VisibleForTesting
static float round(float value)
{
return Math.round(value * 100.0F) / 100.0F;
}
private static void addState(SimpleDataSet result, RepairJobDesc desc)
{
result.column("repair_id", desc.parentSessionId);
result.column("session_id", desc.sessionId);
result.column("keyspace_name", desc.keyspace);
result.column("table_name", desc.columnFamily);
result.column("ranges", toStringList(desc.ranges));
}
private static List toStringList(Collection list)
{
if (list == null)
return null;
return list.stream().map(Object::toString).collect(Collectors.toList());
}
private static Set toStringSet(Collection list)
{
if (list == null)
return null;
return list.stream().map(Object::toString).collect(Collectors.toSet());
}
private static TableMetadata parse(String keyspace, String comment, String query)
{
return CreateTableStatement.parse(query, keyspace)
.comment(comment)
.kind(TableMetadata.Kind.VIRTUAL)
.partitioner(new LocalPartitioner(UUIDType.instance))
.build();
}
private static String stdColumns(boolean time)
{
String str = " id timeuuid,\n" +
" last_updated_at timestamp,\n" +
" completed boolean,\n" +
" duration_millis bigint,\n" +
" failure_cause text,\n" +
" success_message text,\n";
if (!time)
str = str.replace("id timeuuid", "id uuid");
return str + stdStateColumns();
}
private static String stdColumnsWithStatus(boolean time)
{
return stdColumns(time) + " status text,\n";
}
}