From 0393c306331e6b46182b0693ff192be13d20698e Mon Sep 17 00:00:00 2001 From: Aleksey Yeschenko Date: Fri, 10 Oct 2014 17:32:58 +0300 Subject: [PATCH] Fix merging schemas with re-dropped keyspaces patch by Aleksey Yeschenko; reviewed by Pavel Yaskevich for CASSANDRA-7256 --- CHANGES.txt | 1 + .../org/apache/cassandra/db/DefsTables.java | 198 ++++++++---------- 2 files changed, 84 insertions(+), 115 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bc12402789..b633e48a95 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.0.11: + * Fix merging schemas with re-dropped keyspaces (CASSANDRA-7256) * Fix counters in supercolumns during live upgrades from 1.2 (CASSANDRA-7188) * Notify DT subscribers when a column family is truncated (CASSANDRA-8088) * Add sanity check of $JAVA on startup (CASSANDRA-7676) diff --git a/src/java/org/apache/cassandra/db/DefsTables.java b/src/java/org/apache/cassandra/db/DefsTables.java index bc2b36d8fe..35eecc0991 100644 --- a/src/java/org/apache/cassandra/db/DefsTables.java +++ b/src/java/org/apache/cassandra/db/DefsTables.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.*; +import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; @@ -189,135 +190,102 @@ public class DefsTables Schema.instance.updateVersionAndAnnounce(); } - private static Set mergeKeyspaces(Map old, Map updated) + private static Set mergeKeyspaces(Map before, Map after) { - // calculate the difference between old and new states (note that entriesOnlyLeft() will be always empty) - MapDifference diff = Maps.difference(old, updated); + List created = new ArrayList<>(); + List altered = new ArrayList<>(); + Set dropped = new HashSet<>(); - /** - * At first step we check if any new keyspaces were added. + /* + * - we don't care about entriesOnlyOnLeft() or entriesInCommon(), because only the changes are of interest to us + * - of all entriesOnlyOnRight(), we only care about ones that have live columns; it's possible to have a ColumnFamily + * there that only has the top-level deletion, if: + * a) a pushed DROP KEYSPACE change for a keyspace hadn't ever made it to this node in the first place + * b) a pulled dropped keyspace that got dropped before it could find a way to this node + * - of entriesDiffering(), we don't care about the scenario where both pre and post-values have zero live columns: + * that means that a keyspace had been recreated and dropped, and the recreated keyspace had never found a way + * to this node */ + MapDifference diff = Maps.difference(before, after); + for (Map.Entry entry : diff.entriesOnlyOnRight().entrySet()) - { - ColumnFamily ksAttrs = entry.getValue(); + if (entry.getValue().getColumnCount() > 0) + created.add(new Row(entry.getKey(), entry.getValue())); - // we don't care about nested ColumnFamilies here because those are going to be processed separately - if (!(ksAttrs.getColumnCount() == 0)) - addKeyspace(KSMetaData.fromSchema(new Row(entry.getKey(), entry.getValue()), Collections.emptyList())); + for (Map.Entry> entry : diff.entriesDiffering().entrySet()) + { + String keyspaceName = AsciiType.instance.compose(entry.getKey().key); + + ColumnFamily pre = entry.getValue().leftValue(); + ColumnFamily post = entry.getValue().rightValue(); + + if (pre.getColumnCount() > 0 && post.getColumnCount() > 0) + altered.add(keyspaceName); + else if (pre.getColumnCount() > 0) + dropped.add(keyspaceName); + else if (post.getColumnCount() > 0) // a (re)created keyspace + created.add(new Row(entry.getKey(), post)); } - /** - * At second step we check if there were any keyspaces re-created, in this context - * re-created means that they were previously deleted but still exist in the low-level schema as empty keys - */ - - Map> modifiedEntries = diff.entriesDiffering(); - - // instead of looping over all modified entries and skipping processed keys all the time - // we would rather store "left to process" items and iterate over them removing already met keys - List leftToProcess = new ArrayList(modifiedEntries.size()); - - for (Map.Entry> entry : modifiedEntries.entrySet()) - { - ColumnFamily prevValue = entry.getValue().leftValue(); - ColumnFamily newValue = entry.getValue().rightValue(); - - if (prevValue.getColumnCount() == 0) - { - addKeyspace(KSMetaData.fromSchema(new Row(entry.getKey(), newValue), Collections.emptyList())); - continue; - } - - leftToProcess.add(entry.getKey()); - } - - if (leftToProcess.size() == 0) - return Collections.emptySet(); - - /** - * At final step we updating modified keyspaces and saving keyspaces drop them later - */ - - Set keyspacesToDrop = new HashSet(); - - for (DecoratedKey key : leftToProcess) - { - MapDifference.ValueDifference valueDiff = modifiedEntries.get(key); - - ColumnFamily newState = valueDiff.rightValue(); - - if (newState.getColumnCount() == 0) - keyspacesToDrop.add(AsciiType.instance.getString(key.key)); - else - updateKeyspace(KSMetaData.fromSchema(new Row(key, newState), Collections.emptyList())); - } - - return keyspacesToDrop; + for (Row row : created) + addKeyspace(KSMetaData.fromSchema(row, Collections.emptyList())); + for (String name : altered) + updateKeyspace(name); + return dropped; } - private static void mergeColumnFamilies(Map old, Map updated) + // see the comments for mergeKeyspaces() + private static void mergeColumnFamilies(Map before, Map after) { - // calculate the difference between old and new states (note that entriesOnlyLeft() will be always empty) - MapDifference diff = Maps.difference(old, updated); + List created = new ArrayList<>(); + List altered = new ArrayList<>(); + List dropped = new ArrayList<>(); + + MapDifference diff = Maps.difference(before, after); - // check if any new Keyspaces with ColumnFamilies were added. for (Map.Entry entry : diff.entriesOnlyOnRight().entrySet()) + if (entry.getValue().getColumnCount() > 0) + created.addAll(KSMetaData.deserializeColumnFamilies(new Row(entry.getKey(), entry.getValue())).values()); + + for (Map.Entry> entry : diff.entriesDiffering().entrySet()) { - ColumnFamily cfAttrs = entry.getValue(); + String keyspaceName = AsciiType.instance.compose(entry.getKey().key); - if (!(cfAttrs.getColumnCount() == 0)) + ColumnFamily pre = entry.getValue().leftValue(); + ColumnFamily post = entry.getValue().rightValue(); + + if (pre.getColumnCount() > 0 && post.getColumnCount() > 0) { - Map cfDefs = KSMetaData.deserializeColumnFamilies(new Row(entry.getKey(), cfAttrs)); + MapDifference delta = + Maps.difference(Schema.instance.getKSMetaData(keyspaceName).cfMetaData(), + KSMetaData.deserializeColumnFamilies(new Row(entry.getKey(), post))); - for (CFMetaData cfDef : cfDefs.values()) - addColumnFamily(cfDef); + dropped.addAll(delta.entriesOnlyOnLeft().values()); + created.addAll(delta.entriesOnlyOnRight().values()); + Iterables.addAll(altered, Iterables.transform(delta.entriesDiffering().values(), new Function, CFMetaData>() + { + public CFMetaData apply(MapDifference.ValueDifference pair) + { + return pair.rightValue(); + } + })); + } + else if (pre.getColumnCount() > 0) + { + dropped.addAll(Schema.instance.getKSMetaData(keyspaceName).cfMetaData().values()); + } + else if (post.getColumnCount() > 0) + { + created.addAll(KSMetaData.deserializeColumnFamilies(new Row(entry.getKey(), post)).values()); } } - // deal with modified ColumnFamilies (remember that all of the keyspace nested ColumnFamilies are put to the single row) - Map> modifiedEntries = diff.entriesDiffering(); - - for (DecoratedKey keyspace : modifiedEntries.keySet()) - { - MapDifference.ValueDifference valueDiff = modifiedEntries.get(keyspace); - - ColumnFamily prevValue = valueDiff.leftValue(); // state before external modification - ColumnFamily newValue = valueDiff.rightValue(); // updated state - - Row newRow = new Row(keyspace, newValue); - - if (prevValue.getColumnCount() == 0) // whole keyspace was deleted and now it's re-created - { - for (CFMetaData cfm : KSMetaData.deserializeColumnFamilies(newRow).values()) - addColumnFamily(cfm); - } - else if (newValue.getColumnCount() == 0) // whole keyspace is deleted - { - for (CFMetaData cfm : KSMetaData.deserializeColumnFamilies(new Row(keyspace, prevValue)).values()) - dropColumnFamily(cfm.ksName, cfm.cfName); - } - else // has modifications in the nested ColumnFamilies, need to perform nested diff to determine what was really changed - { - String ksName = AsciiType.instance.getString(keyspace.key); - - Map oldCfDefs = new HashMap(); - for (CFMetaData cfm : Schema.instance.getKSMetaData(ksName).cfMetaData().values()) - oldCfDefs.put(cfm.cfName, cfm); - - Map newCfDefs = KSMetaData.deserializeColumnFamilies(newRow); - - MapDifference cfDefDiff = Maps.difference(oldCfDefs, newCfDefs); - - for (CFMetaData cfDef : cfDefDiff.entriesOnlyOnRight().values()) - addColumnFamily(cfDef); - - for (CFMetaData cfDef : cfDefDiff.entriesOnlyOnLeft().values()) - dropColumnFamily(cfDef.ksName, cfDef.cfName); - - for (MapDifference.ValueDifference cfDef : cfDefDiff.entriesDiffering().values()) - updateColumnFamily(cfDef.rightValue()); - } - } + for (CFMetaData cfm : created) + addColumnFamily(cfm); + for (CFMetaData cfm : altered) + updateColumnFamily(cfm.ksName, cfm.cfName); + for (CFMetaData cfm : dropped) + dropColumnFamily(cfm.ksName, cfm.cfName); } private static void addKeyspace(KSMetaData ksm) @@ -355,9 +323,9 @@ public class DefsTables } } - private static void updateKeyspace(KSMetaData newState) + private static void updateKeyspace(String ksName) { - KSMetaData oldKsm = Schema.instance.getKSMetaData(newState.name); + KSMetaData oldKsm = Schema.instance.getKSMetaData(ksName); assert oldKsm != null; KSMetaData newKsm = KSMetaData.cloneWith(oldKsm.reloadAttributes(), oldKsm.cfMetaData().values()); @@ -365,14 +333,14 @@ public class DefsTables if (!StorageService.instance.isClientMode()) { - Keyspace.open(newState.name).createReplicationStrategy(newKsm); + Keyspace.open(ksName).createReplicationStrategy(newKsm); MigrationManager.instance.notifyUpdateKeyspace(newKsm); } } - private static void updateColumnFamily(CFMetaData newState) + private static void updateColumnFamily(String ksName, String cfName) { - CFMetaData cfm = Schema.instance.getCFMetaData(newState.ksName, newState.cfName); + CFMetaData cfm = Schema.instance.getCFMetaData(ksName, cfName); assert cfm != null; cfm.reload();