Make incremental backup configurable per table

patch by Maxwell Guo; reviewed by Stefan Miklosovic and Benjamin Lerer for CASSANDRA-15402
This commit is contained in:
maxwellguo 2022-10-27 22:05:49 +08:00 committed by Stefan Miklosovic
parent fb09a5c1bb
commit ee8b66da8c
15 changed files with 232 additions and 6 deletions

View File

@ -1,4 +1,5 @@
4.2
* Make incremental backup configurable per table (CASSANDRA-15402)
* Change shebangs of Python scripts to resolve Python 3 from env command (CASSANDRA-17832)
* Add reasons to guardrail messages and consider guardrails in the error message for needed ALLOW FILTERING (CASSANDRA-17967)
* Add support for CQL functions on collections, tuples and UDTs (CASSANDRA-17811)

View File

@ -102,6 +102,10 @@ New features
--older-than-timestamp 2022-12-03T10:15:30Z.
- Cassandra logs can be viewed in the virtual table system_views.system_logs.
Please uncomment the respective appender in logback.xml file to make logs flow into this table. This feature is turned off by default.
- Added new CQL table property 'incremental_backups' which is by default true. When 'incremental_backups' property in cassandra.yaml
is set to true and table property is set to false, incremental backups for that specific table will not be done.
When 'incremental_backups' in casandra.yaml is set to false, the newly added table property does not have any effect.
Both properties have to be set to true (cassandra.yaml and table property) in order to make incremental backups.
Upgrading
---------
@ -109,7 +113,7 @@ Upgrading
there is a dedicated flag in snapshot manifest instead. On upgrade of a node to version 4.2, on node's start, in case there
are such ephemeral snapshots on disk, they will be deleted (same behaviour as before) and any new ephemeral snapshots
will stop to create ephemeral marker files as flag in a snapshot manifest was introduced instead.
- There was new table property introduced called 'allow_auto_snapshot' (see section 'New features'). Hence, upgraded
- There were new table properties introduced called 'allow_auto_snapshot' and 'incremental_backups' (see section 'New features'). Hence, upgraded
node will be on a new schema version. Please do a rolling upgrade of nodes of a cluster to converge to one schema version.
Deprecation

View File

@ -923,9 +923,11 @@ rpc_keepalive: true
# internode_socket_receive_buffer_size:
# Set to true to have Cassandra create a hard link to each sstable
# flushed or streamed locally in a backups/ subdirectory of the
# keyspace data. Removing these links is the operator's
# responsibility.
# flushed or streamed locally in a backups/ subdirectory of all the
# keyspace data in this node. Removing these links is the operator's
# responsibility. The operator can also turn off incremental backups
# for specified table by setting table parameter incremental_backups to
# false, which is set to true by default. See CASSANDRA-15402
incremental_backups: false
# Whether or not to take a snapshot before each compaction. Be

View File

@ -47,6 +47,7 @@ class Cql3ParsingRuleSet(CqlParsingRuleSet):
('bloom_filter_fp_chance', None),
('comment', None),
('gc_grace_seconds', None),
('incremental_backups', None),
('min_index_interval', None),
('max_index_interval', None),
('default_time_to_live', None),
@ -523,6 +524,8 @@ def cf_prop_val_completer(ctxt, cass):
return [Hint('<\'none\'|\'blocking\'>')]
if this_opt == 'allow_auto_snapshot':
return [Hint('<boolean>')]
if this_opt == 'incremental_backups':
return [Hint('<boolean>')]
return [Hint('<option_value>')]

View File

@ -639,6 +639,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
'bloom_filter_fp_chance', 'compaction',
'compression',
'default_time_to_live', 'gc_grace_seconds',
'incremental_backups',
'max_index_interval',
'memtable',
'memtable_flush_period_in_ms',
@ -650,6 +651,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
'bloom_filter_fp_chance', 'compaction',
'compression',
'default_time_to_live', 'gc_grace_seconds',
'incremental_backups',
'max_index_interval',
'memtable',
'memtable_flush_period_in_ms',
@ -699,6 +701,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
choices=['allow_auto_snapshot', 'bloom_filter_fp_chance', 'compaction',
'compression',
'default_time_to_live', 'gc_grace_seconds',
'incremental_backups',
'max_index_interval',
'memtable',
'memtable_flush_period_in_ms',

View File

@ -664,6 +664,7 @@ class TestCqlshOutput(BaseTestCase):
AND default_time_to_live = 0
AND extensions = {}
AND gc_grace_seconds = 864000
AND incremental_backups = true
AND max_index_interval = 2048
AND memtable_flush_period_in_ms = 0
AND min_index_interval = 128

View File

@ -133,6 +133,9 @@ public final class TableAttributes extends PropertyDefinitions
if (hasOption(GC_GRACE_SECONDS))
builder.gcGraceSeconds(getInt(GC_GRACE_SECONDS));
if (hasOption(INCREMENTAL_BACKUPS))
builder.incrementalBackups(getBoolean(INCREMENTAL_BACKUPS.toString(), true));
if (hasOption(MAX_INDEX_INTERVAL))
builder.maxIndexInterval(getInt(MAX_INDEX_INTERVAL));

View File

@ -3134,6 +3134,11 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return metadata().params.allowAutoSnapshot && DatabaseDescriptor.isAutoSnapshot();
}
public boolean isTableIncrementalBackupsEnabled()
{
return DatabaseDescriptor.isIncrementalBackupsEnabled() && metadata().params.incrementalBackups;
}
/**
* Discard all SSTables that were created before given timestamp.
*

View File

@ -418,7 +418,7 @@ public class Tracker
public void maybeIncrementallyBackup(final Iterable<SSTableReader> sstables)
{
if (!DatabaseDescriptor.isIncrementalBackupsEnabled())
if (!cfstore.isTableIncrementalBackupsEnabled())
return;
for (SSTableReader sstable : sstables)

View File

@ -113,6 +113,7 @@ public final class SchemaKeyspace
+ "extensions frozen<map<text, blob>>,"
+ "flags frozen<set<text>>," // SUPER, COUNTER, DENSE, COMPOUND
+ "gc_grace_seconds int,"
+ "incremental_backups boolean,"
+ "id uuid,"
+ "max_index_interval int,"
+ "memtable_flush_period_in_ms int,"
@ -181,6 +182,7 @@ public final class SchemaKeyspace
+ "default_time_to_live int,"
+ "extensions frozen<map<text, blob>>,"
+ "gc_grace_seconds int,"
+ "incremental_backups boolean,"
+ "id uuid,"
+ "include_all_columns boolean,"
+ "max_index_interval int,"
@ -570,6 +572,11 @@ public final class SchemaKeyspace
// auto-snapshotting is enabled, to avoid RTE in pre-4.2 versioned node during upgrades
if (!params.allowAutoSnapshot)
builder.add("allow_auto_snapshot", false);
// As above, only add the incremental_backups column if the value is not default (true) and
// incremental_backups is enabled, to avoid RTE in pre-4.2 versioned node during upgrades
if (!params.incrementalBackups)
builder.add("incremental_backups", false);
}
private static void addAlterTableToSchemaMutation(TableMetadata oldTable, TableMetadata newTable, Mutation.SimpleBuilder builder)
@ -988,6 +995,10 @@ public final class SchemaKeyspace
if (row.has("allow_auto_snapshot"))
builder.allowAutoSnapshot(row.getBoolean("allow_auto_snapshot"));
// incremental_backups column was introduced in 4.2
if (row.has("incremental_backups"))
builder.incrementalBackups(row.getBoolean("incremental_backups"));
return builder.build();
}

View File

@ -263,6 +263,11 @@ public class TableMetadata implements SchemaElement
{
return false;
}
public boolean isIncrementalBackupsEnabled()
{
return params.incrementalBackups;
}
public boolean isStaticCompactTable()
{

View File

@ -52,6 +52,7 @@ public final class TableParams
DEFAULT_TIME_TO_LIVE,
EXTENSIONS,
GC_GRACE_SECONDS,
INCREMENTAL_BACKUPS,
MAX_INDEX_INTERVAL,
MEMTABLE_FLUSH_PERIOD_IN_MS,
MIN_INDEX_INTERVAL,
@ -73,6 +74,7 @@ public final class TableParams
public final double bloomFilterFpChance;
public final double crcCheckChance;
public final int gcGraceSeconds;
public final boolean incrementalBackups;
public final int defaultTimeToLive;
public final int memtableFlushPeriodInMs;
public final int minIndexInterval;
@ -96,6 +98,7 @@ public final class TableParams
: builder.bloomFilterFpChance;
crcCheckChance = builder.crcCheckChance;
gcGraceSeconds = builder.gcGraceSeconds;
incrementalBackups = builder.incrementalBackups;
defaultTimeToLive = builder.defaultTimeToLive;
memtableFlushPeriodInMs = builder.memtableFlushPeriodInMs;
minIndexInterval = builder.minIndexInterval;
@ -128,6 +131,7 @@ public final class TableParams
.crcCheckChance(params.crcCheckChance)
.defaultTimeToLive(params.defaultTimeToLive)
.gcGraceSeconds(params.gcGraceSeconds)
.incrementalBackups(params.incrementalBackups)
.maxIndexInterval(params.maxIndexInterval)
.memtableFlushPeriodInMs(params.memtableFlushPeriodInMs)
.minIndexInterval(params.minIndexInterval)
@ -213,7 +217,8 @@ public final class TableParams
&& allowAutoSnapshot == p.allowAutoSnapshot
&& bloomFilterFpChance == p.bloomFilterFpChance
&& crcCheckChance == p.crcCheckChance
&& gcGraceSeconds == p.gcGraceSeconds
&& gcGraceSeconds == p.gcGraceSeconds
&& incrementalBackups == p.incrementalBackups
&& defaultTimeToLive == p.defaultTimeToLive
&& memtableFlushPeriodInMs == p.memtableFlushPeriodInMs
&& minIndexInterval == p.minIndexInterval
@ -237,6 +242,7 @@ public final class TableParams
bloomFilterFpChance,
crcCheckChance,
gcGraceSeconds,
incrementalBackups,
defaultTimeToLive,
memtableFlushPeriodInMs,
minIndexInterval,
@ -262,6 +268,7 @@ public final class TableParams
.add(CRC_CHECK_CHANCE.toString(), crcCheckChance)
.add(GC_GRACE_SECONDS.toString(), gcGraceSeconds)
.add(DEFAULT_TIME_TO_LIVE.toString(), defaultTimeToLive)
.add(INCREMENTAL_BACKUPS.toString(), incrementalBackups)
.add(MEMTABLE_FLUSH_PERIOD_IN_MS.toString(), memtableFlushPeriodInMs)
.add(MIN_INDEX_INTERVAL.toString(), minIndexInterval)
.add(MAX_INDEX_INTERVAL.toString(), maxIndexInterval)
@ -314,6 +321,8 @@ public final class TableParams
.newLine()
.append("AND gc_grace_seconds = ").append(gcGraceSeconds)
.newLine()
.append("AND incremental_backups = ").append(incrementalBackups)
.newLine()
.append("AND max_index_interval = ").append(maxIndexInterval)
.newLine()
.append("AND memtable_flush_period_in_ms = ").append(memtableFlushPeriodInMs)
@ -332,6 +341,7 @@ public final class TableParams
private double bloomFilterFpChance = -1;
private double crcCheckChance = 1.0;
private int gcGraceSeconds = 864000; // 10 days
private boolean incrementalBackups = true;
private int defaultTimeToLive = 0;
private int memtableFlushPeriodInMs = 0;
private int minIndexInterval = 128;
@ -385,6 +395,12 @@ public final class TableParams
return this;
}
public Builder incrementalBackups(boolean val)
{
incrementalBackups = val;
return this;
}
public Builder defaultTimeToLive(int val)
{
defaultTimeToLive = val;

View File

@ -0,0 +1,169 @@
/*
* 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.distributed.test;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SequenceBasedSSTableId;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.cassandra.Util.getBackups;
import static org.apache.cassandra.distributed.Cluster.build;
import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL;
import static org.apache.cassandra.distributed.test.ExecUtil.rethrow;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class TableLevelIncrementalBackupsTest extends TestBaseImpl
{
@Test
public void testIncrementalBackupEnabledCreateTable() throws Exception
{
try (Cluster c = getCluster(true))
{
c.schemaChange(withKeyspace("CREATE TABLE %s.test_table (a text primary key, b int) WITH incremental_backups = true"));
disableCompaction(c, KEYSPACE, "test_table");
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 1, true, KEYSPACE, "test_table");
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 2, true, KEYSPACE, "test_table");
c.schemaChange(withKeyspace("DROP TABLE %s.test_table"));
c.schemaChange(withKeyspace("CREATE TABLE %s.test_table2 (a text primary key, b int) WITH incremental_backups = false"));
disableCompaction(c, KEYSPACE, "test_table2");
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table2 (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 0, false, KEYSPACE, "test_table2");
c.schemaChange(withKeyspace("DROP TABLE %s.test_table2"));
}
}
@Test
public void testIncrementalBackupEnabledAlterTable() throws Exception
{
try (Cluster c = getCluster(true))
{
c.schemaChange(withKeyspace("CREATE TABLE %s.test_table (a text primary key, b int) WITH incremental_backups = false"));
disableCompaction(c, KEYSPACE, "test_table");
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 0, false, KEYSPACE, "test_table");
c.schemaChange(withKeyspace("ALTER TABLE %s.test_table WITH incremental_backups = true"));
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 1, true, KEYSPACE, "test_table");
c.schemaChange(withKeyspace("DROP TABLE %s.test_table"));
c.schemaChange(withKeyspace("CREATE TABLE %s.test_table2 (a text primary key, b int) WITH incremental_backups = true"));
disableCompaction(c, KEYSPACE, "test_table2");
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table2 (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 1, true, KEYSPACE, "test_table2");
c.schemaChange(withKeyspace("ALTER TABLE %s.test_table2 WITH incremental_backups = false"));
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table2 (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 1, false, KEYSPACE, "test_table2");
c.schemaChange(withKeyspace("DROP TABLE %s.test_table2"));
}
}
@Test
public void testIncrementalBackupWhenCreateTableByDefault() throws Exception
{
try (Cluster c = getCluster(true))
{
//incremental_backups is set to true by default
c.schemaChange(withKeyspace("CREATE TABLE %s.test_table (a text primary key, b int)"));
disableCompaction(c, KEYSPACE, "test_table");
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 1, true, KEYSPACE, "test_table");
c.schemaChange(withKeyspace("ALTER TABLE %s.test_table WITH incremental_backups = false"));
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 1, false, KEYSPACE, "test_table");
c.schemaChange(withKeyspace("ALTER TABLE %s.test_table WITH incremental_backups = true"));
c.coordinator(1).execute(withKeyspace("INSERT INTO %s.test_table (a, b) VALUES ('a', 1)"), ALL);
flush(c, KEYSPACE);
assertBackupSSTablesCount(c, 2, true, KEYSPACE, "test_table");
c.schemaChange(withKeyspace("DROP TABLE %s.test_table"));
}
}
private Cluster getCluster(boolean incrementalBackups) throws IOException
{
return init(build(2).withDataDirCount(1).withConfig(c -> c.with(Feature.GOSSIP)
.set("incremental_backups", incrementalBackups)).start());
}
private void flush(Cluster cluster, String keyspace)
{
for (int i = 1; i < cluster.size() + 1; i++)
cluster.get(i).flush(keyspace);
}
private void disableCompaction(Cluster cluster, String keyspace, String table)
{
for (int i = 1; i < cluster.size() + 1; i++)
cluster.get(i).nodetool("disableautocompaction", keyspace, table);
}
private static void assertBackupSSTablesCount(Cluster cluster, int expectedSeqGenIds, boolean enable, String ks, String... tableNames)
{
for (int i = 1; i < cluster.size() + 1; i++)
{
cluster.get(i).runOnInstance(rethrow(() -> Arrays.stream(tableNames).forEach(tableName -> assertTableMetaIncrementalBackupEnable(ks, tableName, enable))));
cluster.get(i).runOnInstance(rethrow(() -> Arrays.stream(tableNames).forEach(tableName -> assertSSTablesCount(getBackups(ks, tableName), tableName, expectedSeqGenIds))));
}
}
private static void assertSSTablesCount(Set<Descriptor> descs, String tableName, int expectedSeqGenIds)
{
List<String> seqSSTables = descs.stream().filter(desc -> desc.id instanceof SequenceBasedSSTableId).map(Descriptor::baseFilename).sorted().collect(Collectors.toList());
assertThat(seqSSTables).describedAs("SSTables of %s with sequence based id", tableName).hasSize(expectedSeqGenIds);
}
private static void assertTableMetaIncrementalBackupEnable(String ks, String tableName, boolean enable)
{
ColumnFamilyStore columnFamilyStore = ColumnFamilyStore.getIfExists(ks, tableName);
if (enable)
assertTrue(columnFamilyStore.isTableIncrementalBackupsEnabled());
else
assertFalse(columnFamilyStore.isTableIncrementalBackupsEnabled());
}
}

View File

@ -851,6 +851,7 @@ public class DescribeStatementTest extends CQLTester
" AND default_time_to_live = 0\n" +
" AND extensions = {}\n" +
" AND gc_grace_seconds = 864000\n" +
" AND incremental_backups = true\n" +
" AND max_index_interval = 2048\n" +
" AND memtable_flush_period_in_ms = 0\n" +
" AND min_index_interval = 128\n" +
@ -872,6 +873,7 @@ public class DescribeStatementTest extends CQLTester
" AND crc_check_chance = 1.0\n" +
" AND extensions = {}\n" +
" AND gc_grace_seconds = 864000\n" +
" AND incremental_backups = true\n" +
" AND max_index_interval = 2048\n" +
" AND memtable_flush_period_in_ms = 0\n" +
" AND min_index_interval = 128\n" +

View File

@ -309,6 +309,7 @@ public class SchemaCQLHelperTest extends CQLTester
" AND default_time_to_live = 4\n" +
" AND extensions = {'ext1': 0x76616c31}\n" +
" AND gc_grace_seconds = 5\n" +
" AND incremental_backups = true\n" +
" AND max_index_interval = 7\n" +
" AND memtable_flush_period_in_ms = 8\n" +
" AND min_index_interval = 6\n" +