diff --git a/CHANGES.txt b/CHANGES.txt index 1b6dcf3c5e..6d234a7055 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.0 + * Add system_views.snapshots virtual table (CASSANDRA-18102) * Update OpenHFT dependencies (chronicle-queue, chronicle-core, chronicle-bytes, chronicle-wire, chronicle-threads) (CASSANDRA-18049) * Remove org.apache.cassandra.hadoop code (CASSANDRA-18323) * Remove deprecated CQL functions dateOf and unixTimestampOf (CASSANDRA-18328) diff --git a/NEWS.txt b/NEWS.txt index c1fe5427f2..48789e4bbe 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -131,6 +131,7 @@ New features `maxTimestamp - minTimestamp`, effectively non-zero for SSTables produced by TimeWindowCompactionStrategy. - Added local read/write ratio to tablestats. - Added system_views.max_sstable_size and system_views.max_sstable_duration tables. + - Added virtual table system_views.snapshots to see all snapshots from CQL shell. Upgrading --------- diff --git a/src/java/org/apache/cassandra/db/virtual/SnapshotsTable.java b/src/java/org/apache/cassandra/db/virtual/SnapshotsTable.java new file mode 100644 index 0000000000..d3df293903 --- /dev/null +++ b/src/java/org/apache/cassandra/db/virtual/SnapshotsTable.java @@ -0,0 +1,82 @@ +/* + * 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.Date; + +import org.apache.cassandra.db.marshal.BooleanType; +import org.apache.cassandra.db.marshal.LongType; +import org.apache.cassandra.db.marshal.TimestampType; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.dht.LocalPartitioner; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.service.snapshot.TableSnapshot; + +public class SnapshotsTable extends AbstractVirtualTable +{ + private static final String NAME = "name"; + private static final String KEYSPACE_NAME = "keyspace_name"; + private static final String TABLE_NAME = "table_name"; + private static final String TRUE_SIZE = "true_size"; + private static final String SIZE_ON_DISK = "size_on_disk"; + private static final String CREATED_AT = "created_at"; + private static final String EXPIRES_AT = "expires_at"; + private static final String EPHEMERAL = "ephemeral"; + + SnapshotsTable(String keyspace) + { + super(TableMetadata.builder(keyspace, "snapshots") + .comment("available snapshots") + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(UTF8Type.instance)) + .addPartitionKeyColumn(NAME, UTF8Type.instance) + .addClusteringColumn(KEYSPACE_NAME, UTF8Type.instance) + .addClusteringColumn(TABLE_NAME, UTF8Type.instance) + .addRegularColumn(TRUE_SIZE, LongType.instance) + .addRegularColumn(SIZE_ON_DISK, LongType.instance) + .addRegularColumn(CREATED_AT, TimestampType.instance) + .addRegularColumn(EXPIRES_AT, TimestampType.instance) + .addRegularColumn(EPHEMERAL, BooleanType.instance) + .build()); + } + + @Override + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + + for (TableSnapshot tableSnapshot : StorageService.instance.snapshotManager.loadSnapshots()) + { + SimpleDataSet row = result.row(tableSnapshot.getTag(), + tableSnapshot.getKeyspaceName(), + tableSnapshot.getTableName()) + .column(TRUE_SIZE, tableSnapshot.computeTrueSizeBytes()) + .column(SIZE_ON_DISK, tableSnapshot.computeSizeOnDiskBytes()) + .column(CREATED_AT, new Date(tableSnapshot.getCreatedAt().toEpochMilli())); + + if (tableSnapshot.isExpiring()) + row.column(EXPIRES_AT, new Date(tableSnapshot.getExpiresAt().toEpochMilli())); + + row.column(EPHEMERAL, tableSnapshot.isEphemeral()); + } + + return result; + } +} diff --git a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java index d2aac53764..12df6c7790 100644 --- a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java +++ b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java @@ -49,6 +49,7 @@ public final class SystemViewsKeyspace extends VirtualKeyspace .add(new GossipInfoTable(VIRTUAL_VIEWS)) .add(new QueriesTable(VIRTUAL_VIEWS)) .add(new LogMessagesTable(VIRTUAL_VIEWS)) + .add(new SnapshotsTable(VIRTUAL_VIEWS)) .addAll(LocalRepairTables.getAll(VIRTUAL_VIEWS)) .build()); } diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index a7c743fd71..d2e3da95c7 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -311,7 +311,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE private final List preShutdownHooks = new ArrayList<>(); private final List postShutdownHooks = new ArrayList<>(); - private final SnapshotManager snapshotManager = new SnapshotManager(); + public final SnapshotManager snapshotManager = new SnapshotManager(); public static final StorageService instance = new StorageService(); diff --git a/test/unit/org/apache/cassandra/db/virtual/SnapshotsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SnapshotsTableTest.java new file mode 100644 index 0000000000..09832309c1 --- /dev/null +++ b/test/unit/org/apache/cassandra/db/virtual/SnapshotsTableTest.java @@ -0,0 +1,105 @@ +/* + * 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.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Date; + +import com.google.common.collect.ImmutableList; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.apache.cassandra.config.DurationSpec; +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.utils.Clock; + +public class SnapshotsTableTest extends CQLTester +{ + private static final String KS_NAME = "vts"; + private static final String SNAPSHOT_TTL = "snapshotTtl"; + private static final String SNAPSHOT_NO_TTL = "snapshotNoTtl"; + private static final DurationSpec.IntSecondsBound ttl = new DurationSpec.IntSecondsBound("4h"); + + @Before + public void before() throws Throwable + { + SnapshotsTable vtable = new SnapshotsTable(KS_NAME); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(vtable))); + + createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))"); + + for (int i = 0; i != 10; ++i) + execute("INSERT INTO %s (pk, ck) VALUES (?, ?)", i, i); + + flush(); + } + + @After + public void after() + { + StorageService.instance.clearSnapshot(Collections.emptyMap(), SNAPSHOT_NO_TTL, KEYSPACE); + StorageService.instance.clearSnapshot(Collections.emptyMap(), SNAPSHOT_TTL, KEYSPACE); + + schemaChange(String.format("DROP TABLE %s", KEYSPACE + "." + currentTable())); + } + + @Test + public void testSnapshots() throws Throwable + { + Instant now = Instant.ofEpochMilli(Clock.Global.currentTimeMillis()).truncatedTo(ChronoUnit.MILLIS); + Date createdAt = new Date(now.toEpochMilli()); + Date expiresAt = new Date(now.plusSeconds(ttl.toSeconds()).toEpochMilli()); + + getCurrentColumnFamilyStore(KEYSPACE).snapshot(SNAPSHOT_NO_TTL, null, false, false, null, null, now); + getCurrentColumnFamilyStore(KEYSPACE).snapshot(SNAPSHOT_TTL, null, false, false, ttl, null, now); + + // query all from snapshots virtual table + UntypedResultSet result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots"); + assertRowsIgnoringOrder(result, + row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), createdAt, null, false), + row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); + + // query with conditions + result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where ephemeral = false"); + assertRows(result, + row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), createdAt, null, false), + row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); + + result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where size_on_disk > 1000"); + assertRows(result, + row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), createdAt, null, false), + row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); + + result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where name = ?", SNAPSHOT_TTL); + assertRows(result, + row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); + + // clear some snapshots + StorageService.instance.clearSnapshot(Collections.emptyMap(), SNAPSHOT_NO_TTL, KEYSPACE); + + result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots"); + assertRowsIgnoringOrder(result, + row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); + } +} \ No newline at end of file