From ab5a8c77e4509fee9c4942ae391bbd82d7f7b295 Mon Sep 17 00:00:00 2001 From: T Jake Luciani Date: Thu, 10 Mar 2011 20:54:50 +0000 Subject: [PATCH] Use UUID as node identifiers in counters instead of IP addresses Patch by Sylvain Lebresne; Reviewed by T Jake Luciani for CASSANDRA-1938 git-svn-id: https://svn.apache.org/repos/asf/cassandra/trunk@1080345 13f79535-47bb-0310-9956-ffa450edef68 --- interface/cassandra.thrift | 1 + .../org/apache/cassandra/thrift/CfDef.java | 94 ++- src/avro/internode.genavro | 1 + .../apache/cassandra/config/CFMetaData.java | 32 +- .../cassandra/config/DatabaseDescriptor.java | 10 +- .../cassandra/config/RawColumnFamily.java | 1 + .../cql/CreateColumnFamilyStatement.java | 1 + .../org/apache/cassandra/db/ColumnFamily.java | 5 +- .../cassandra/db/ColumnFamilySerializer.java | 12 +- .../cassandra/db/ColumnFamilyStore.java | 4 +- .../apache/cassandra/db/ColumnSerializer.java | 12 +- .../cassandra/db/CompactionManager.java | 21 +- .../apache/cassandra/db/CounterColumn.java | 105 ++- .../apache/cassandra/db/CounterMutation.java | 130 +++- .../org/apache/cassandra/db/ReadResponse.java | 3 +- src/java/org/apache/cassandra/db/Row.java | 9 +- .../org/apache/cassandra/db/RowMutation.java | 48 +- .../org/apache/cassandra/db/SuperColumn.java | 9 +- .../org/apache/cassandra/db/SystemTable.java | 91 +++ src/java/org/apache/cassandra/db/Table.java | 31 + .../cassandra/db/context/CounterContext.java | 674 +++++++++++++----- .../db/marshal/AbstractCommutativeType.java | 5 - .../db/marshal/CounterColumnType.java | 33 - .../cassandra/io/IColumnSerializer.java | 22 +- .../cassandra/io/LazilyCompactedRow.java | 17 +- .../apache/cassandra/io/PrecompactedRow.java | 102 ++- .../io/sstable/SSTableIdentityIterator.java | 2 +- .../cassandra/io/sstable/SSTableWriter.java | 27 +- .../cassandra/io/util/ColumnSortedMap.java | 42 +- .../cassandra/service/StorageService.java | 3 +- .../cassandra/thrift/CassandraServer.java | 1 + .../org/apache/cassandra/utils/NodeId.java | 223 ++++++ test/resources/CounterCF.json | 2 +- test/unit/org/apache/cassandra/Util.java | 6 + .../org/apache/cassandra/db/CleanupTest.java | 5 +- .../cassandra/db/CounterColumnTest.java | 181 ++--- .../org/apache/cassandra/db/DefsTest.java | 2 + .../apache/cassandra/db/SuperColumnTest.java | 72 +- .../db/context/CounterContextTest.java | 466 ++++++------ ...java => SSTableWriterCommutativeTest.java} | 92 +-- .../service/AntiEntropyServiceTest.java | 6 +- .../AntiEntropyServiceTestAbstract.java | 6 +- 42 files changed, 1800 insertions(+), 809 deletions(-) create mode 100644 src/java/org/apache/cassandra/utils/NodeId.java rename test/unit/org/apache/cassandra/io/sstable/{SSTableWriterAESCommutativeTest.java => SSTableWriterCommutativeTest.java} (61%) diff --git a/interface/cassandra.thrift b/interface/cassandra.thrift index 30464127d0..c8c5e9ffcc 100644 --- a/interface/cassandra.thrift +++ b/interface/cassandra.thrift @@ -397,6 +397,7 @@ struct CfDef { 22: optional i32 memtable_throughput_in_mb, 23: optional double memtable_operations_in_millions, 24: optional bool replicate_on_write=0, + 25: optional double merge_shards_chance, } /* describes a keyspace. */ diff --git a/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java b/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java index 0058ee4145..a7cdf4da9b 100644 --- a/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java +++ b/interface/thrift/gen-java/org/apache/cassandra/thrift/CfDef.java @@ -72,6 +72,7 @@ public class CfDef implements TBase, java.io.Serializable, private static final TField MEMTABLE_THROUGHPUT_IN_MB_FIELD_DESC = new TField("memtable_throughput_in_mb", TType.I32, (short)22); private static final TField MEMTABLE_OPERATIONS_IN_MILLIONS_FIELD_DESC = new TField("memtable_operations_in_millions", TType.DOUBLE, (short)23); private static final TField REPLICATE_ON_WRITE_FIELD_DESC = new TField("replicate_on_write", TType.BOOL, (short)24); + private static final TField MERGE_SHARDS_CHANCE_FIELD_DESC = new TField("merge_shards_chance", TType.DOUBLE, (short)25); public String keyspace; public String name; @@ -94,6 +95,7 @@ public class CfDef implements TBase, java.io.Serializable, public int memtable_throughput_in_mb; public double memtable_operations_in_millions; public boolean replicate_on_write; + public double merge_shards_chance; /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ public enum _Fields implements TFieldIdEnum { @@ -117,7 +119,8 @@ public class CfDef implements TBase, java.io.Serializable, MEMTABLE_FLUSH_AFTER_MINS((short)21, "memtable_flush_after_mins"), MEMTABLE_THROUGHPUT_IN_MB((short)22, "memtable_throughput_in_mb"), MEMTABLE_OPERATIONS_IN_MILLIONS((short)23, "memtable_operations_in_millions"), - REPLICATE_ON_WRITE((short)24, "replicate_on_write"); + REPLICATE_ON_WRITE((short)24, "replicate_on_write"), + MERGE_SHARDS_CHANCE((short)25, "merge_shards_chance"); private static final Map byName = new HashMap(); @@ -174,6 +177,8 @@ public class CfDef implements TBase, java.io.Serializable, return MEMTABLE_OPERATIONS_IN_MILLIONS; case 24: // REPLICATE_ON_WRITE return REPLICATE_ON_WRITE; + case 25: // MERGE_SHARDS_CHANCE + return MERGE_SHARDS_CHANCE; default: return null; } @@ -227,7 +232,8 @@ public class CfDef implements TBase, java.io.Serializable, private static final int __MEMTABLE_THROUGHPUT_IN_MB_ISSET_ID = 10; private static final int __MEMTABLE_OPERATIONS_IN_MILLIONS_ISSET_ID = 11; private static final int __REPLICATE_ON_WRITE_ISSET_ID = 12; - private BitSet __isset_bit_vector = new BitSet(13); + private static final int __MERGE_SHARDS_CHANCE_ISSET_ID = 13; + private BitSet __isset_bit_vector = new BitSet(14); public static final Map<_Fields, FieldMetaData> metaDataMap; static { @@ -275,6 +281,8 @@ public class CfDef implements TBase, java.io.Serializable, new FieldValueMetaData(TType.DOUBLE))); tmpMap.put(_Fields.REPLICATE_ON_WRITE, new FieldMetaData("replicate_on_write", TFieldRequirementType.OPTIONAL, new FieldValueMetaData(TType.BOOL))); + tmpMap.put(_Fields.MERGE_SHARDS_CHANCE, new FieldMetaData("merge_shards_chance", TFieldRequirementType.OPTIONAL, + new FieldValueMetaData(TType.DOUBLE))); metaDataMap = Collections.unmodifiableMap(tmpMap); FieldMetaData.addStructMetaDataMap(CfDef.class, metaDataMap); } @@ -350,6 +358,7 @@ public class CfDef implements TBase, java.io.Serializable, this.memtable_throughput_in_mb = other.memtable_throughput_in_mb; this.memtable_operations_in_millions = other.memtable_operations_in_millions; this.replicate_on_write = other.replicate_on_write; + this.merge_shards_chance = other.merge_shards_chance; } public CfDef deepCopy() { @@ -394,6 +403,8 @@ public class CfDef implements TBase, java.io.Serializable, this.memtable_operations_in_millions = 0.0; this.replicate_on_write = false; + setMerge_shards_chanceIsSet(false); + this.merge_shards_chance = 0.0; } public String getKeyspace() { @@ -902,6 +913,29 @@ public class CfDef implements TBase, java.io.Serializable, __isset_bit_vector.set(__REPLICATE_ON_WRITE_ISSET_ID, value); } + public double getMerge_shards_chance() { + return this.merge_shards_chance; + } + + public CfDef setMerge_shards_chance(double merge_shards_chance) { + this.merge_shards_chance = merge_shards_chance; + setMerge_shards_chanceIsSet(true); + return this; + } + + public void unsetMerge_shards_chance() { + __isset_bit_vector.clear(__MERGE_SHARDS_CHANCE_ISSET_ID); + } + + /** Returns true if field merge_shards_chance is set (has been asigned a value) and false otherwise */ + public boolean isSetMerge_shards_chance() { + return __isset_bit_vector.get(__MERGE_SHARDS_CHANCE_ISSET_ID); + } + + public void setMerge_shards_chanceIsSet(boolean value) { + __isset_bit_vector.set(__MERGE_SHARDS_CHANCE_ISSET_ID, value); + } + public void setFieldValue(_Fields field, Object value) { switch (field) { case KEYSPACE: @@ -1072,6 +1106,14 @@ public class CfDef implements TBase, java.io.Serializable, } break; + case MERGE_SHARDS_CHANCE: + if (value == null) { + unsetMerge_shards_chance(); + } else { + setMerge_shards_chance((Double)value); + } + break; + } } @@ -1140,6 +1182,9 @@ public class CfDef implements TBase, java.io.Serializable, case REPLICATE_ON_WRITE: return new Boolean(isReplicate_on_write()); + case MERGE_SHARDS_CHANCE: + return new Double(getMerge_shards_chance()); + } throw new IllegalStateException(); } @@ -1193,6 +1238,8 @@ public class CfDef implements TBase, java.io.Serializable, return isSetMemtable_operations_in_millions(); case REPLICATE_ON_WRITE: return isSetReplicate_on_write(); + case MERGE_SHARDS_CHANCE: + return isSetMerge_shards_chance(); } throw new IllegalStateException(); } @@ -1399,6 +1446,15 @@ public class CfDef implements TBase, java.io.Serializable, return false; } + boolean this_present_merge_shards_chance = true && this.isSetMerge_shards_chance(); + boolean that_present_merge_shards_chance = true && that.isSetMerge_shards_chance(); + if (this_present_merge_shards_chance || that_present_merge_shards_chance) { + if (!(this_present_merge_shards_chance && that_present_merge_shards_chance)) + return false; + if (this.merge_shards_chance != that.merge_shards_chance) + return false; + } + return true; } @@ -1511,6 +1567,11 @@ public class CfDef implements TBase, java.io.Serializable, if (present_replicate_on_write) builder.append(replicate_on_write); + boolean present_merge_shards_chance = true && (isSetMerge_shards_chance()); + builder.append(present_merge_shards_chance); + if (present_merge_shards_chance) + builder.append(merge_shards_chance); + return builder.toHashCode(); } @@ -1732,6 +1793,16 @@ public class CfDef implements TBase, java.io.Serializable, return lastComparison; } } + lastComparison = Boolean.valueOf(isSetMerge_shards_chance()).compareTo(typedOther.isSetMerge_shards_chance()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetMerge_shards_chance()) { + lastComparison = TBaseHelper.compareTo(this.merge_shards_chance, typedOther.merge_shards_chance); + if (lastComparison != 0) { + return lastComparison; + } + } return 0; } @@ -1920,6 +1991,14 @@ public class CfDef implements TBase, java.io.Serializable, TProtocolUtil.skip(iprot, field.type); } break; + case 25: // MERGE_SHARDS_CHANCE + if (field.type == TType.DOUBLE) { + this.merge_shards_chance = iprot.readDouble(); + setMerge_shards_chanceIsSet(true); + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; default: TProtocolUtil.skip(iprot, field.type); } @@ -2059,6 +2138,11 @@ public class CfDef implements TBase, java.io.Serializable, oprot.writeBool(this.replicate_on_write); oprot.writeFieldEnd(); } + if (isSetMerge_shards_chance()) { + oprot.writeFieldBegin(MERGE_SHARDS_CHANCE_FIELD_DESC); + oprot.writeDouble(this.merge_shards_chance); + oprot.writeFieldEnd(); + } oprot.writeFieldStop(); oprot.writeStructEnd(); } @@ -2221,6 +2305,12 @@ public class CfDef implements TBase, java.io.Serializable, sb.append(this.replicate_on_write); first = false; } + if (isSetMerge_shards_chance()) { + if (!first) sb.append(", "); + sb.append("merge_shards_chance:"); + sb.append(this.merge_shards_chance); + first = false; + } sb.append(")"); return sb.toString(); } diff --git a/src/avro/internode.genavro b/src/avro/internode.genavro index 95218ccbae..a18e26fd5a 100644 --- a/src/avro/internode.genavro +++ b/src/avro/internode.genavro @@ -59,6 +59,7 @@ protocol InterNode { union { int, null } memtable_flush_after_mins = 60; union { null, int } memtable_throughput_in_mb = null; union { null, double} memtable_operations_in_millions = null; + union { null, double} merge_shards_chance = null; union { int, null } id; union { array, null } column_metadata; } diff --git a/src/java/org/apache/cassandra/config/CFMetaData.java b/src/java/org/apache/cassandra/config/CFMetaData.java index 8601bff6b3..3437bbb8dd 100644 --- a/src/java/org/apache/cassandra/config/CFMetaData.java +++ b/src/java/org/apache/cassandra/config/CFMetaData.java @@ -60,6 +60,7 @@ public final class CFMetaData public final static int DEFAULT_MEMTABLE_LIFETIME_IN_MINS = 60 * 24; public final static int DEFAULT_MEMTABLE_THROUGHPUT_IN_MB = sizeMemtableThroughput(); public final static double DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS = sizeMemtableOperations(DEFAULT_MEMTABLE_THROUGHPUT_IN_MB); + public final static double DEFAULT_MERGE_SHARDS_CHANCE = 0.1; private static final int MIN_CF_ID = 1000; @@ -72,6 +73,7 @@ public final class CFMetaData public static final CFMetaData MigrationsCf = newSystemTable(Migration.MIGRATIONS_CF, 2, "individual schema mutations", TimeUUIDType.instance, null, DEFAULT_SYSTEM_MEMTABLE_THROUGHPUT_IN_MB); public static final CFMetaData SchemaCf = newSystemTable(Migration.SCHEMA_CF, 3, "current state of the schema", UTF8Type.instance, null, DEFAULT_SYSTEM_MEMTABLE_THROUGHPUT_IN_MB); public static final CFMetaData IndexCf = newSystemTable(SystemTable.INDEX_CF, 5, "indexes that have been completed", UTF8Type.instance, null, DEFAULT_SYSTEM_MEMTABLE_THROUGHPUT_IN_MB); + public static final CFMetaData NodeIdCf = newSystemTable(SystemTable.NODE_ID_CF, 6, "nodeId and their metadata", TimeUUIDType.instance, null, DEFAULT_SYSTEM_MEMTABLE_THROUGHPUT_IN_MB); private static CFMetaData newSystemTable(String cfName, int cfId, String comment, AbstractType comparator, AbstractType subComparator, int memtableThroughPutInMB) { @@ -94,6 +96,7 @@ public final class CFMetaData DEFAULT_MEMTABLE_LIFETIME_IN_MINS, memtableThroughPutInMB, sizeMemtableOperations(memtableThroughPutInMB), + 0, cfId, Collections.emptyMap()); } @@ -160,6 +163,7 @@ public final class CFMetaData private int memtableFlushAfterMins; // default 60 private int memtableThroughputInMb; // default based on heap size private double memtableOperationsInMillions; // default based on throughput + private double mergeShardsChance; // default 0.1, chance [0.0, 1.0] of merging old shards during replication // NOTE: if you find yourself adding members to this class, make sure you keep the convert methods in lockstep. private final Map column_metadata; @@ -183,6 +187,7 @@ public final class CFMetaData int memtableFlushAfterMins, Integer memtableThroughputInMb, Double memtableOperationsInMillions, + double mergeShardsChance, Integer cfId, Map column_metadata) @@ -216,6 +221,7 @@ public final class CFMetaData this.memtableOperationsInMillions = memtableOperationsInMillions == null ? DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS : memtableOperationsInMillions; + this.mergeShardsChance = mergeShardsChance; this.cfId = cfId; this.column_metadata = new HashMap(column_metadata); } @@ -251,6 +257,7 @@ public final class CFMetaData int memTime, Integer memSize, Double memOps, + double mergeShardsChance, //This constructor generates the id! Map column_metadata) { @@ -273,6 +280,7 @@ public final class CFMetaData memTime, memSize, memOps, + mergeShardsChance, nextId(), column_metadata); } @@ -298,6 +306,7 @@ public final class CFMetaData DEFAULT_MEMTABLE_LIFETIME_IN_MINS, DEFAULT_MEMTABLE_THROUGHPUT_IN_MB, DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS, + 0, Collections.emptyMap()); } @@ -323,6 +332,7 @@ public final class CFMetaData cfm.memtableFlushAfterMins, cfm.memtableThroughputInMb, cfm.memtableOperationsInMillions, + cfm.mergeShardsChance, cfm.cfId, cfm.column_metadata); } @@ -349,6 +359,7 @@ public final class CFMetaData cfm.memtableFlushAfterMins, cfm.memtableThroughputInMb, cfm.memtableOperationsInMillions, + cfm.mergeShardsChance, cfm.cfId, cfm.column_metadata); } @@ -389,6 +400,7 @@ public final class CFMetaData cf.memtable_flush_after_mins = memtableFlushAfterMins; cf.memtable_throughput_in_mb = memtableThroughputInMb; cf.memtable_operations_in_millions = memtableOperationsInMillions; + cf.merge_shards_chance = mergeShardsChance; cf.column_metadata = SerDeUtils.createArray(column_metadata.size(), org.apache.cassandra.db.migration.avro.ColumnDef.SCHEMA$); for (ColumnDefinition cd : column_metadata.values()) @@ -429,6 +441,7 @@ public final class CFMetaData Integer memtable_flush_after_mins = cf.memtable_flush_after_mins == null ? DEFAULT_MEMTABLE_LIFETIME_IN_MINS : cf.memtable_flush_after_mins; Integer memtable_throughput_in_mb = cf.memtable_throughput_in_mb == null ? DEFAULT_MEMTABLE_THROUGHPUT_IN_MB : cf.memtable_throughput_in_mb; Double memtable_operations_in_millions = cf.memtable_operations_in_millions == null ? DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS : cf.memtable_operations_in_millions; + double merge_shards_chance = cf.merge_shards_chance == null ? DEFAULT_MERGE_SHARDS_CHANCE : cf.merge_shards_chance; return new CFMetaData(cf.keyspace.toString(), cf.name.toString(), @@ -449,6 +462,7 @@ public final class CFMetaData memtable_flush_after_mins, memtable_throughput_in_mb, memtable_operations_in_millions, + merge_shards_chance, cf.id, column_metadata); } @@ -473,6 +487,11 @@ public final class CFMetaData return readRepairChance; } + public double getMergeShardsChance() + { + return readRepairChance; + } + public boolean getReplicateOnWrite() { return replicateOnWrite; @@ -561,6 +580,7 @@ public final class CFMetaData .append(memtableFlushAfterMins, rhs.memtableFlushAfterMins) .append(memtableThroughputInMb, rhs.memtableThroughputInMb) .append(memtableOperationsInMillions, rhs.memtableOperationsInMillions) + .append(mergeShardsChance, rhs.mergeShardsChance) .isEquals(); } @@ -588,6 +608,7 @@ public final class CFMetaData .append(memtableFlushAfterMins) .append(memtableThroughputInMb) .append(memtableOperationsInMillions) + .append(mergeShardsChance) .toHashCode(); } @@ -622,6 +643,8 @@ public final class CFMetaData cf_def.memtable_throughput_in_mb = CFMetaData.DEFAULT_MEMTABLE_THROUGHPUT_IN_MB; if (cf_def.memtable_operations_in_millions == null) cf_def.memtable_operations_in_millions = CFMetaData.DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS; + if (cf_def.merge_shards_chance == null) + cf_def.merge_shards_chance = CFMetaData.DEFAULT_MERGE_SHARDS_CHANCE; } /** applies implicit defaults to cf definition. useful in updates */ @@ -641,6 +664,8 @@ public final class CFMetaData cf_def.setMemtable_throughput_in_mb(CFMetaData.DEFAULT_MEMTABLE_THROUGHPUT_IN_MB); if (!cf_def.isSetMemtable_operations_in_millions()) cf_def.setMemtable_operations_in_millions(CFMetaData.DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS); + if (!cf_def.isSetMerge_shards_chance()) + cf_def.setMerge_shards_chance(CFMetaData.DEFAULT_MERGE_SHARDS_CHANCE); } // merges some final fields from this CFM with modifiable fields from CfDef into a new CFMetaData. @@ -683,6 +708,7 @@ public final class CFMetaData memtableFlushAfterMins = cf_def.memtable_flush_after_mins; memtableThroughputInMb = cf_def.memtable_throughput_in_mb; memtableOperationsInMillions = cf_def.memtable_operations_in_millions; + mergeShardsChance = cf_def.merge_shards_chance; // adjust secondary indexes. figure out who is coming and going. Set toRemove = new HashSet(); @@ -746,6 +772,7 @@ public final class CFMetaData def.setMemtable_flush_after_mins(cfm.memtableFlushAfterMins); def.setMemtable_throughput_in_mb(cfm.memtableThroughputInMb); def.setMemtable_operations_in_millions(cfm.memtableOperationsInMillions); + def.setMerge_shards_chance(cfm.mergeShardsChance); List column_meta = new ArrayList< org.apache.cassandra.thrift.ColumnDef>(cfm.column_metadata.size()); for (ColumnDefinition cd : cfm.column_metadata.values()) { @@ -788,6 +815,7 @@ public final class CFMetaData def.memtable_flush_after_mins = cfm.memtableFlushAfterMins; def.memtable_throughput_in_mb = cfm.memtableThroughputInMb; def.memtable_operations_in_millions = cfm.memtableOperationsInMillions; + def.merge_shards_chance = cfm.mergeShardsChance; List column_meta = new ArrayList(cfm.column_metadata.size()); for (ColumnDefinition cd : cfm.column_metadata.values()) { @@ -825,7 +853,8 @@ public final class CFMetaData newDef.row_cache_save_period_in_seconds = def.getRow_cache_save_period_in_seconds(); newDef.row_cache_size = def.getRow_cache_size(); newDef.subcomparator_type = def.getSubcomparator_type(); - + newDef.merge_shards_chance = def.getMerge_shards_chance(); + List columnMeta = new ArrayList(); if (def.isSetColumn_metadata()) { @@ -947,6 +976,7 @@ public final class CFMetaData .append("memtableFlushAfterMins", memtableFlushAfterMins) .append("memtableThroughputInMb", memtableThroughputInMb) .append("memtableOperationsInMillions", memtableOperationsInMillions) + .append("mergeShardsChance", mergeShardsChance) .append("column_metadata", column_metadata) .toString(); } diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index 0f5f216eb9..6b3ccb491d 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -377,12 +377,14 @@ public class DatabaseDescriptor CFMetaData.HintsCf, CFMetaData.MigrationsCf, CFMetaData.SchemaCf, - CFMetaData.IndexCf); + CFMetaData.IndexCf, + CFMetaData.NodeIdCf); CFMetaData.map(CFMetaData.StatusCf); CFMetaData.map(CFMetaData.HintsCf); CFMetaData.map(CFMetaData.MigrationsCf); CFMetaData.map(CFMetaData.SchemaCf); CFMetaData.map(CFMetaData.IndexCf); + CFMetaData.map(CFMetaData.NodeIdCf); tables.put(Table.SYSTEM_TABLE, systemMeta); /* Load the seeds for node contact points */ @@ -605,6 +607,11 @@ public class DatabaseDescriptor throw new ConfigurationException("memtable_operations_in_millions must be a positive double"); } + if (cf.merge_shards_chance < 0.0 || cf.merge_shards_chance > 1.0) + { + throw new ConfigurationException("merge_shards_chance must be between 0.0 and 1.0 (0% and 100%)"); + } + Map metadata = new TreeMap(); for (RawColumnDefinition rcd : cf.column_metadata) @@ -646,6 +653,7 @@ public class DatabaseDescriptor cf.memtable_flush_after_mins, cf.memtable_throughput_in_mb, cf.memtable_operations_in_millions, + cf.merge_shards_chance, metadata); } defs.add(new KSMetaData(keyspace.name, diff --git a/src/java/org/apache/cassandra/config/RawColumnFamily.java b/src/java/org/apache/cassandra/config/RawColumnFamily.java index ca7138c01f..b764decb98 100644 --- a/src/java/org/apache/cassandra/config/RawColumnFamily.java +++ b/src/java/org/apache/cassandra/config/RawColumnFamily.java @@ -47,4 +47,5 @@ public class RawColumnFamily public int memtable_flush_after_mins = CFMetaData.DEFAULT_MEMTABLE_LIFETIME_IN_MINS; public Integer memtable_throughput_in_mb; public Double memtable_operations_in_millions; + public double merge_shards_chance = CFMetaData.DEFAULT_MERGE_SHARDS_CHANCE; } diff --git a/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java b/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java index 90e0c1c851..2307c629d8 100644 --- a/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java +++ b/src/java/org/apache/cassandra/cql/CreateColumnFamilyStatement.java @@ -226,6 +226,7 @@ public class CreateColumnFamilyStatement getPropertyInt(KW_MEMTABLEFLUSHINMINS, CFMetaData.DEFAULT_MEMTABLE_LIFETIME_IN_MINS), getPropertyInt(KW_MEMTABLESIZEINMB, CFMetaData.DEFAULT_MEMTABLE_THROUGHPUT_IN_MB), getPropertyDouble(KW_MEMTABLEOPSINMILLIONS, CFMetaData.DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS), + 0, getColumns(comparator)); } catch (ConfigurationException e) diff --git a/src/java/org/apache/cassandra/db/ColumnFamily.java b/src/java/org/apache/cassandra/db/ColumnFamily.java index 4137c0def3..475ac686a1 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamily.java +++ b/src/java/org/apache/cassandra/db/ColumnFamily.java @@ -134,15 +134,12 @@ public class ColumnFamily implements IColumnContainer, IIterableColumns delete(cf); } - /** - * FIXME: Gross. - */ public IColumnSerializer getColumnSerializer() { return columnSerializer; } - int getColumnCount() + public int getColumnCount() { return columns.size(); } diff --git a/src/java/org/apache/cassandra/db/ColumnFamilySerializer.java b/src/java/org/apache/cassandra/db/ColumnFamilySerializer.java index 543aed490e..e4288c3d0c 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilySerializer.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilySerializer.java @@ -108,10 +108,10 @@ public class ColumnFamilySerializer implements ICompactSerializer2 public ColumnFamily deserialize(DataInput dis) throws IOException { - return deserialize(dis, false); + return deserialize(dis, false, false); } - public ColumnFamily deserialize(DataInput dis, boolean intern) throws IOException + public ColumnFamily deserialize(DataInput dis, boolean intern, boolean fromRemote) throws IOException { if (!dis.readBoolean()) return null; @@ -122,23 +122,23 @@ public class ColumnFamilySerializer implements ICompactSerializer2 throw new UnserializableColumnFamilyException("Couldn't find cfId=" + cfId, cfId); ColumnFamily cf = ColumnFamily.create(cfId); deserializeFromSSTableNoColumns(cf, dis); - deserializeColumns(dis, cf, intern); + deserializeColumns(dis, cf, intern, fromRemote); return cf; } - public void deserializeColumns(DataInput dis, ColumnFamily cf, boolean intern) throws IOException + public void deserializeColumns(DataInput dis, ColumnFamily cf, boolean intern, boolean fromRemote) throws IOException { int size = dis.readInt(); ColumnFamilyStore interner = intern ? Table.open(CFMetaData.getCF(cf.id()).left).getColumnFamilyStore(cf.id()) : null; for (int i = 0; i < size; ++i) { - IColumn column = cf.getColumnSerializer().deserialize(dis, interner); + IColumn column = cf.getColumnSerializer().deserialize(dis, interner, fromRemote); cf.addColumn(column); } } public ColumnFamily deserializeFromSSTableNoColumns(ColumnFamily cf, DataInput input) throws IOException - { + { cf.delete(input.readInt(), input.readLong()); return cf; } diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index 9594ea52e2..e73c6f23cf 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -1030,9 +1030,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean return maxFile; } - public void forceCleanup() throws ExecutionException, InterruptedException + public void forceCleanup(NodeId.OneShotRenewer renewer) throws ExecutionException, InterruptedException { - CompactionManager.instance.performCleanup(ColumnFamilyStore.this); + CompactionManager.instance.performCleanup(ColumnFamilyStore.this, renewer); } public void scrub() throws ExecutionException, InterruptedException diff --git a/src/java/org/apache/cassandra/db/ColumnSerializer.java b/src/java/org/apache/cassandra/db/ColumnSerializer.java index bc3fe22967..44e614ccc1 100644 --- a/src/java/org/apache/cassandra/db/ColumnSerializer.java +++ b/src/java/org/apache/cassandra/db/ColumnSerializer.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.io.IColumnSerializer; +import org.apache.cassandra.db.context.CounterContext; import org.apache.cassandra.io.ICompactSerializer2; import org.apache.cassandra.utils.ByteBufferUtil; @@ -70,10 +71,15 @@ public class ColumnSerializer implements IColumnSerializer public Column deserialize(DataInput dis) throws IOException { - return deserialize(dis, null); + return deserialize(dis, null, false); } - public Column deserialize(DataInput dis, ColumnFamilyStore interner) throws IOException + /* + * For counter columns, we must know when we deserialize them if what we + * deserialize comes from a remote host. If it does, then we must clear + * the delta. + */ + public Column deserialize(DataInput dis, ColumnFamilyStore interner, boolean fromRemote) throws IOException { ByteBuffer name = ByteBufferUtil.readWithShortLength(dis); if (name.remaining() <= 0) @@ -87,6 +93,8 @@ public class ColumnSerializer implements IColumnSerializer long timestampOfLastDelete = dis.readLong(); long ts = dis.readLong(); ByteBuffer value = ByteBufferUtil.readWithLength(dis); + if (fromRemote) + value = CounterContext.instance().clearAllDelta(value); return new CounterColumn(name, value, ts, timestampOfLastDelete); } else if ((b & EXPIRATION_MASK) != 0) diff --git a/src/java/org/apache/cassandra/db/CompactionManager.java b/src/java/org/apache/cassandra/db/CompactionManager.java index 89f8118486..65c0661613 100644 --- a/src/java/org/apache/cassandra/db/CompactionManager.java +++ b/src/java/org/apache/cassandra/db/CompactionManager.java @@ -52,6 +52,7 @@ import org.apache.cassandra.streaming.OperationType; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.WrappedRunnable; +import org.apache.cassandra.utils.NodeId; import org.cliffc.high_scale_lib.NonBlockingHashMap; public class CompactionManager implements CompactionManagerMBean @@ -161,7 +162,7 @@ public class CompactionManager implements CompactionManagerMBean } } - public void performCleanup(final ColumnFamilyStore cfStore) throws InterruptedException, ExecutionException + public void performCleanup(final ColumnFamilyStore cfStore, final NodeId.OneShotRenewer renewer) throws InterruptedException, ExecutionException { Callable runnable = new Callable() { @@ -171,7 +172,7 @@ public class CompactionManager implements CompactionManagerMBean try { if (!cfStore.isInvalid()) - doCleanupCompaction(cfStore); + doCleanupCompaction(cfStore, renewer); return this; } finally @@ -678,11 +679,12 @@ public class CompactionManager implements CompactionManagerMBean * * @throws IOException */ - private void doCleanupCompaction(ColumnFamilyStore cfs) throws IOException + private void doCleanupCompaction(ColumnFamilyStore cfs, NodeId.OneShotRenewer renewer) throws IOException { assert !cfs.isIndex(); Table table = cfs.table; Collection ranges = StorageService.instance.getLocalRanges(table.name); + boolean isCommutative = cfs.metadata.getDefaultValidator().isCommutative(); for (SSTableReader sstable : cfs.getSSTables()) { @@ -718,11 +720,16 @@ public class CompactionManager implements CompactionManagerMBean } else { - while (row.hasNext()) + if (!indexedColumns.isEmpty() || isCommutative) { - IColumn column = row.next(); - if (indexedColumns.contains(column.name())) - Table.cleanupIndexEntry(cfs, row.getKey().key, column); + while (row.hasNext()) + { + IColumn column = row.next(); + if (column instanceof CounterColumn) + renewer.maybeRenew((CounterColumn)column); + if (indexedColumns.contains(column.name())) + Table.cleanupIndexEntry(cfs, row.getKey().key, column); + } } } } diff --git a/src/java/org/apache/cassandra/db/CounterColumn.java b/src/java/org/apache/cassandra/db/CounterColumn.java index 0a26f7f479..b257c17c8e 100644 --- a/src/java/org/apache/cassandra/db/CounterColumn.java +++ b/src/java/org/apache/cassandra/db/CounterColumn.java @@ -18,29 +18,33 @@ package org.apache.cassandra.db; +import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.util.Arrays; +import java.util.Map; import org.apache.log4j.Logger; import org.apache.cassandra.db.context.CounterContext; import org.apache.cassandra.db.context.IContext.ContextRelationship; import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.NodeId; /** * A column that represents a partitioned counter. */ public class CounterColumn extends Column { - private static Logger logger = Logger.getLogger(CounterColumn.class); + private static final Logger logger = Logger.getLogger(CounterColumn.class); - private static CounterContext contextManager = CounterContext.instance(); + protected static final CounterContext contextManager = CounterContext.instance(); - protected final long timestampOfLastDelete; + private final long timestampOfLastDelete; public CounterColumn(ByteBuffer name, long value, long timestamp) { @@ -98,11 +102,30 @@ public class CounterColumn extends Column return null; } + /* + * We have to special case digest creation for counter column because + * we don't want to include the information about which shard of the + * context is a delta or not, since this information differs from node to + * node. + */ @Override public void updateDigest(MessageDigest digest) { - super.updateDigest(digest); - digest.update(ByteBufferUtil.bytes(timestampOfLastDelete)); + digest.update(name.duplicate()); + // We don't take the deltas into account in a digest + contextManager.updateDigest(digest, value); + DataOutputBuffer buffer = new DataOutputBuffer(); + try + { + buffer.writeLong(timestamp); + buffer.writeByte(serializationFlags()); + buffer.writeLong(timestampOfLastDelete); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + digest.update(buffer.getData(), 0, buffer.getLength()); } @Override @@ -181,17 +204,69 @@ public class CounterColumn extends Column return ColumnSerializer.COUNTER_MASK; } - public CounterColumn cleanNodeCounts(InetAddress node) + /** + * Check if a given nodeId is found in this CounterColumn context. + */ + public boolean hasNodeId(NodeId id) { - // use cases: - // 1) AES post-stream - // 2) RRR, after CF.cloneMe() - // 3) RRR, after CF.diff() which creates a new CF - ByteBuffer cleanedValue = contextManager.cleanNodeCounts(value, node); - if (cleanedValue == value) // reference equality is enough - return this; - if (0 == value.remaining()) + return contextManager.hasNodeId(value(), id); + } + + public CounterColumn computeOldShardMerger() + { + ByteBuffer bb = contextManager.computeOldShardMerger(value(), NodeId.getOldLocalNodeIds()); + if (bb == null) return null; - return new CounterColumn(name, cleanedValue, timestamp, timestampOfLastDelete); + else + return new CounterColumn(name(), bb, timestamp(), timestampOfLastDelete); + } + + private CounterColumn removeOldShards(int gcBefore) + { + ByteBuffer bb = contextManager.removeOldShards(value(), gcBefore); + if (bb == value()) + return this; + else + { + return new CounterColumn(name(), bb, timestamp(), timestampOfLastDelete); + } + } + + public static void removeOldShards(ColumnFamily cf, int gcBefore) + { + if (!cf.isSuper()) + { + for (Map.Entry entry : cf.getColumnsMap().entrySet()) + { + ByteBuffer cname = entry.getKey(); + IColumn c = entry.getValue(); + if (!(c instanceof CounterColumn)) + continue; + CounterColumn cleaned = ((CounterColumn) c).removeOldShards(gcBefore); + if (cleaned != c) + { + cf.remove(cname); + cf.addColumn(cleaned); + } + } + } + else + { + for (Map.Entry entry : cf.getColumnsMap().entrySet()) + { + SuperColumn c = (SuperColumn) entry.getValue(); + for (IColumn subColumn : c.getSubColumns()) + { + if (!(subColumn instanceof CounterColumn)) + continue; + CounterColumn cleaned = ((CounterColumn) subColumn).removeOldShards(gcBefore); + if (cleaned != subColumn) + { + c.remove(subColumn.name()); + c.addColumn(cleaned); + } + } + } + } } } diff --git a/src/java/org/apache/cassandra/db/CounterMutation.java b/src/java/org/apache/cassandra/db/CounterMutation.java index 14d8630530..0ae6d0e728 100644 --- a/src/java/org/apache/cassandra/db/CounterMutation.java +++ b/src/java/org/apache/cassandra/db/CounterMutation.java @@ -22,12 +22,14 @@ import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.lang.ThreadLocal; import java.nio.ByteBuffer; import java.net.InetAddress; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.LinkedList; +import java.util.Random; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,6 +52,15 @@ public class CounterMutation implements IMutation private final RowMutation rowMutation; private final ConsistencyLevel consistency; + private static final ThreadLocal random = new ThreadLocal() + { + @Override + protected Random initialValue() + { + return new Random(); + } + }; + public CounterMutation(RowMutation rowMutation, ConsistencyLevel consistency) { this.rowMutation = rowMutation; @@ -88,16 +99,34 @@ public class CounterMutation implements IMutation { if (!columnFamily.metadata().getReplicateOnWrite()) continue; + addReadCommandFromColumnFamily(rowMutation.getTable(), rowMutation.key(), columnFamily, readCommands); + } - // CF type: regular - if (!columnFamily.isSuper()) - { - QueryPath queryPath = new QueryPath(columnFamily.metadata().cfName); - ReadCommand readCommand = new SliceByNamesReadCommand(rowMutation.getTable(), rowMutation.key(), queryPath, columnFamily.getColumnNames()); - readCommands.add(readCommand); + // create a replication RowMutation + RowMutation replicationMutation = new RowMutation(rowMutation.getTable(), rowMutation.key()); + for (ReadCommand readCommand : readCommands) + { + Table table = Table.open(readCommand.table); + Row row = readCommand.getRow(table); + if (row == null || row.cf == null) continue; - } + row = mergeOldShards(readCommand.table, row); + replicationMutation.add(row.cf); + } + return replicationMutation; + } + + private void addReadCommandFromColumnFamily(String table, ByteBuffer key, ColumnFamily columnFamily, List commands) + { + // CF type: regular + if (!columnFamily.isSuper()) + { + QueryPath queryPath = new QueryPath(columnFamily.metadata().cfName); + commands.add(new SliceByNamesReadCommand(table, key, queryPath, columnFamily.getColumnNames())); + } + else + { // CF type: super for (IColumn superColumn : columnFamily.getSortedColumns()) { @@ -111,41 +140,76 @@ public class CounterMutation implements IMutation subColNames.add(subCol.name()); } - ReadCommand readCommand = new SliceByNamesReadCommand(rowMutation.getTable(), rowMutation.key(), queryPath, subColNames); - readCommands.add(readCommand); + commands.add(new SliceByNamesReadCommand(table, key, queryPath, subColNames)); } } + } - // replicate to non-local replicas - List foreignReplicas = StorageService.instance.getLiveNaturalEndpoints(rowMutation.getTable(), rowMutation.key()); - foreignReplicas.remove(FBUtilities.getLocalAddress()); // remove local replica - - // create a replication RowMutation - RowMutation replicationMutation = new RowMutation(rowMutation.getTable(), rowMutation.key()); - for (ReadCommand readCommand : readCommands) + private Row mergeOldShards(String table, Row row) throws IOException + { + ColumnFamily cf = row.cf; + // random check for merging to allow lessening the performance impact + if (cf.metadata().getMergeShardsChance() > random.get().nextDouble()) { - Table table = Table.open(readCommand.table); - Row row = readCommand.getRow(table); - if (row == null || row.cf == null) - continue; - AbstractType defaultValidator = row.cf.metadata().getDefaultValidator(); - if (defaultValidator.isCommutative()) + ColumnFamily merger = computeShardMerger(cf); + if (merger != null) { - /** - * Clean out contexts for all nodes we're sending the repair to, otherwise, - * we could send a context which is local to one of the foreign replicas, - * which would then incorrectly add that to its own count, because - * local resolution aggregates. - */ - // note: the following logic could be optimized - for (InetAddress foreignNode : foreignReplicas) + RowMutation localMutation = new RowMutation(table, row.key.key); + localMutation.add(merger); + localMutation.apply(); + + cf.addAll(merger); + } + } + return row; + } + + private ColumnFamily computeShardMerger(ColumnFamily cf) + { + ColumnFamily merger = null; + + // CF type: regular + if (!cf.isSuper()) + { + for (IColumn column : cf.getSortedColumns()) + { + if (!(column instanceof CounterColumn)) + continue; + IColumn c = ((CounterColumn)column).computeOldShardMerger(); + if (c != null) { - ((AbstractCommutativeType)defaultValidator).cleanContext(row.cf, foreignNode); + if (merger == null) + merger = cf.cloneMeShallow(); + merger.addColumn(c); } } - replicationMutation.add(row.cf); } - return replicationMutation; + else // CF type: super + { + for (IColumn superColumn : cf.getSortedColumns()) + { + IColumn mergerSuper = null; + for (IColumn column : superColumn.getSubColumns()) + { + if (!(column instanceof CounterColumn)) + continue; + IColumn c = ((CounterColumn)column).computeOldShardMerger(); + if (c != null) + { + if (mergerSuper == null) + mergerSuper = ((SuperColumn)superColumn).cloneMeShallow(); + mergerSuper.addColumn(c); + } + } + if (mergerSuper != null) + { + if (merger == null) + merger = cf.cloneMeShallow(); + merger.addColumn(mergerSuper); + } + } + } + return merger; } public Message makeMutationMessage(int version) throws IOException diff --git a/src/java/org/apache/cassandra/db/ReadResponse.java b/src/java/org/apache/cassandra/db/ReadResponse.java index 64dbb1686f..fdb07a769d 100644 --- a/src/java/org/apache/cassandra/db/ReadResponse.java +++ b/src/java/org/apache/cassandra/db/ReadResponse.java @@ -109,7 +109,8 @@ class ReadResponseSerializer implements ICompactSerializer Row row = null; if (!isDigest) { - row = Row.serializer().deserialize(dis, version); + // This is coming from a remote host + row = Row.serializer().deserialize(dis, version, true); } return isDigest ? new ReadResponse(ByteBuffer.wrap(digest)) : new ReadResponse(row); diff --git a/src/java/org/apache/cassandra/db/Row.java b/src/java/org/apache/cassandra/db/Row.java index 0d791bea6c..7cca2e6354 100644 --- a/src/java/org/apache/cassandra/db/Row.java +++ b/src/java/org/apache/cassandra/db/Row.java @@ -64,9 +64,14 @@ class RowSerializer implements ICompactSerializer ColumnFamily.serializer().serialize(row.cf, dos); } - public Row deserialize(DataInputStream dis, int version) throws IOException + public Row deserialize(DataInputStream dis, int version, boolean fromRemote) throws IOException { return new Row(StorageService.getPartitioner().decorateKey(ByteBufferUtil.readWithShortLength(dis)), - ColumnFamily.serializer().deserialize(dis)); + ColumnFamily.serializer().deserialize(dis, false, fromRemote)); + } + + public Row deserialize(DataInputStream dis, int version) throws IOException + { + return deserialize(dis, version, false); } } diff --git a/src/java/org/apache/cassandra/db/RowMutation.java b/src/java/org/apache/cassandra/db/RowMutation.java index c1dcf056aa..5bbde35273 100644 --- a/src/java/org/apache/cassandra/db/RowMutation.java +++ b/src/java/org/apache/cassandra/db/RowMutation.java @@ -1,20 +1,20 @@ /** - * 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. - */ +* 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; @@ -53,7 +53,7 @@ public class RowMutation implements IMutation, MessageProducer private ByteBuffer key_; // map of column family id to mutations for that column family. protected Map modifications_ = new HashMap(); - + private Map preserializedBuffers = new HashMap(); public RowMutation(String table, ByteBuffer key) @@ -106,7 +106,7 @@ public class RowMutation implements IMutation, MessageProducer * family object. * param @ cf - column family name * param @ columnFamily - the column family. - */ + */ public void add(ColumnFamily columnFamily) { assert columnFamily != null; @@ -133,7 +133,7 @@ public class RowMutation implements IMutation, MessageProducer * param @ value - value associated with the column * param @ timestamp - timestamp associated with this data. * param @ timeToLive - ttl for the column, 0 for standard (non expiring) columns - */ + */ public void add(QueryPath path, ByteBuffer value, long timestamp, int timeToLive) { Integer id = CFMetaData.getId(table_, path.columnFamilyName); @@ -183,15 +183,12 @@ public class RowMutation implements IMutation, MessageProducer /* * This is equivalent to calling commit. Applies the changes to * to the table that is obtained by calling Table.open(). - */ + */ public void apply() throws IOException { Table.open(table_).apply(this, true); } - /** - * Apply without touching the commitlog. For testing. - */ public void applyUnsafe() throws IOException { Table.open(table_).apply(this, false); @@ -200,7 +197,7 @@ public class RowMutation implements IMutation, MessageProducer /* * This is equivalent to calling commit. Applies the changes to * to the table that is obtained by calling Table.open(). - */ + */ void applyBinary() throws IOException, ExecutionException, InterruptedException { Table.open(table_).load(this); @@ -364,7 +361,8 @@ public class RowMutation implements IMutation, MessageProducer for (int i = 0; i < size; ++i) { Integer cfid = Integer.valueOf(dis.readInt()); - ColumnFamily cf = ColumnFamily.serializer().deserialize(dis, true); + // This is coming from a remote host + ColumnFamily cf = ColumnFamily.serializer().deserialize(dis, true, true); modifications.put(cfid, cf); } return new RowMutation(table, key, modifications); diff --git a/src/java/org/apache/cassandra/db/SuperColumn.java b/src/java/org/apache/cassandra/db/SuperColumn.java index c2389187f1..892167eab7 100644 --- a/src/java/org/apache/cassandra/db/SuperColumn.java +++ b/src/java/org/apache/cassandra/db/SuperColumn.java @@ -361,10 +361,15 @@ class SuperColumnSerializer implements IColumnSerializer public IColumn deserialize(DataInput dis) throws IOException { - return deserialize(dis, null); + return deserialize(dis, null, false); } public IColumn deserialize(DataInput dis, ColumnFamilyStore interner) throws IOException + { + return deserialize(dis, interner, false); + } + + public IColumn deserialize(DataInput dis, ColumnFamilyStore interner, boolean fromRemote) throws IOException { ByteBuffer name = ByteBufferUtil.readWithShortLength(dis); int localDeleteTime = dis.readInt(); @@ -377,7 +382,7 @@ class SuperColumnSerializer implements IColumnSerializer /* read the number of columns */ int size = dis.readInt(); ColumnSerializer serializer = Column.serializer(); - ColumnSortedMap preSortedMap = new ColumnSortedMap(comparator, serializer, dis, interner, size); + ColumnSortedMap preSortedMap = new ColumnSortedMap(comparator, serializer, dis, interner, size, fromRemote); SuperColumn superColumn = new SuperColumn(name, new ConcurrentSkipListMap(preSortedMap)); if (localDeleteTime != Integer.MIN_VALUE && localDeleteTime <= 0) { diff --git a/src/java/org/apache/cassandra/db/SystemTable.java b/src/java/org/apache/cassandra/db/SystemTable.java index fbd65bbc4a..ffeebf6cea 100644 --- a/src/java/org/apache/cassandra/db/SystemTable.java +++ b/src/java/org/apache/cassandra/db/SystemTable.java @@ -26,8 +26,11 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.ExecutionException; import org.slf4j.Logger; @@ -43,6 +46,7 @@ import org.apache.cassandra.dht.Token; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.NodeId; import static com.google.common.base.Charsets.UTF_8; @@ -51,6 +55,7 @@ public class SystemTable private static Logger logger = LoggerFactory.getLogger(SystemTable.class); public static final String STATUS_CF = "LocationInfo"; // keep the old CF string for backwards-compatibility public static final String INDEX_CF = "IndexInfo"; + public static final String NODE_ID_CF = "NodeIdInfo"; private static final ByteBuffer LOCATION_KEY = ByteBufferUtil.bytes("L"); private static final ByteBuffer RING_KEY = ByteBufferUtil.bytes("Ring"); private static final ByteBuffer BOOTSTRAP_KEY = ByteBufferUtil.bytes("Bootstrap"); @@ -60,6 +65,8 @@ public class SystemTable private static final ByteBuffer GENERATION = ByteBufferUtil.bytes("Generation"); private static final ByteBuffer CLUSTERNAME = ByteBufferUtil.bytes("ClusterName"); private static final ByteBuffer PARTITIONER = ByteBufferUtil.bytes("Partioner"); + private static final ByteBuffer CURRENT_LOCAL_NODE_ID_KEY = ByteBufferUtil.bytes("CurrentLocal"); + private static final ByteBuffer ALL_LOCAL_NODE_ID_KEY = ByteBufferUtil.bytes("Local"); private static DecoratedKey decorate(ByteBuffer key) { @@ -370,4 +377,88 @@ public class SystemTable forceBlockingFlush(INDEX_CF); } + + /** + * Read the current local node id from the system table or null if no + * such node id is recorded. + */ + public static NodeId getCurrentLocalNodeId() + { + ByteBuffer id = null; + Table table = Table.open(Table.SYSTEM_TABLE); + QueryFilter filter = QueryFilter.getIdentityFilter(decorate(CURRENT_LOCAL_NODE_ID_KEY), + new QueryPath(NODE_ID_CF)); + ColumnFamily cf = table.getColumnFamilyStore(NODE_ID_CF).getColumnFamily(filter); + if (cf != null) + { + assert cf.getColumnCount() <= 1; + if (cf.getColumnCount() > 0) + id = cf.iterator().next().name(); + } + if (id != null) + { + return NodeId.wrap(id); + } + else + { + return null; + } + } + + /** + * Write a new current local node id to the system table. + * + * @param oldNodeId the previous local node id (that {@code newNodeId} + * replace) or null if no such node id exists (new node or removed system + * table) + * @param newNodeId the new current local node id to record + */ + public static void writeCurrentLocalNodeId(NodeId oldNodeId, NodeId newNodeId) + { + long now = System.currentTimeMillis(); + ByteBuffer ip = ByteBuffer.wrap(FBUtilities.getLocalAddress().getAddress()); + + ColumnFamily cf = ColumnFamily.create(Table.SYSTEM_TABLE, NODE_ID_CF); + cf.addColumn(new Column(newNodeId.bytes(), ip, now)); + ColumnFamily cf2 = cf.cloneMe(); + if (oldNodeId != null) + { + cf2.addColumn(new DeletedColumn(oldNodeId.bytes(), (int) (now / 1000), now)); + } + RowMutation rmCurrent = new RowMutation(Table.SYSTEM_TABLE, CURRENT_LOCAL_NODE_ID_KEY); + RowMutation rmAll = new RowMutation(Table.SYSTEM_TABLE, ALL_LOCAL_NODE_ID_KEY); + rmCurrent.add(cf2); + rmAll.add(cf); + try + { + rmCurrent.apply(); + rmAll.apply(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public static List getOldLocalNodeIds() + { + List l = new ArrayList(); + + Table table = Table.open(Table.SYSTEM_TABLE); + QueryFilter filter = QueryFilter.getIdentityFilter(decorate(ALL_LOCAL_NODE_ID_KEY), + new QueryPath(NODE_ID_CF)); + ColumnFamily cf = table.getColumnFamilyStore(NODE_ID_CF).getColumnFamily(filter); + + NodeId previous = null; + for (IColumn c : cf.getReverseSortedColumns()) + { + if (previous != null) + l.add(new NodeId.NodeIdRecord(previous, c.timestamp())); + + // this will ignore the last column on purpose since it is the + // current local node id + previous = NodeId.wrap(c.name()); + } + return l; + } } diff --git a/src/java/org/apache/cassandra/db/Table.java b/src/java/org/apache/cassandra/db/Table.java index 38d6afe7f8..edb12ecc9b 100644 --- a/src/java/org/apache/cassandra/db/Table.java +++ b/src/java/org/apache/cassandra/db/Table.java @@ -51,6 +51,7 @@ import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.NodeId; import org.cliffc.high_scale_lib.NonBlockingHashMap; public class Table @@ -161,6 +162,36 @@ public class Table return cfs; } + /** + * Do a cleanup of keys that do not belong locally. + */ + public void forceCleanup(NodeId.OneShotRenewer renewer) throws IOException, ExecutionException, InterruptedException + { + if (name.equals(SYSTEM_TABLE)) + throw new UnsupportedOperationException("Cleanup of the system table is neither necessary nor wise"); + + // Sort the column families in order of SSTable size, so cleanup of smaller CFs + // can free up space for larger ones + List sortedColumnFamilies = new ArrayList(columnFamilyStores.values()); + Collections.sort(sortedColumnFamilies, new Comparator() + { + // Compare first on size and, if equal, sort by name (arbitrary & deterministic). + public int compare(ColumnFamilyStore cf1, ColumnFamilyStore cf2) + { + long diff = (cf1.getTotalDiskSpaceUsed() - cf2.getTotalDiskSpaceUsed()); + if (diff > 0) + return 1; + if (diff < 0) + return -1; + return cf1.columnFamily.compareTo(cf2.columnFamily); + } + }); + + // Cleanup in sorted order to free up space for the larger ones + for (ColumnFamilyStore cfs : sortedColumnFamilies) + cfs.forceCleanup(renewer); + } + /** * Take a snapshot of the entire set of column families with a given timestamp. * diff --git a/src/java/org/apache/cassandra/db/context/CounterContext.java b/src/java/org/apache/cassandra/db/context/CounterContext.java index 5881cfed01..326b1fc856 100644 --- a/src/java/org/apache/cassandra/db/context/CounterContext.java +++ b/src/java/org/apache/cassandra/db/context/CounterContext.java @@ -17,41 +17,56 @@ */ package org.apache.cassandra.db.context; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.nio.ByteBuffer; - -import org.apache.commons.lang.ArrayUtils; +import java.security.MessageDigest; +import java.util.*; import org.apache.cassandra.db.DBConstants; import org.apache.cassandra.utils.ByteBufferUtil; -import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.NodeId; /** * An implementation of a partitioned counter context. * - * The data structure is: - * a vector of (node id, logical clock, count) tuples. + * A context is primarily a list of tuples (node id, clock, count) -- called + * shard in the following. But with some shard are flagged as delta (with + * special resolution rules in merge()). * - * On update, the node will: - * 1) increment the logical clock, and - * 2) update the count associated w/ its tuple. + * The data structure has two parts: + * a) a header containing the lists of "delta" (a list of references to the second parts) + * b) a list of shard -- (node id, logical clock, count) tuples -- (the so-called 'body' below) * - * The logical clock represents the # of operations executed - * by the node. The aggregated count can be determined - * by rolling up all the counts from each tuple. + * The exact layout is: + * | header | body | + * context : |--|------|----------| + * ^ ^ + * | list of indices in the body list (2*#elt bytes) + * #elt in rest of header (2 bytes) * - * NOTE: only a given node id may increment its associated count and - * care must be taken to ensure that tuples are correctly made consistent. + * The body layout being: + * + * body: |----|----|----|----|----|----|.... + * ^ ^ ^ ^ ^ ^ + * | | count_1 | | count_2 + * | clock_1 | clock_2 + * nodeid_1 nodeid_2 + * + * The rules when merging two shard with the same nodeid are: + * - delta + delta = sum counts (and logical clock) + * - delta + other = keep the delta one + * - other + other = keep the shard with highest logical clock */ public class CounterContext implements IContext { - private static final int idLength; - private static final byte[] localId; - private static final ByteBuffer wrappedLocalId; - private static final int clockLength = DBConstants.longSize_; - private static final int countLength = DBConstants.longSize_; - private static final int stepLength; // length: id + logical clock + count + private static final int HEADER_SIZE_LENGTH = DBConstants.shortSize_; + private static final int HEADER_ELT_LENGTH = DBConstants.shortSize_; + private static final int CLOCK_LENGTH = DBConstants.longSize_; + private static final int COUNT_LENGTH = DBConstants.longSize_; + private static final int STEP_LENGTH = NodeId.LENGTH + CLOCK_LENGTH + COUNT_LENGTH; + + // Time in ms since a node id has been renewed before we consider using it + // during a merge + private static final long MIN_MERGE_DELAY = 5 * 60 * 1000; // should be aplenty // lazy-load singleton private static class LazyHolder @@ -59,14 +74,6 @@ public class CounterContext implements IContext private static final CounterContext counterContext = new CounterContext(); } - static - { - localId = FBUtilities.getLocalAddress().getAddress(); - wrappedLocalId = ByteBuffer.wrap(localId); - idLength = localId.length; - stepLength = idLength + clockLength + countLength; - } - public static CounterContext instance() { return LazyHolder.counterContext; @@ -81,25 +88,43 @@ public class CounterContext implements IContext */ public ByteBuffer create(long value) { - ByteBuffer context = ByteBuffer.allocate(stepLength); - writeElementAtOffset(context, 0, localId, 1L, value); + ByteBuffer context = ByteBuffer.allocate(HEADER_SIZE_LENGTH + HEADER_ELT_LENGTH + STEP_LENGTH); + // The first (and only) elt is a delta + context.putShort(0, (short)1); + context.putShort(HEADER_SIZE_LENGTH, (short)0); + writeElementAtOffset(context, HEADER_SIZE_LENGTH + HEADER_ELT_LENGTH, NodeId.getLocalId(), 1L, value); return context; } - // For testing purposes - public ByteBuffer create(byte[] id, long clock, long value) + // Provided for use by unit tests + public ByteBuffer create(NodeId id, long clock, long value, boolean isDelta) { - ByteBuffer context = ByteBuffer.allocate(stepLength); - writeElementAtOffset(context, 0, id, clock, value); + ByteBuffer context = ByteBuffer.allocate(HEADER_SIZE_LENGTH + (isDelta ? HEADER_ELT_LENGTH : 0) + STEP_LENGTH); + context.putShort(0, (short)(isDelta ? 1 : 0)); + if (isDelta) + { + context.putShort(HEADER_SIZE_LENGTH, (short)0); + } + writeElementAtOffset(context, HEADER_SIZE_LENGTH + (isDelta ? HEADER_ELT_LENGTH : 0), id, clock, value); return context; } // write a tuple (node id, clock, count) at offset - protected static void writeElementAtOffset(ByteBuffer context, int offset, byte[] id, long clock, long count) + private static void writeElementAtOffset(ByteBuffer context, int offset, NodeId id, long clock, long count) { - System.arraycopy(id, 0, context.array(), offset + context.arrayOffset(), idLength); - context.putLong(offset + idLength, clock); - context.putLong(offset + idLength + clockLength, count); + ByteBufferUtil.arrayCopy(id.bytes(), id.bytes().position(), context, offset, NodeId.LENGTH); + context.putLong(offset + NodeId.LENGTH, clock); + context.putLong(offset + NodeId.LENGTH + CLOCK_LENGTH, count); + } + + private static int headerLength(ByteBuffer context) + { + return HEADER_SIZE_LENGTH + context.getShort(context.position()) * HEADER_ELT_LENGTH; + } + + private static int compareId(ByteBuffer bb1, int pos1, ByteBuffer bb2, int pos2) + { + return ByteBufferUtil.compareSubArrays(bb1, pos1, bb2, pos2, NodeId.LENGTH); } /** @@ -110,40 +135,38 @@ public class CounterContext implements IContext * LESS_THAN: Subset of nodes and every count is equal or less than its corollary. * DISJOINT: Node sets are not equal and/or counts are not all greater or less than. * - * Strategy: - * compare node logical clocks (like a version vector). + * Strategy: compare node logical clocks (like a version vector). * - * @param left - * counter context. - * @param right - * counter context. + * @param left counter context. + * @param right counter context. * @return the ContextRelationship between the contexts. */ public ContextRelationship diff(ByteBuffer left, ByteBuffer right) { ContextRelationship relationship = ContextRelationship.EQUAL; + ContextState leftState = new ContextState(left, headerLength(left)); + ContextState rightState = new ContextState(right, headerLength(right)); - int leftIndex = left.position(); - int rightIndex = right.position(); - while (leftIndex < left.remaining() && rightIndex < right.remaining()) + while (leftState.hasRemaining() && rightState.hasRemaining()) { // compare id bytes - int compareId = ByteBufferUtil.compareSubArrays(left, leftIndex, right, rightIndex, idLength); + int compareId = leftState.compareIdTo(rightState); if (compareId == 0) { - long leftClock = left.getLong(leftIndex + idLength); - long rightClock = right.getLong(rightIndex + idLength); + long leftClock = leftState.getClock(); + long rightClock = rightState.getClock(); - // advance indexes - leftIndex += stepLength; - rightIndex += stepLength; + // advance + leftState.moveToNext(); + rightState.moveToNext(); // process clock comparisons if (leftClock == rightClock) { continue; } - else if (leftClock > rightClock) + else if ((leftClock >= 0 && rightClock > 0 && leftClock > rightClock) + || (leftClock < 0 && (rightClock > 0 || leftClock < rightClock))) { if (relationship == ContextRelationship.EQUAL) { @@ -160,7 +183,6 @@ public class CounterContext implements IContext } } else - // leftClock < rightClock { if (relationship == ContextRelationship.EQUAL) { @@ -180,7 +202,7 @@ public class CounterContext implements IContext else if (compareId > 0) { // only advance the right context - rightIndex += stepLength; + rightState.moveToNext(); if (relationship == ContextRelationship.EQUAL) { @@ -196,11 +218,10 @@ public class CounterContext implements IContext continue; } } - else + else // compareId < 0 { - // compareId < 0 // only advance the left context - leftIndex += stepLength; + leftState.moveToNext(); if (relationship == ContextRelationship.EQUAL) { @@ -219,7 +240,7 @@ public class CounterContext implements IContext } // check final lengths - if (leftIndex < left.remaining()) + if (leftState.hasRemaining()) { if (relationship == ContextRelationship.EQUAL) { @@ -230,7 +251,7 @@ public class CounterContext implements IContext return ContextRelationship.DISJOINT; } } - else if (rightIndex < right.remaining()) + else if (rightState.hasRemaining()) { if (relationship == ContextRelationship.EQUAL) { @@ -248,100 +269,108 @@ public class CounterContext implements IContext /** * Return a context w/ an aggregated count for each node id. * - * @param left - * counter context. - * @param right - * counter context. + * @param left counter context. + * @param right counter context. */ public ByteBuffer merge(ByteBuffer left, ByteBuffer right) { + ContextState leftState = new ContextState(left, headerLength(left)); + ContextState rightState = new ContextState(right, headerLength(right)); + // Compute size of result - int size = 0; - int leftOffset = left.position(); - int rightOffset = right.position(); - while ((leftOffset < left.limit()) && (rightOffset < right.limit())) + int mergedHeaderLength = HEADER_SIZE_LENGTH; + int mergedBodyLength = 0; + + while (leftState.hasRemaining() && rightState.hasRemaining()) { - int cmp = ByteBufferUtil.compareSubArrays(left, leftOffset, right, rightOffset, idLength); + int cmp = leftState.compareIdTo(rightState); if (cmp == 0) { - ++size; - rightOffset += stepLength; - leftOffset += stepLength; + mergedBodyLength += STEP_LENGTH; + if (leftState.isDelta() || rightState.isDelta()) + mergedHeaderLength += HEADER_ELT_LENGTH; + leftState.moveToNext(); + rightState.moveToNext(); } else if (cmp > 0) { - ++size; - rightOffset += stepLength; + mergedBodyLength += STEP_LENGTH; + if (rightState.isDelta()) + mergedHeaderLength += HEADER_ELT_LENGTH; + rightState.moveToNext(); } else // cmp < 0 { - ++size; - leftOffset += stepLength; + mergedBodyLength += STEP_LENGTH; + if (leftState.isDelta()) + mergedHeaderLength += HEADER_ELT_LENGTH; + leftState.moveToNext(); } } - size += (left.limit() - leftOffset) / stepLength; - size += (right.limit() - rightOffset) / stepLength; + mergedHeaderLength += leftState.remainingHeaderLength() + rightState.remainingHeaderLength(); + mergedBodyLength += leftState.remainingBodyLength() + rightState.remainingBodyLength(); - ByteBuffer merged = ByteBuffer.allocate(size * stepLength); - - // Do the actual merge: - // a) local id: sum clocks, counts - // b) remote id: keep highest clock, count (reconcile) - int mergedOffset = merged.position(); - leftOffset = left.position(); - rightOffset = right.position(); - while ((leftOffset < left.limit()) && (rightOffset < right.limit())) + // Do the actual merge + ByteBuffer merged = ByteBuffer.allocate(mergedHeaderLength + mergedBodyLength); + merged.putShort(merged.position(), (short) ((mergedHeaderLength - HEADER_SIZE_LENGTH) / HEADER_ELT_LENGTH)); + ContextState mergedState = new ContextState(merged, mergedHeaderLength); + leftState.reset(); + rightState.reset(); + while (leftState.hasRemaining() && rightState.hasRemaining()) { - int cmp = ByteBufferUtil.compareSubArrays(left, leftOffset, right, rightOffset, idLength); + int cmp = leftState.compareIdTo(rightState); if (cmp == 0) { - // sum for local id, keep highest othewise - long leftClock = left.getLong(leftOffset + idLength); - long rightClock = right.getLong(rightOffset + idLength); - if (ByteBufferUtil.compareSubArrays(left, leftOffset, wrappedLocalId, 0, idLength) == 0) + if (leftState.isDelta() || rightState.isDelta()) { - long leftCount = left.getLong(leftOffset + idLength + clockLength); - long rightCount = right.getLong(rightOffset + idLength + clockLength); - writeElementAtOffset(merged, mergedOffset, localId, leftClock + rightClock, leftCount + rightCount); + // Local id and at least one is a delta + if (leftState.isDelta() && rightState.isDelta()) + { + // both delta, sum + long clock = leftState.getClock() + rightState.getClock(); + long count = leftState.getCount() + rightState.getCount(); + mergedState.writeElement(leftState.getNodeId(), clock, count, true); + } + else + { + // Only one have delta, keep that one + (leftState.isDelta() ? leftState : rightState).copyTo(mergedState); + } } else { - if (leftClock >= rightClock) - ByteBufferUtil.arrayCopy(left, leftOffset, merged, mergedOffset, stepLength); + long leftClock = leftState.getClock(); + long rightClock = rightState.getClock(); + if ((leftClock >= 0 && rightClock > 0 && leftClock >= rightClock) + || (leftClock < 0 && (rightClock > 0 || leftClock < rightClock))) + leftState.copyTo(mergedState); else - ByteBufferUtil.arrayCopy(right, rightOffset, merged, mergedOffset, stepLength); + rightState.copyTo(mergedState); } - mergedOffset += stepLength; - rightOffset += stepLength; - leftOffset += stepLength; + rightState.moveToNext(); + leftState.moveToNext(); } else if (cmp > 0) { - ByteBufferUtil.arrayCopy(right, rightOffset, merged, mergedOffset, stepLength); - mergedOffset += stepLength; - rightOffset += stepLength; + rightState.copyTo(mergedState); + rightState.moveToNext(); } else // cmp < 0 { - ByteBufferUtil.arrayCopy(left, leftOffset, merged, mergedOffset, stepLength); - mergedOffset += stepLength; - leftOffset += stepLength; + leftState.copyTo(mergedState); + leftState.moveToNext(); } } - if (leftOffset < left.limit()) - ByteBufferUtil.arrayCopy( - left, - leftOffset, - merged, - mergedOffset, - left.limit() - leftOffset); - if (rightOffset < right.limit()) - ByteBufferUtil.arrayCopy( - right, - rightOffset, - merged, - mergedOffset, - right.limit() - rightOffset); + while (leftState.hasRemaining()) + { + leftState.copyTo(mergedState); + leftState.moveToNext(); + } + while (rightState.hasRemaining()) + { + rightState.copyTo(mergedState); + rightState.moveToNext(); + } return merged; } @@ -349,88 +378,391 @@ public class CounterContext implements IContext /** * Human-readable String from context. * - * @param context - * version context. + * @param context counter context. * @return a human-readable String of the context. */ public String toString(ByteBuffer context) { + ContextState state = new ContextState(context, headerLength(context)); StringBuilder sb = new StringBuilder(); sb.append("["); - for (int offset = context.position(); offset < context.limit(); offset += stepLength) + + while (state.hasRemaining()) { - if (offset > context.position()) + if (state.elementIdx() > 0) { sb.append(","); } sb.append("{"); - try - { - int absOffset = context.arrayOffset() + offset; - InetAddress address = InetAddress.getByAddress( - ArrayUtils.subarray(context.array(), absOffset, absOffset + idLength)); - sb.append(address.getHostAddress()); - } - catch (UnknownHostException uhe) - { - sb.append("?.?.?.?"); - } - sb.append(", "); - sb.append(context.getLong(offset + idLength)); - sb.append(", "); - sb.append(context.getLong(offset + idLength + clockLength)); - + sb.append(state.getNodeId().toString()).append(", "); + sb.append(state.getClock()).append(", ");; + sb.append(state.getCount()); sb.append("}"); + if (state.isDelta()) + { + sb.append("*"); + } + state.moveToNext(); } + sb.append("]"); return sb.toString(); } - // return an aggregated count across all node ids + /** + * Returns the aggregated count across all node ids. + * + * @param context a counter context + * @terurn the aggregated count represented by {@code context} + */ public long total(ByteBuffer context) { long total = 0L; - for (int offset = context.position(); offset < context.limit(); offset += stepLength) + // we could use a ContextState but it is easy enough that we avoid the object creation + for (int offset = context.position() + headerLength(context); offset < context.limit(); offset += STEP_LENGTH) { - long count = context.getLong(offset + idLength + clockLength); + long count = context.getLong(offset + NodeId.LENGTH + CLOCK_LENGTH); total += count; } return total; } - // remove the count for a given node id - public ByteBuffer cleanNodeCounts(ByteBuffer context, InetAddress node) + /** + * Remove all the delta of a context (i.e, set an empty header). + * + * @param context a counter context + * @return a version of {@code context} where no count are a delta. + */ + public ByteBuffer clearAllDelta(ByteBuffer context) { - // calculate node id - ByteBuffer nodeId = ByteBuffer.wrap(node.getAddress()); + int headerLength = headerLength(context); + if (headerLength == 0) + return context; - // look for this node id - for (int offset = 0; offset < context.remaining(); offset += stepLength) + ByteBuffer cleaned = ByteBuffer.allocate(context.remaining() - headerLength + HEADER_SIZE_LENGTH); + cleaned.putShort(cleaned.position(), (short)0); + ByteBufferUtil.arrayCopy( + context, + context.position() + headerLength, + cleaned, + cleaned.position() + HEADER_SIZE_LENGTH, + context.remaining() - headerLength); + return cleaned; + } + + /** + * Update a MessageDigest with the content of a context. + * Note that this skips the header entirely since the header information + * has local meaning only, while digests a meant for comparison across + * nodes. This means in particular that we always have: + * updateDigest(ctx) == updateDigest(clearAllDelta(ctx)) + */ + public void updateDigest(MessageDigest message, ByteBuffer context) + { + int hlength = headerLength(context); + ByteBuffer dup = context.duplicate(); + dup.position(context.position() + hlength); + message.update(dup); + } + + /** + * Checks whether the provided context has a count for the provided + * NodeId. + * + * TODO: since the context is sorted, we could implement a binary search. + * This is however not called in any critical path and contexts will be + * fairly small so it doesn't matter much. + */ + public boolean hasNodeId(ByteBuffer context, NodeId id) + { + // we could use a ContextState but it is easy enough that we avoid the object creation + for (int offset = context.position() + headerLength(context); offset < context.limit(); offset += STEP_LENGTH) { - int cmp = ByteBufferUtil.compareSubArrays(context, context.position() + offset, nodeId, 0, idLength); - if (cmp < 0) - continue; - else if (cmp == 0) + if (id.equals(NodeId.wrap(context, offset))) { - // node id found: remove node count - ByteBuffer truncatedContext = ByteBuffer.allocate(context.remaining() - stepLength); - ByteBufferUtil.arrayCopy(context, context.position(), truncatedContext, 0, offset); - ByteBufferUtil.arrayCopy( - context, - context.position() + offset + stepLength, - truncatedContext, - offset, - context.remaining() - (offset + stepLength)); - return truncatedContext; - } - else // cmp > 0 - { - break; // node id not present + return true; } } + return false; + } - return context; + /** + * Compute a new context such that if applied to context yields the same + * total but with the older local node id merged into the second to older one + * (excluding current local node id) if need be. + */ + public ByteBuffer computeOldShardMerger(ByteBuffer context, List oldIds) + { + long now = System.currentTimeMillis(); + int hlength = headerLength(context); + + // Don't bother if we know we can't find what we are looking for + if (oldIds.size() < 2 + || now - oldIds.get(0).timestamp < MIN_MERGE_DELAY + || now - oldIds.get(1).timestamp < MIN_MERGE_DELAY + || context.remaining() - hlength < 2 * STEP_LENGTH) + return null; + + Iterator recordIterator = oldIds.iterator(); + NodeId.NodeIdRecord currRecord = recordIterator.next(); + + ContextState state = new ContextState(context, hlength); + ContextState foundState = null; + + while (state.hasRemaining() && currRecord != null) + { + if (now - currRecord.timestamp < MIN_MERGE_DELAY) + return context; + + int c = state.getNodeId().compareTo(currRecord.id); + if (c == 0) + { + if (foundState == null) + { + // We found a canditate for being merged + if (state.getClock() < 0) + return null; + + foundState = state.duplicate(); + currRecord = recordIterator.hasNext() ? recordIterator.next() : null; + state.moveToNext(); + } + else + { + // Found someone to merge it to + int nbDelta = foundState.isDelta() ? 1 : 0; + nbDelta += state.isDelta() ? 1 : 0; + ContextState merger = ContextState.allocate(2, nbDelta); + + long fclock = foundState.getClock(); + long fcount = foundState.getCount(); + long clock = state.getClock(); + long count = state.getCount(); + + if (foundState.isDelta()) + merger.writeElement(foundState.getNodeId(), -now - fclock, -fcount, true); + else + merger.writeElement(foundState.getNodeId(), -now, 0); + + if (state.isDelta()) + merger.writeElement(state.getNodeId(), fclock + clock, fcount, true); + else + merger.writeElement(state.getNodeId(), fclock + clock, fcount + count); + + return merger.context; + } + } + else if (c < 0) // nodeid < record + { + state.moveToNext(); + } + else // c > 0, nodeid > record + { + currRecord = recordIterator.hasNext() ? recordIterator.next() : null; + } + } + return null; + } + + /** + * Remove shards that have been canceled through computeOldShardMerger + * since a time older than gcBefore. + * Used by compaction to strip context of unecessary information, + * shrinking them. + */ + public ByteBuffer removeOldShards(ByteBuffer context, int gcBefore) + { + int hlength = headerLength(context); + ContextState state = new ContextState(context, hlength); + int removedBodySize = 0, removedHeaderSize = 0; + while (state.hasRemaining()) + { + long clock = state.getClock(); + if (clock < 0 && -((int)(clock / 1000)) < gcBefore) + { + assert state.getCount() == 0; + removedBodySize += STEP_LENGTH; + if (state.isDelta()) + removedHeaderSize += HEADER_ELT_LENGTH; + } + state.moveToNext(); + } + + if (removedBodySize == 0) + return context; + + int newSize = context.remaining() - removedHeaderSize - removedBodySize; + int newHlength = hlength - removedHeaderSize; + ByteBuffer cleanedContext = ByteBuffer.allocate(newSize); + cleanedContext.putShort(cleanedContext.position(), (short) ((newHlength - HEADER_SIZE_LENGTH) / HEADER_ELT_LENGTH)); + ContextState cleaned = new ContextState(cleanedContext, newHlength); + + state.reset(); + while (state.hasRemaining()) + { + long clock = state.getClock(); + if (clock > 0 || -((int)(clock / 1000)) >= gcBefore) + { + state.copyTo(cleaned); + } + state.moveToNext(); + } + return cleanedContext; + } + + /** + * Helper class to work on contexts (works by iterating over them). + * A context being abstractly a list of tuple (nodeid, clock, count), a + * ContextState encapsulate a context and a position to one of the tuple. + * It also allow to create new context iteratively. + * + * Note: this is intrinsically a private class intended for use by the + * methods of CounterContext only. It is however public because it is + * convenient to create handcrafted context for unit tests. + */ + public static class ContextState + { + public final ByteBuffer context; + public final int headerLength; + private int headerOffset; // offset from context.position() + private int bodyOffset; // offset from context.position() + private boolean currentIsDelta; + + public ContextState(ByteBuffer context, int headerLength) + { + this(context, headerLength, HEADER_SIZE_LENGTH, headerLength, false); + updateIsDelta(); + } + + public ContextState(ByteBuffer context) + { + this(context, headerLength(context)); + } + + private ContextState(ByteBuffer context, int headerLength, int headerOffset, int bodyOffset, boolean currentIsDelta) + { + this.context = context; + this.headerLength = headerLength; + this.headerOffset = headerOffset; + this.bodyOffset = bodyOffset; + this.currentIsDelta = currentIsDelta; + } + + public boolean isDelta() + { + return currentIsDelta; + } + + private void updateIsDelta() + { + currentIsDelta = (headerOffset < headerLength) && context.getShort(context.position() + headerOffset) == (short) elementIdx(); + } + + public boolean hasRemaining() + { + return bodyOffset < context.remaining(); + } + + public int remainingHeaderLength() + { + return headerLength - headerOffset; + } + + public int remainingBodyLength() + { + return context.remaining() - bodyOffset; + } + + public void moveToNext() + { + bodyOffset += STEP_LENGTH; + if (currentIsDelta) + { + headerOffset += HEADER_ELT_LENGTH; + } + updateIsDelta(); + } + + // This advance other to the next position (but not this) + public void copyTo(ContextState other) + { + ByteBufferUtil.arrayCopy(context, context.position() + bodyOffset, other.context, other.context.position() + other.bodyOffset, STEP_LENGTH); + if (currentIsDelta) + { + other.context.putShort(other.context.position() + other.headerOffset, (short) other.elementIdx()); + } + other.currentIsDelta = currentIsDelta; + other.moveToNext(); + } + + public int compareIdTo(ContextState other) + { + return compareId(context, context.position() + bodyOffset, other.context, other.context.position() + other.bodyOffset); + } + + public void reset() + { + this.headerOffset = HEADER_SIZE_LENGTH; + this.bodyOffset = headerLength; + updateIsDelta(); + } + + public NodeId getNodeId() + { + return NodeId.wrap(context, context.position() + bodyOffset); + } + + public long getClock() + { + return context.getLong(context.position() + bodyOffset + NodeId.LENGTH); + } + + public long getCount() + { + return context.getLong(context.position() + bodyOffset + NodeId.LENGTH + CLOCK_LENGTH); + } + + // Advance this to the next position + public void writeElement(NodeId id, long clock, long count, boolean isDelta) + { + writeElementAtOffset(context, context.position() + bodyOffset, id, clock, count); + if (isDelta) + { + context.putShort(context.position() + headerOffset, (short)elementIdx()); + } + currentIsDelta = isDelta; + moveToNext(); + } + + public void writeElement(NodeId id, long clock, long count) + { + writeElement(id, clock, count, false); + } + + public int elementIdx() + { + return (bodyOffset - headerLength) / STEP_LENGTH; + } + + public ContextState duplicate() + { + return new ContextState(context, headerLength, headerOffset, bodyOffset, currentIsDelta); + } + + /* + * Allocate a new context big enough for {@code elementCount} elements + * with {@code deltaCount} of them being delta, and return the initial + * ContextState corresponding. + */ + public static ContextState allocate(int elementCount, int deltaCount) + { + assert deltaCount <= elementCount; + int hlength = HEADER_SIZE_LENGTH + deltaCount * HEADER_ELT_LENGTH; + ByteBuffer context = ByteBuffer.allocate(hlength + elementCount * STEP_LENGTH); + context.putShort(0, (short)deltaCount); + return new ContextState(context, hlength); + } } } diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractCommutativeType.java b/src/java/org/apache/cassandra/db/marshal/AbstractCommutativeType.java index 8790b81fe0..2fa1b16d19 100644 --- a/src/java/org/apache/cassandra/db/marshal/AbstractCommutativeType.java +++ b/src/java/org/apache/cassandra/db/marshal/AbstractCommutativeType.java @@ -43,9 +43,4 @@ public abstract class AbstractCommutativeType extends AbstractType * create commutative column */ public abstract Column createColumn(ByteBuffer name, ByteBuffer value, long timestamp); - - /** - * remove target node from commutative columns - */ - public abstract void cleanContext(IColumnContainer cc, InetAddress node); } diff --git a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java index 8ed9ae62e9..5af8db0667 100644 --- a/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java +++ b/src/java/org/apache/cassandra/db/marshal/CounterColumnType.java @@ -53,38 +53,6 @@ public class CounterColumnType extends AbstractCommutativeType return new CounterUpdateColumn(name, value, timestamp); } - /** - * remove target node from commutative columns - */ - public void cleanContext(IColumnContainer cc, InetAddress node) - { - if ((cc instanceof ColumnFamily) && ((ColumnFamily)cc).isSuper()) - { - for (IColumn column : cc.getSortedColumns()) - { - SuperColumn supercol = (SuperColumn)column; - cleanContext(supercol, node); - if (0 == supercol.getSubColumns().size()) - cc.remove(supercol.name()); - } - return; - } - - for (IColumn column : cc.getSortedColumns()) - { - if (!(column instanceof CounterColumn)) // DeletedColumn - continue; - CounterColumn counterColumn = (CounterColumn)column; - CounterColumn cleanedColumn = counterColumn.cleanNodeCounts(node); - if (cleanedColumn == counterColumn) - continue; - cc.remove(counterColumn.name()); - //XXX: on "clean," must copy-and-replace - if (null != cleanedColumn) - cc.addColumn(cleanedColumn); - } - } - public ByteBuffer fromString(String source) { return ByteBufferUtil.hexToBytes(source); @@ -96,4 +64,3 @@ public class CounterColumnType extends AbstractCommutativeType throw new MarshalException(String.format("Expected 8 or 0 byte long (%d)", bytes.remaining())); } } - diff --git a/src/java/org/apache/cassandra/io/IColumnSerializer.java b/src/java/org/apache/cassandra/io/IColumnSerializer.java index cf7bb7c5ba..1b76cf7d92 100644 --- a/src/java/org/apache/cassandra/io/IColumnSerializer.java +++ b/src/java/org/apache/cassandra/io/IColumnSerializer.java @@ -1,4 +1,24 @@ package org.apache.cassandra.io; +/* + * + * 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. + * + */ import java.io.DataInput; import java.io.IOException; @@ -8,5 +28,5 @@ import org.apache.cassandra.db.IColumn; public interface IColumnSerializer extends ICompactSerializer2 { - public IColumn deserialize(DataInput in, ColumnFamilyStore interner) throws IOException; + public IColumn deserialize(DataInput in, ColumnFamilyStore interner, boolean fromRemote) throws IOException; } diff --git a/src/java/org/apache/cassandra/io/LazilyCompactedRow.java b/src/java/org/apache/cassandra/io/LazilyCompactedRow.java index d11e971303..0f04dc5300 100644 --- a/src/java/org/apache/cassandra/io/LazilyCompactedRow.java +++ b/src/java/org/apache/cassandra/io/LazilyCompactedRow.java @@ -34,6 +34,7 @@ import org.apache.commons.collections.iterators.CollatingIterator; import org.apache.cassandra.db.ColumnFamily; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ColumnIndexer; +import org.apache.cassandra.db.CounterColumn; import org.apache.cassandra.db.IColumn; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.io.sstable.SSTable; @@ -143,17 +144,7 @@ public class LazilyCompactedRow extends AbstractCompactedRow implements IIterabl Iterator iter = iterator(); while (iter.hasNext()) { - IColumn column = iter.next(); - out.reset(); - try - { - emptyColumnFamily.getColumnSerializer().serialize(column, out); - } - catch (IOException e) - { - throw new IOError(e); - } - digest.update(out.getData(), 0, out.getLength()); + iter.next().updateDigest(digest); } } @@ -225,6 +216,10 @@ public class LazilyCompactedRow extends AbstractCompactedRow implements IIterabl assert container != null; IColumn reduced = container.iterator().next(); ColumnFamily purged = shouldPurge ? ColumnFamilyStore.removeDeleted(container, gcBefore) : container; + if (purged != null && purged.metadata().getDefaultValidator().isCommutative()) + { + CounterColumn.removeOldShards(purged, gcBefore); + } if (purged == null || !purged.iterator().hasNext()) { container.clear(); diff --git a/src/java/org/apache/cassandra/io/PrecompactedRow.java b/src/java/org/apache/cassandra/io/PrecompactedRow.java index 05e3abbf8b..0cefff0727 100644 --- a/src/java/org/apache/cassandra/io/PrecompactedRow.java +++ b/src/java/org/apache/cassandra/io/PrecompactedRow.java @@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.db.ColumnFamily; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ColumnIndexer; +import org.apache.cassandra.db.CounterColumn; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.io.sstable.SSTable; import org.apache.cassandra.io.sstable.SSTableIdentityIterator; @@ -47,22 +48,17 @@ public class PrecompactedRow extends AbstractCompactedRow { private static Logger logger = LoggerFactory.getLogger(PrecompactedRow.class); - private final DataOutputBuffer buffer; - private final DataOutputBuffer headerBuffer; - private int columnCount = 0; + private final ColumnFamily compactedCf; - public PrecompactedRow(DecoratedKey key, DataOutputBuffer buffer) + public PrecompactedRow(DecoratedKey key, ColumnFamily compacted) { super(key); - this.buffer = buffer; - this.headerBuffer = new DataOutputBuffer(); + this.compactedCf = compacted; } public PrecompactedRow(ColumnFamilyStore cfStore, List rows, boolean major, int gcBefore, boolean forceDeserialize) { super(rows.get(0).getKey()); - buffer = new DataOutputBuffer(); - headerBuffer = new DataOutputBuffer(); Set sstables = new HashSet(); for (SSTableIdentityIterator row : rows) @@ -71,73 +67,75 @@ public class PrecompactedRow extends AbstractCompactedRow } boolean shouldPurge = major || !cfStore.isKeyInRemainingSSTables(key, sstables); - if (rows.size() > 1 || shouldPurge || !rows.get(0).sstable.descriptor.isLatestVersion || forceDeserialize) + ColumnFamily cf = null; + for (SSTableIdentityIterator row : rows) { - ColumnFamily cf = null; - for (SSTableIdentityIterator row : rows) - { - ColumnFamily thisCF; - try - { - thisCF = row.getColumnFamilyWithColumns(); - } - catch (IOException e) - { - logger.error("Skipping row " + key + " in " + row.getPath(), e); - continue; - } - if (cf == null) - { - cf = thisCF; - } - else - { - cf.addAll(thisCF); - } - } - ColumnFamily cfPurged = shouldPurge ? ColumnFamilyStore.removeDeleted(cf, gcBefore) : cf; - if (cfPurged == null) - return; - - ColumnIndexer.serialize(cfPurged, headerBuffer); - columnCount = ColumnFamily.serializer().serializeForSSTable(cfPurged, buffer); - } - else - { - assert rows.size() == 1; + ColumnFamily thisCF; try { - rows.get(0).echoData(buffer); - columnCount = rows.get(0).columnCount; + thisCF = row.getColumnFamilyWithColumns(); } catch (IOException e) { - throw new IOError(e); + logger.error("Skipping row " + key + " in " + row.getPath(), e); + continue; } + if (cf == null) + { + cf = thisCF; + } + else + { + cf.addAll(thisCF); + } + } + compactedCf = shouldPurge ? ColumnFamilyStore.removeDeleted(cf, gcBefore) : cf; + if (compactedCf != null && compactedCf.metadata().getDefaultValidator().isCommutative()) + { + CounterColumn.removeOldShards(compactedCf, gcBefore); } } public void write(DataOutput out) throws IOException { - long dataSize = headerBuffer.getLength() + buffer.getLength(); - assert dataSize > 0; - out.writeLong(dataSize); - out.write(headerBuffer.getData(), 0, headerBuffer.getLength()); - out.write(buffer.getData(), 0, buffer.getLength()); + if (compactedCf != null) + { + DataOutputBuffer buffer = new DataOutputBuffer(); + DataOutputBuffer headerBuffer = new DataOutputBuffer(); + ColumnIndexer.serialize(compactedCf, headerBuffer); + ColumnFamily.serializer().serializeForSSTable(compactedCf, buffer); + out.writeLong(headerBuffer.getLength() + buffer.getLength()); + out.write(headerBuffer.getData(), 0, headerBuffer.getLength()); + out.write(buffer.getData(), 0, buffer.getLength()); + } } public void update(MessageDigest digest) { - digest.update(buffer.getData(), 0, buffer.getLength()); + if (compactedCf != null) + { + DataOutputBuffer buffer = new DataOutputBuffer(); + try + { + ColumnFamily.serializer().serializeCFInfo(compactedCf, buffer); + buffer.writeInt(compactedCf.getColumnCount()); + digest.update(buffer.getData(), 0, buffer.getLength()); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + compactedCf.updateDigest(digest); + } } public boolean isEmpty() { - return buffer.getLength() == 0; + return compactedCf == null || compactedCf.getColumnCount() == 0; } public int columnCount() { - return columnCount; + return compactedCf == null ? 0 : compactedCf.getColumnCount(); } } diff --git a/src/java/org/apache/cassandra/io/sstable/SSTableIdentityIterator.java b/src/java/org/apache/cassandra/io/sstable/SSTableIdentityIterator.java index c703053869..ddf9e1bfe4 100644 --- a/src/java/org/apache/cassandra/io/sstable/SSTableIdentityIterator.java +++ b/src/java/org/apache/cassandra/io/sstable/SSTableIdentityIterator.java @@ -173,7 +173,7 @@ public class SSTableIdentityIterator implements Comparable { - private ColumnSerializer serializer; - private DataInput dis; - private Comparator comparator; - private int length; - private ColumnFamilyStore interner; + private final ColumnSerializer serializer; + private final DataInput dis; + private final Comparator comparator; + private final int length; + private final ColumnFamilyStore interner; + private final boolean fromRemote; - public ColumnSortedMap(Comparator comparator, ColumnSerializer serializer, DataInput dis, ColumnFamilyStore interner, int length) + public ColumnSortedMap(Comparator comparator, ColumnSerializer serializer, DataInput dis, ColumnFamilyStore interner, int length, boolean fromRemote) { this.comparator = comparator; this.serializer = serializer; this.interner = interner; this.dis = dis; this.length = length; + this.fromRemote = fromRemote; } public int size() @@ -141,23 +143,25 @@ public class ColumnSortedMap implements SortedMap public Set> entrySet() { - return new ColumnSet(serializer, dis, interner, length); + return new ColumnSet(serializer, dis, interner, length, fromRemote); } } class ColumnSet implements Set> { - private ColumnSerializer serializer; - private DataInput dis; - private int length; - private ColumnFamilyStore interner; + private final ColumnSerializer serializer; + private final DataInput dis; + private final int length; + private final ColumnFamilyStore interner; + private boolean fromRemote; - public ColumnSet(ColumnSerializer serializer, DataInput dis, ColumnFamilyStore interner, int length) + public ColumnSet(ColumnSerializer serializer, DataInput dis, ColumnFamilyStore interner, int length, boolean fromRemote) { this.serializer = serializer; this.dis = dis; this.interner = interner; this.length = length; + this.fromRemote = fromRemote; } public int size() @@ -177,7 +181,7 @@ class ColumnSet implements Set> public Iterator> iterator() { - return new ColumnIterator(serializer, dis, interner, length); + return new ColumnIterator(serializer, dis, interner, length, fromRemote); } public Object[] toArray() @@ -227,18 +231,20 @@ class ColumnSet implements Set> class ColumnIterator implements Iterator> { - private ColumnSerializer serializer; - private DataInput dis; - private int length; + private final ColumnSerializer serializer; + private final DataInput dis; + private final int length; + private final boolean fromRemote; private int count = 0; private ColumnFamilyStore interner; - public ColumnIterator(ColumnSerializer serializer, DataInput dis, ColumnFamilyStore interner, int length) + public ColumnIterator(ColumnSerializer serializer, DataInput dis, ColumnFamilyStore interner, int length, boolean fromRemote) { this.dis = dis; this.serializer = serializer; this.interner = interner; this.length = length; + this.fromRemote = fromRemote; } private IColumn deserializeNext() @@ -246,7 +252,7 @@ class ColumnIterator implements Iterator> try { count++; - return serializer.deserialize(dis, interner); + return serializer.deserialize(dis, interner, fromRemote); } catch (IOException e) { diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index 16b2412bca..f3ab187e71 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -1306,9 +1306,10 @@ public class StorageService implements IEndpointStateChangeSubscriber, StorageSe if (tableName.equals("system")) throw new RuntimeException("Cleanup of the system table is neither necessary nor wise"); + NodeId.OneShotRenewer nodeIdRenewer = new NodeId.OneShotRenewer(); for (ColumnFamilyStore cfStore : getValidColumnFamilies(tableName, columnFamilies)) { - cfStore.forceCleanup(); + cfStore.forceCleanup(nodeIdRenewer); } } diff --git a/src/java/org/apache/cassandra/thrift/CassandraServer.java b/src/java/org/apache/cassandra/thrift/CassandraServer.java index 905fad9ad0..3e03e7da6c 100644 --- a/src/java/org/apache/cassandra/thrift/CassandraServer.java +++ b/src/java/org/apache/cassandra/thrift/CassandraServer.java @@ -945,6 +945,7 @@ public class CassandraServer implements Cassandra.Iface cf_def.isSetMemtable_flush_after_mins() ? cf_def.memtable_flush_after_mins : CFMetaData.DEFAULT_MEMTABLE_LIFETIME_IN_MINS, cf_def.isSetMemtable_throughput_in_mb() ? cf_def.memtable_throughput_in_mb : CFMetaData.DEFAULT_MEMTABLE_THROUGHPUT_IN_MB, cf_def.isSetMemtable_operations_in_millions() ? cf_def.memtable_operations_in_millions : CFMetaData.DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS, + cf_def.isSetMerge_shards_chance() ? cf_def.merge_shards_chance : CFMetaData.DEFAULT_MERGE_SHARDS_CHANCE, ColumnDefinition.fromColumnDef(cf_def.column_metadata)); } diff --git a/src/java/org/apache/cassandra/utils/NodeId.java b/src/java/org/apache/cassandra/utils/NodeId.java new file mode 100644 index 0000000000..3edc00d16e --- /dev/null +++ b/src/java/org/apache/cassandra/utils/NodeId.java @@ -0,0 +1,223 @@ +/** + * 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.utils; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.db.CounterColumn; +import org.apache.cassandra.db.SystemTable; + +public class NodeId implements Comparable +{ + private static final Logger logger = LoggerFactory.getLogger(NodeId.class); + + public static final int LENGTH = 16; // we assume a fixed length size for all NodeIds + + private static final LocalNodeIdHistory localIds = new LocalNodeIdHistory(); + + private ByteBuffer id; + + public static NodeId getLocalId() + { + return localIds.current.get(); + } + + /** + * Renew the local node id. + * To use only when this strictly necessary, as using this will make all + * counter context grow with time. + */ + public static synchronized void renewLocalId() + { + localIds.renewCurrent(); + } + + /** + * Return the list of old local node id of this node. + * It is guaranteed that the returned list is sorted by growing node id + * (and hence the first item will be the oldest node id for this host) + */ + public static List getOldLocalNodeIds() + { + return localIds.olds; + } + + /** + * Function for test purposes, do not use otherwise. + * Pack an int in a valid NodeId so that the resulting ids respects the + * numerical ordering. Used for creating handcrafted but easy to + * understand contexts in unit tests (see CounterContextTest). + */ + public static NodeId fromInt(int n) + { + long lowBits = 0xC000000000000000L | n; + return new NodeId(ByteBuffer.allocate(16).putLong(0, 0).putLong(8, lowBits)); + } + + /* + * For performance reasons, this function interns the provided ByteBuffer. + */ + public static NodeId wrap(ByteBuffer id) + { + return new NodeId(id); + } + + public static NodeId wrap(ByteBuffer bb, int offset) + { + ByteBuffer dup = bb.duplicate(); + dup.position(offset); + dup.limit(dup.position() + LENGTH); + return wrap(dup); + } + + private NodeId(ByteBuffer id) + { + if (id.remaining() != LENGTH) + throw new IllegalArgumentException("A NodeId representation is exactly " + LENGTH + " bytes"); + + this.id = id; + } + + public static NodeId generate() + { + return new NodeId(ByteBuffer.wrap(UUIDGen.decompose(UUIDGen.makeType1UUIDFromHost(FBUtilities.getLocalAddress())))); + } + + /* + * For performance reasons, this function returns a reference to the internal ByteBuffer. Clients not modify the + * result of this function. + */ + public ByteBuffer bytes() + { + return id; + } + + public boolean isLocalId() + { + return equals(getLocalId()); + } + + public int compareTo(NodeId o) + { + return ByteBufferUtil.compareSubArrays(id, id.position(), o.id, o.id.position(), NodeId.LENGTH); + } + + @Override + public String toString() + { + return UUIDGen.getUUID(id).toString(); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + NodeId otherId = (NodeId)o; + return id.equals(otherId.id); + } + + @Override + public int hashCode() + { + return id.hashCode(); + } + + public static class OneShotRenewer + { + private boolean renewed; + private final NodeId initialId; + + public OneShotRenewer() + { + renewed = false; + initialId = getLocalId(); + } + + public void maybeRenew(CounterColumn column) + { + if (!renewed && column.hasNodeId(initialId)) + { + renewLocalId(); + renewed = true; + } + } + } + + private static class LocalNodeIdHistory + { + private final AtomicReference current; + private final List olds; + + LocalNodeIdHistory() + { + NodeId id = SystemTable.getCurrentLocalNodeId(); + if (id == null) + { + // no recorded local node id, generating a new one and saving it + id = generate(); + logger.info("No saved local node id, using newly generated: {}", id); + SystemTable.writeCurrentLocalNodeId(null, id); + current = new AtomicReference(id); + olds = new CopyOnWriteArrayList(); + } + else + { + logger.info("Saved local node id: {}", id); + current = new AtomicReference(id); + olds = new CopyOnWriteArrayList(SystemTable.getOldLocalNodeIds()); + } + } + + synchronized void renewCurrent() + { + NodeId newNodeId = generate(); + NodeId old = current.get(); + SystemTable.writeCurrentLocalNodeId(old, newNodeId); + current.set(newNodeId); + olds.add(new NodeIdRecord(old)); + } + } + + public static class NodeIdRecord + { + public final NodeId id; + public final long timestamp; + + public NodeIdRecord(NodeId id) + { + this(id, System.currentTimeMillis()); + } + + public NodeIdRecord(NodeId id, long timestamp) + { + this.id = id; + this.timestamp = timestamp; + } + } +} diff --git a/test/resources/CounterCF.json b/test/resources/CounterCF.json index 112fd4e1c6..7a9225a52a 100644 --- a/test/resources/CounterCF.json +++ b/test/resources/CounterCF.json @@ -1,3 +1,3 @@ { - "726f7741": [["636f6c4141", "7f0000010000000000000001000000000000002a", 1294532915068, "c", 0]] + "726f7741": [["636f6c4141", "000100008c619170467411e00000fe8ebeead9ee0000000000000001000000000000002a", 1294532915068, "c", 0]] } diff --git a/test/unit/org/apache/cassandra/Util.java b/test/unit/org/apache/cassandra/Util.java index 66a62ff34f..e23d7eec10 100644 --- a/test/unit/org/apache/cassandra/Util.java +++ b/test/unit/org/apache/cassandra/Util.java @@ -41,6 +41,7 @@ import org.apache.cassandra.gms.ApplicationState; import org.apache.cassandra.gms.VersionedValue; import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.utils.NodeId; import static com.google.common.base.Charsets.UTF_8; @@ -149,6 +150,11 @@ public class Util return result; } + public static boolean equalsNodeId(NodeId n, ByteBuffer context, int offset) + { + return NodeId.wrap(context, context.position() + offset).equals(n); + } + public static ColumnFamily cloneAndRemoveDeleted(ColumnFamily cf, int gcBefore) { return ColumnFamilyStore.removeDeleted(cf.cloneMe(), gcBefore); diff --git a/test/unit/org/apache/cassandra/db/CleanupTest.java b/test/unit/org/apache/cassandra/db/CleanupTest.java index c7dfaec042..8defdc7340 100644 --- a/test/unit/org/apache/cassandra/db/CleanupTest.java +++ b/test/unit/org/apache/cassandra/db/CleanupTest.java @@ -44,6 +44,7 @@ import org.apache.cassandra.thrift.IndexClause; import org.apache.cassandra.thrift.IndexExpression; import org.apache.cassandra.thrift.IndexOperator; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.NodeId; public class CleanupTest extends CleanupHelper { @@ -75,7 +76,7 @@ public class CleanupTest extends CleanupHelper assertEquals(LOOPS, rows.size()); // with one token in the ring, owned by the local node, cleanup should be a no-op - CompactionManager.instance.performCleanup(cfs); + CompactionManager.instance.performCleanup(cfs, new NodeId.OneShotRenewer()); // check data is still there rows = cfs.getRangeSlice(null, Util.range("", ""), 1000, new IdentityQueryFilter()); @@ -113,7 +114,7 @@ public class CleanupTest extends CleanupHelper tmd.clearUnsafe(); assert StorageService.instance.getLocalRanges(TABLE1).isEmpty(); - CompactionManager.instance.performCleanup(cfs); + CompactionManager.instance.performCleanup(cfs, new NodeId.OneShotRenewer()); // row data should be gone rows = cfs.getRangeSlice(null, Util.range("", ""), 1000, new IdentityQueryFilter()); diff --git a/test/unit/org/apache/cassandra/db/CounterColumnTest.java b/test/unit/org/apache/cassandra/db/CounterColumnTest.java index 601c1286f5..1b37675f5d 100644 --- a/test/unit/org/apache/cassandra/db/CounterColumnTest.java +++ b/test/unit/org/apache/cassandra/db/CounterColumnTest.java @@ -18,6 +18,8 @@ */ package org.apache.cassandra.db; +import java.security.MessageDigest; + import static org.junit.Assert.*; import java.io.ByteArrayInputStream; @@ -35,10 +37,12 @@ import org.junit.Test; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.Util; import org.apache.cassandra.db.context.CounterContext; +import static org.apache.cassandra.db.context.CounterContext.ContextState; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.NodeId; public class CounterColumnTest extends SchemaLoader { @@ -52,7 +56,7 @@ public class CounterColumnTest extends SchemaLoader static { - idLength = 4; // size of int + idLength = NodeId.LENGTH; clockLength = 8; // size of long countLength = 8; // size of long @@ -62,18 +66,16 @@ public class CounterColumnTest extends SchemaLoader @Test public void testCreate() throws UnknownHostException { - AbstractCommutativeType type = CounterColumnType.instance; long delta = 3L; - CounterUpdateColumn cuc = (CounterUpdateColumn)type.createColumn( - ByteBufferUtil.bytes("x"), - ByteBufferUtil.bytes(delta), - 1L); + CounterUpdateColumn cuc = new CounterUpdateColumn(ByteBufferUtil.bytes("x"), delta, 1L); CounterColumn column = cuc.localCopy(Table.open("Keyspace5").getColumnFamilyStore("Counter1")); assert delta == column.total(); - assert Arrays.equals(FBUtilities.getLocalAddress().getAddress(), ArrayUtils.subarray(column.value().array(), 0, idLength)); - assert 1L == column.value().getLong(0*stepLength + idLength); - assert delta == column.value().getLong(0*stepLength + idLength + clockLength); + assert 1 == column.value().getShort(0); + assert 0 == column.value().getShort(2); + assert NodeId.wrap(column.value(), 4).isLocalId(); + assert 1L == column.value().getLong(4 + 0*stepLength + idLength); + assert delta == column.value().getLong(4 + 0*stepLength + idLength + clockLength); } @Test @@ -149,36 +151,36 @@ public class CounterColumnTest extends SchemaLoader assert ((CounterColumn)reconciled).timestampOfLastDelete() == right.getMarkedForDeleteAt(); // live < live last delete - left = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(FBUtilities.toByteArray(1), 2L, 3L), 1L, Long.MIN_VALUE); - right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(FBUtilities.toByteArray(1), 1L, 1L), 4L, 3L); + left = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(NodeId.fromInt(1), 2L, 3L, false), 1L, Long.MIN_VALUE); + right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(NodeId.fromInt(1), 1L, 1L, false), 4L, 3L); assert left.reconcile(right) == right; // live last delete > live - left = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(FBUtilities.toByteArray(1), 2L, 3L), 6L, 5L); - right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(FBUtilities.toByteArray(1), 1L, 1L), 4L, 3L); + left = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(NodeId.fromInt(1), 2L, 3L, false), 6L, 5L); + right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(NodeId.fromInt(1), 1L, 1L, false), 4L, 3L); assert left.reconcile(right) == left; // live + live - left = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(FBUtilities.toByteArray(1), 1L, 1L), 4L, Long.MIN_VALUE); - right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(FBUtilities.toByteArray(1), 2L, 3L), 1L, Long.MIN_VALUE); + left = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(NodeId.fromInt(1), 1L, 1L, false), 4L, Long.MIN_VALUE); + right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(NodeId.fromInt(1), 2L, 3L, false), 1L, Long.MIN_VALUE); reconciled = left.reconcile(right); assert reconciled.name().equals(left.name()); assert ((CounterColumn)reconciled).total() == 3L; assert reconciled.timestamp() == 4L; - left = reconciled; - right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(FBUtilities.toByteArray(2), 1L, 5L), 2L, Long.MIN_VALUE); + left = reconciled; + right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(NodeId.fromInt(2), 1L, 5L, false), 2L, Long.MIN_VALUE); reconciled = left.reconcile(right); assert reconciled.name().equals(left.name()); assert ((CounterColumn)reconciled).total() == 8L; assert reconciled.timestamp() == 4L; - left = reconciled; - right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(FBUtilities.toByteArray(2), 2L, 2L), 6L, Long.MIN_VALUE); + left = reconciled; + right = new CounterColumn(ByteBufferUtil.bytes("x"), cc.create(NodeId.fromInt(2), 2L, 2L, false), 6L, Long.MIN_VALUE); reconciled = left.reconcile(right); assert reconciled.name().equals(left.name()); @@ -186,15 +188,16 @@ public class CounterColumnTest extends SchemaLoader assert reconciled.timestamp() == 6L; context = reconciled.value(); - assert 2 * stepLength == context.remaining(); + int hd = 2; // header + assert hd + 2 * stepLength == context.remaining(); - assert 1 == context.getInt(0*stepLength); - assert 2L == context.getLong(0*stepLength + idLength); - assert 3L == context.getLong(0*stepLength + idLength + clockLength); + assert Util.equalsNodeId(NodeId.fromInt(1), context, hd + 0*stepLength); + assert 2L == context.getLong(hd + 0*stepLength + idLength); + assert 3L == context.getLong(hd + 0*stepLength + idLength + clockLength); - assert 2 == context.getInt(1*stepLength); - assert 2L == context.getLong(1*stepLength + idLength); - assert 2L == context.getLong(1*stepLength + idLength + clockLength); + assert Util.equalsNodeId(NodeId.fromInt(2), context, hd + 1*stepLength); + assert 2L == context.getLong(hd + 1*stepLength + idLength); + assert 2L == context.getLong(hd + 1*stepLength + idLength + clockLength); assert ((CounterColumn)reconciled).timestampOfLastDelete() == Long.MIN_VALUE; } @@ -202,8 +205,8 @@ public class CounterColumnTest extends SchemaLoader @Test public void testDiff() throws UnknownHostException { - ByteBuffer left; - ByteBuffer right; + ContextState left; + ContextState right; CounterColumn leftCol; CounterColumn rightCol; @@ -223,91 +226,97 @@ public class CounterColumnTest extends SchemaLoader assert null == rightCol.diff(leftCol); // equality: equal nodes, all counts same - left = ByteBuffer.wrap(Util.concatByteArrays( - FBUtilities.toByteArray(3), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(6), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(9), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L) - )); - //left = cc.update(left, InetAddress.getByAddress(FBUtilities.toByteArray(3)), 0L); - right = ByteBufferUtil.clone(left); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 3L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); + right = new ContextState(ByteBufferUtil.clone(left.context), 2); - leftCol = new CounterColumn(ByteBufferUtil.bytes("x"), left, 1L); - rightCol = new CounterColumn(ByteBufferUtil.bytes("x"), right, 1L); + leftCol = new CounterColumn(ByteBufferUtil.bytes("x"), left.context, 1L); + rightCol = new CounterColumn(ByteBufferUtil.bytes("x"), right.context, 1L); assert null == leftCol.diff(rightCol); // greater than: left has superset of nodes (counts equal) - left = ByteBuffer.wrap(Util.concatByteArrays( - FBUtilities.toByteArray(3), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(6), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(9), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(12), FBUtilities.toByteArray(0L), FBUtilities.toByteArray(0L) - )); - right = ByteBuffer.wrap(Util.concatByteArrays( - FBUtilities.toByteArray(3), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(6), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(9), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L) - )); + left = ContextState.allocate(4, 0); + left.writeElement(NodeId.fromInt(3), 3L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); + left.writeElement(NodeId.fromInt(12), 0L, 0L); - leftCol = new CounterColumn(ByteBufferUtil.bytes("x"), left, 1L); - rightCol = new CounterColumn(ByteBufferUtil.bytes("x"), right, 1L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 3L, 0L); + right.writeElement(NodeId.fromInt(6), 2L, 0L); + right.writeElement(NodeId.fromInt(9), 1L, 0L); + + leftCol = new CounterColumn(ByteBufferUtil.bytes("x"), left.context, 1L); + rightCol = new CounterColumn(ByteBufferUtil.bytes("x"), right.context, 1L); assert null == leftCol.diff(rightCol); // less than: right has subset of nodes (counts equal) assert leftCol == rightCol.diff(leftCol); // disjoint: right and left have disjoint node sets - left = ByteBuffer.wrap(Util.concatByteArrays( - FBUtilities.toByteArray(3), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(4), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(9), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L) - )); - right = ByteBuffer.wrap(Util.concatByteArrays( - FBUtilities.toByteArray(3), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(6), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(9), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L) - )); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 1L, 0L); + left.writeElement(NodeId.fromInt(4), 1L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); - leftCol = new CounterColumn(ByteBufferUtil.bytes("x"), left, 1L); - rightCol = new CounterColumn(ByteBufferUtil.bytes("x"), right, 1L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 1L, 0L); + right.writeElement(NodeId.fromInt(6), 1L, 0L); + right.writeElement(NodeId.fromInt(9), 1L, 0L); + + leftCol = new CounterColumn(ByteBufferUtil.bytes("x"), left.context, 1L); + rightCol = new CounterColumn(ByteBufferUtil.bytes("x"), right.context, 1L); assert rightCol == leftCol.diff(rightCol); assert leftCol == rightCol.diff(leftCol); } @Test - public void testCleanNodeCounts() throws UnknownHostException + public void testSerializeDeserialize() throws IOException { - ByteBuffer context = ByteBuffer.wrap(Util.concatByteArrays( - FBUtilities.toByteArray(1), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(1L), - FBUtilities.toByteArray(2), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(2L), - FBUtilities.toByteArray(4), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(3L), - FBUtilities.toByteArray(8), FBUtilities.toByteArray(4L), FBUtilities.toByteArray(4L) - )); - CounterColumn c = new CounterColumn(ByteBufferUtil.bytes("x"), context, 1L); + CounterContext.ContextState state = CounterContext.ContextState.allocate(4, 2); + state.writeElement(NodeId.fromInt(1), 4L, 4L); + state.writeElement(NodeId.fromInt(2), 4L, 4L, true); + state.writeElement(NodeId.fromInt(3), 4L, 4L); + state.writeElement(NodeId.fromInt(4), 4L, 4L, true); - CounterColumn d = c.cleanNodeCounts(InetAddress.getByAddress(FBUtilities.toByteArray(4))); + CounterColumn original = new CounterColumn(ByteBufferUtil.bytes("x"), state.context, 1L); + DataOutputBuffer bufOut = new DataOutputBuffer(); + Column.serializer().serialize(original, bufOut); + byte[] serialized = bufOut.getData(); - assertEquals(7L, d.total()); + ByteArrayInputStream bufIn = new ByteArrayInputStream(serialized, 0, serialized.length); + CounterColumn deserialized = (CounterColumn)Column.serializer().deserialize(new DataInputStream(bufIn)); + assert original.equals(deserialized); + + bufIn = new ByteArrayInputStream(serialized, 0, serialized.length); + CounterColumn deserializedOnRemote = (CounterColumn)Column.serializer().deserialize(new DataInputStream(bufIn), null, true); + assert deserializedOnRemote.name().equals(original.name()); + assert deserializedOnRemote.total() == original.total(); + assert deserializedOnRemote.value().equals(cc.clearAllDelta(original.value())); + assert deserializedOnRemote.timestamp() == deserialized.timestamp(); + assert deserializedOnRemote.timestampOfLastDelete() == deserialized.timestampOfLastDelete(); } @Test - public void testSerializeDeserialize() throws IOException + public void testUpdateDigest() throws Exception { - ColumnFamily cf; + MessageDigest digest1 = MessageDigest.getInstance("md5"); + MessageDigest digest2 = MessageDigest.getInstance("md5"); - ByteBuffer context = ByteBuffer.wrap(Util.concatByteArrays( - FBUtilities.toByteArray(1), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(1L), - FBUtilities.toByteArray(2), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(2L), - FBUtilities.toByteArray(4), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(3L), - FBUtilities.toByteArray(8), FBUtilities.toByteArray(4L), FBUtilities.toByteArray(4L) - )); - CounterColumn original = new CounterColumn(ByteBufferUtil.bytes("x"), context, 1L); + CounterContext.ContextState state = CounterContext.ContextState.allocate(4, 2); + state.writeElement(NodeId.fromInt(1), 4L, 4L); + state.writeElement(NodeId.fromInt(2), 4L, 4L, true); + state.writeElement(NodeId.fromInt(3), 4L, 4L); + state.writeElement(NodeId.fromInt(4), 4L, 4L, true); - DataOutputBuffer bufOut = new DataOutputBuffer(); - Column.serializer().serialize(original, bufOut); + CounterColumn original = new CounterColumn(ByteBufferUtil.bytes("x"), state.context, 1L); + CounterColumn cleared = new CounterColumn(ByteBufferUtil.bytes("x"), cc.clearAllDelta(state.context), 1L); - ByteArrayInputStream bufIn = new ByteArrayInputStream(bufOut.getData(), 0, bufOut.getLength()); - CounterColumn deserialized = (CounterColumn)Column.serializer().deserialize(new DataInputStream(bufIn)); + original.updateDigest(digest1); + cleared.updateDigest(digest2); - assert original.equals(deserialized); + assert Arrays.equals(digest1.digest(), digest2.digest()); } } diff --git a/test/unit/org/apache/cassandra/db/DefsTest.java b/test/unit/org/apache/cassandra/db/DefsTest.java index 2381dbbd6f..9bcd493e05 100644 --- a/test/unit/org/apache/cassandra/db/DefsTest.java +++ b/test/unit/org/apache/cassandra/db/DefsTest.java @@ -123,6 +123,7 @@ public class DefsTest extends CleanupHelper 500, 500, 500.0, + 0, indexes); // we'll be adding this one later. make sure it's not already there. @@ -779,6 +780,7 @@ public class DefsTest extends CleanupHelper CFMetaData.DEFAULT_MEMTABLE_LIFETIME_IN_MINS, CFMetaData.DEFAULT_MEMTABLE_THROUGHPUT_IN_MB, CFMetaData.DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS, + 0, Collections.emptyMap()); } } diff --git a/test/unit/org/apache/cassandra/db/SuperColumnTest.java b/test/unit/org/apache/cassandra/db/SuperColumnTest.java index 6835a4e83b..7c61e7b012 100644 --- a/test/unit/org/apache/cassandra/db/SuperColumnTest.java +++ b/test/unit/org/apache/cassandra/db/SuperColumnTest.java @@ -26,9 +26,11 @@ import static junit.framework.Assert.assertNull; import static org.apache.cassandra.Util.getBytes; import static org.apache.cassandra.Util.concatByteArrays; import org.apache.cassandra.db.context.CounterContext; +import static org.apache.cassandra.db.context.CounterContext.ContextState; import org.apache.cassandra.db.marshal.LongType; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.NodeId; public class SuperColumnTest { @@ -45,62 +47,58 @@ public class SuperColumnTest @Test public void testAddColumnIncrementCounter() { - byte[] context; + ContextState state; SuperColumn sc = new SuperColumn(ByteBufferUtil.bytes("sc1"), LongType.instance); - context = concatByteArrays( - FBUtilities.toByteArray(1), FBUtilities.toByteArray(7L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(2), FBUtilities.toByteArray(5L), FBUtilities.toByteArray(7L), - FBUtilities.toByteArray(4), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(9L), - FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(3L) - ); - sc.addColumn(new CounterColumn(getBytes(1), ByteBuffer.wrap(context), 3L, 0L)); - context = concatByteArrays( - FBUtilities.toByteArray(2), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(4L), - FBUtilities.toByteArray(4), FBUtilities.toByteArray(4L), FBUtilities.toByteArray(1L), - FBUtilities.toByteArray(8), FBUtilities.toByteArray(9L), FBUtilities.toByteArray(0L), - FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(9L), FBUtilities.toByteArray(5L) - ); - sc.addColumn(new CounterColumn(getBytes(1), ByteBuffer.wrap(context), 10L, 0L)); + state = ContextState.allocate(4, 1); + state.writeElement(NodeId.fromInt(1), 7L, 0L); + state.writeElement(NodeId.fromInt(2), 5L, 7L); + state.writeElement(NodeId.fromInt(4), 2L, 9L); + state.writeElement(NodeId.getLocalId(), 3L, 3L, true); + sc.addColumn(new CounterColumn(getBytes(1), state.context, 3L, 0L)); - context = concatByteArrays( - FBUtilities.toByteArray(2), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(3), FBUtilities.toByteArray(6L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(7), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(0L) - ); - sc.addColumn(new CounterColumn(getBytes(2), ByteBuffer.wrap(context), 9L, 0L)); + state = ContextState.allocate(4, 1); + state.writeElement(NodeId.fromInt(2), 3L, 4L); + state.writeElement(NodeId.fromInt(4), 4L, 1L); + state.writeElement(NodeId.fromInt(8), 9L, 0L); + state.writeElement(NodeId.getLocalId(), 9L, 5L, true); + sc.addColumn(new CounterColumn(getBytes(1), state.context, 10L, 0L)); + + state = ContextState.allocate(3, 0); + state.writeElement(NodeId.fromInt(2), 1L, 0L); + state.writeElement(NodeId.fromInt(3), 6L, 0L); + state.writeElement(NodeId.fromInt(7), 3L, 0L); + sc.addColumn(new CounterColumn(getBytes(2), state.context, 9L, 0L)); assertNotNull(sc.getSubColumn(getBytes(1))); assertNull(sc.getSubColumn(getBytes(3))); // column: 1 - byte[] c1 = concatByteArrays( - FBUtilities.toByteArray(1), FBUtilities.toByteArray(7L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(2), FBUtilities.toByteArray(5L), FBUtilities.toByteArray(7L), - FBUtilities.toByteArray(4), FBUtilities.toByteArray(4L), FBUtilities.toByteArray(1L), - FBUtilities.toByteArray(8), FBUtilities.toByteArray(9L), FBUtilities.toByteArray(0L), - FBUtilities.getLocalAddress().getAddress(), FBUtilities.toByteArray(12L), FBUtilities.toByteArray(8L) - ); + ContextState c1 = ContextState.allocate(5, 1); + c1.writeElement(NodeId.fromInt(1), 7L, 0L); + c1.writeElement(NodeId.fromInt(2), 5L, 7L); + c1.writeElement(NodeId.fromInt(4), 4L, 1L); + c1.writeElement(NodeId.fromInt(8), 9L, 0L); + c1.writeElement(NodeId.getLocalId(), 12L, 8L, true); assert 0 == ByteBufferUtil.compareSubArrays( ((CounterColumn)sc.getSubColumn(getBytes(1))).value(), 0, - ByteBuffer.wrap(c1), + c1.context, 0, - c1.length); + c1.context.remaining()); // column: 2 - byte[] c2 = concatByteArrays( - FBUtilities.toByteArray(2), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(3), FBUtilities.toByteArray(6L), FBUtilities.toByteArray(0L), - FBUtilities.toByteArray(7), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(0L) - ); + ContextState c2 = ContextState.allocate(3, 0); + c2.writeElement(NodeId.fromInt(2), 1L, 0L); + c2.writeElement(NodeId.fromInt(3), 6L, 0L); + c2.writeElement(NodeId.fromInt(7), 3L, 0L); assert 0 == ByteBufferUtil.compareSubArrays( ((CounterColumn)sc.getSubColumn(getBytes(2))).value(), 0, - ByteBuffer.wrap(c2), + c2.context, 0, - c2.length); + c2.context.remaining()); assertNotNull(sc.getSubColumn(getBytes(1))); assertNotNull(sc.getSubColumn(getBytes(2))); diff --git a/test/unit/org/apache/cassandra/db/context/CounterContextTest.java b/test/unit/org/apache/cassandra/db/context/CounterContextTest.java index f0433b0d1d..dc48398ad7 100644 --- a/test/unit/org/apache/cassandra/db/context/CounterContextTest.java +++ b/test/unit/org/apache/cassandra/db/context/CounterContextTest.java @@ -33,315 +33,279 @@ import org.junit.Test; import org.apache.cassandra.Util; import org.apache.cassandra.db.context.IContext.ContextRelationship; -import org.apache.cassandra.utils.FBUtilities; +import static org.apache.cassandra.db.context.CounterContext.ContextState; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.NodeId; -/** - * Note: these tests assume IPv4 (4 bytes) is used for id. - * if IPv6 (16 bytes) is used, tests will fail (but the code will work). - * however, it might be pragmatic to modify the code to just use - * the IPv4 portion of the IPv6 address-space. - */ public class CounterContextTest { private static final CounterContext cc = new CounterContext(); - private static final InetAddress idAddress; - private static final byte[] id; private static final int idLength; private static final int clockLength; private static final int countLength; private static final int stepLength; - private static final int defaultEntries; static { - idAddress = FBUtilities.getLocalAddress(); - id = idAddress.getAddress(); - idLength = 4; // size of int + idLength = NodeId.LENGTH; // size of int clockLength = 8; // size of long countLength = 8; // size of long stepLength = idLength + clockLength + countLength; - - defaultEntries = 10; } @Test public void testCreate() { ByteBuffer context = cc.create(4); - assert context.remaining() == stepLength; + assert context.remaining() == stepLength + 4; } @Test public void testDiff() { - ByteBuffer left = ByteBuffer.allocate(3 * stepLength); - ByteBuffer right; + ContextState left = ContextState.allocate(3, 0); + ContextState right; // equality: equal nodes, all counts same - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); - right = ByteBufferUtil.clone(left); + left.writeElement(NodeId.fromInt(3), 3L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); + right = new ContextState(ByteBufferUtil.clone(left.context), left.headerLength); assert ContextRelationship.EQUAL == - cc.diff(left, right); + cc.diff(left.context, right.context); // greater than: left has superset of nodes (counts equal) - left = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); - cc.writeElementAtOffset(left, 3 * stepLength, FBUtilities.toByteArray(12), 0L, 0L); + left = ContextState.allocate(4, 0); + left.writeElement(NodeId.fromInt(3), 3L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); + left.writeElement(NodeId.fromInt(12), 0L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 3L, 0L); + right.writeElement(NodeId.fromInt(6), 2L, 0L); + right.writeElement(NodeId.fromInt(9), 1L, 0L); assert ContextRelationship.GREATER_THAN == - cc.diff(left, right); + cc.diff(left.context, right.context); // less than: left has subset of nodes (counts equal) - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 3L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); - right = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); - cc.writeElementAtOffset(right, 3 * stepLength, FBUtilities.toByteArray(12), 0L, 0L); + right = ContextState.allocate(4, 0); + right.writeElement(NodeId.fromInt(3), 3L, 0L); + right.writeElement(NodeId.fromInt(6), 2L, 0L); + right.writeElement(NodeId.fromInt(9), 1L, 0L); + right.writeElement(NodeId.fromInt(12), 0L, 0L); assert ContextRelationship.LESS_THAN == - cc.diff(left, right); + cc.diff(left.context, right.context); // greater than: equal nodes, but left has higher counts - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 3L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 3L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 3L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 3L, 0L); + right.writeElement(NodeId.fromInt(6), 2L, 0L); + right.writeElement(NodeId.fromInt(9), 1L, 0L); assert ContextRelationship.GREATER_THAN == - cc.diff(left, right); + cc.diff(left.context, right.context); // less than: equal nodes, but right has higher counts - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 3L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 3L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 3L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 3L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 9L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 3L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 3L, 0L); + right.writeElement(NodeId.fromInt(6), 9L, 0L); + right.writeElement(NodeId.fromInt(9), 3L, 0L); assert ContextRelationship.LESS_THAN == - cc.diff(left, right); + cc.diff(left.context, right.context); // disjoint: right and left have disjoint node sets - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 1L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(4), 1L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 1L, 0L); + left.writeElement(NodeId.fromInt(4), 1L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 1L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 1L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 1L, 0L); + right.writeElement(NodeId.fromInt(6), 1L, 0L); + right.writeElement(NodeId.fromInt(9), 1L, 0L); assert ContextRelationship.DISJOINT == - cc.diff(left, right); + cc.diff(left.context, right.context); - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 1L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(4), 1L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 1L, 0L); + left.writeElement(NodeId.fromInt(4), 1L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(2), 1L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 1L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(12), 1L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(2), 1L, 0L); + right.writeElement(NodeId.fromInt(6), 1L, 0L); + right.writeElement(NodeId.fromInt(12), 1L, 0L); assert ContextRelationship.DISJOINT == - cc.diff(left, right); + cc.diff(left.context, right.context); // disjoint: equal nodes, but right and left have higher counts in differing nodes - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 1L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 3L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 1L, 0L); + left.writeElement(NodeId.fromInt(6), 3L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 1L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 1L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 5L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 1L, 0L); + right.writeElement(NodeId.fromInt(6), 1L, 0L); + right.writeElement(NodeId.fromInt(9), 5L, 0L); assert ContextRelationship.DISJOINT == - cc.diff(left, right); + cc.diff(left.context, right.context); - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 2L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 3L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 2L, 0L); + left.writeElement(NodeId.fromInt(6), 3L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 1L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 9L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 5L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 1L, 0L); + right.writeElement(NodeId.fromInt(6), 9L, 0L); + right.writeElement(NodeId.fromInt(9), 5L, 0L); assert ContextRelationship.DISJOINT == - cc.diff(left, right); + cc.diff(left.context, right.context); // disjoint: left has more nodes, but lower counts - left = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 2L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 3L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 1L, 0L); - cc.writeElementAtOffset(left, 3 * stepLength, FBUtilities.toByteArray(12), 1L, 0L); + left = ContextState.allocate(4, 0); + left.writeElement(NodeId.fromInt(3), 2L, 0L); + left.writeElement(NodeId.fromInt(6), 3L, 0L); + left.writeElement(NodeId.fromInt(9), 1L, 0L); + left.writeElement(NodeId.fromInt(12), 1L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 4L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 9L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 5L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 4L, 0L); + right.writeElement(NodeId.fromInt(6), 9L, 0L); + right.writeElement(NodeId.fromInt(9), 5L, 0L); assert ContextRelationship.DISJOINT == - cc.diff(left, right); + cc.diff(left.context, right.context); // disjoint: left has less nodes, but higher counts - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 5L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 3L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 2L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 5L, 0L); + left.writeElement(NodeId.fromInt(6), 3L, 0L); + left.writeElement(NodeId.fromInt(9), 2L, 0L); - right = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 4L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 3L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 2L, 0L); - cc.writeElementAtOffset(right, 3 * stepLength, FBUtilities.toByteArray(12), 1L, 0L); + right = ContextState.allocate(4, 0); + right.writeElement(NodeId.fromInt(3), 4L, 0L); + right.writeElement(NodeId.fromInt(6), 3L, 0L); + right.writeElement(NodeId.fromInt(9), 2L, 0L); + right.writeElement(NodeId.fromInt(12), 1L, 0L); assert ContextRelationship.DISJOINT == - cc.diff(left, right); + cc.diff(left.context, right.context); // disjoint: mixed nodes and counts - left = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 5L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(9), 2L, 0L); + left = ContextState.allocate(3, 0); + left.writeElement(NodeId.fromInt(3), 5L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 2L, 0L); - right = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 4L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 3L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 2L, 0L); - cc.writeElementAtOffset(right, 3 * stepLength, FBUtilities.toByteArray(12), 1L, 0L); + right = ContextState.allocate(4, 0); + right.writeElement(NodeId.fromInt(3), 4L, 0L); + right.writeElement(NodeId.fromInt(6), 3L, 0L); + right.writeElement(NodeId.fromInt(9), 2L, 0L); + right.writeElement(NodeId.fromInt(12), 1L, 0L); assert ContextRelationship.DISJOINT == - cc.diff(left, right); + cc.diff(left.context, right.context); - left = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(3), 5L, 0L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(6), 2L, 0L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(7), 2L, 0L); - cc.writeElementAtOffset(left, 3 * stepLength, FBUtilities.toByteArray(9), 2L, 0L); + left = ContextState.allocate(4, 0); + left.writeElement(NodeId.fromInt(3), 5L, 0L); + left.writeElement(NodeId.fromInt(6), 2L, 0L); + left.writeElement(NodeId.fromInt(7), 2L, 0L); + left.writeElement(NodeId.fromInt(9), 2L, 0L); - right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(3), 4L, 0L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(6), 3L, 0L); - cc.writeElementAtOffset(right, 2 * stepLength, FBUtilities.toByteArray(9), 2L, 0L); + right = ContextState.allocate(3, 0); + right.writeElement(NodeId.fromInt(3), 4L, 0L); + right.writeElement(NodeId.fromInt(6), 3L, 0L); + right.writeElement(NodeId.fromInt(9), 2L, 0L); assert ContextRelationship.DISJOINT == - cc.diff(left, right); + cc.diff(left.context, right.context); } @Test public void testMerge() { // note: local counts aggregated; remote counts are reconciled (i.e. take max) - ByteBuffer left = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(1), 1L, 1L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(2), 2L, 2L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(4), 6L, 3L); - cc.writeElementAtOffset( - left, - 3 * stepLength, - FBUtilities.getLocalAddress().getAddress(), - 7L, - 3L); + ContextState left = ContextState.allocate(4, 1); + left.writeElement(NodeId.fromInt(1), 1L, 1L); + left.writeElement(NodeId.fromInt(2), 2L, 2L); + left.writeElement(NodeId.fromInt(4), 6L, 3L); + left.writeElement(NodeId.getLocalId(), 7L, 3L, true); - ByteBuffer right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(4), 4L, 4L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(5), 5L, 5L); - cc.writeElementAtOffset( - right, - 2 * stepLength, - FBUtilities.getLocalAddress().getAddress(), - 2L, - 9L); + ContextState right = ContextState.allocate(3, 1); + right.writeElement(NodeId.fromInt(4), 4L, 4L); + right.writeElement(NodeId.fromInt(5), 5L, 5L); + right.writeElement(NodeId.getLocalId(), 2L, 9L, true); - ByteBuffer merged = cc.merge(left, right); + ByteBuffer merged = cc.merge(left.context, right.context); + int hd = 4; - assertEquals(5 * stepLength, merged.remaining()); + assertEquals(hd + 5 * stepLength, merged.remaining()); // local node id's counts are aggregated - assertEquals(0, ByteBufferUtil.compareSubArrays( - ByteBuffer.wrap(FBUtilities.getLocalAddress().getAddress()), - 0, - merged, - 4*stepLength, - 4)); - assertEquals( 9L, merged.getLong(4*stepLength + idLength)); - assertEquals(12L, merged.getLong(4*stepLength + idLength + clockLength)); + assert Util.equalsNodeId(NodeId.getLocalId(), merged, hd + 4*stepLength); + assertEquals( 9L, merged.getLong(hd + 4*stepLength + idLength)); + assertEquals(12L, merged.getLong(hd + 4*stepLength + idLength + clockLength)); // remote node id counts are reconciled (i.e. take max) - assertEquals( 4, merged.getInt( 2*stepLength)); - assertEquals( 6L, merged.getLong(2*stepLength + idLength)); - assertEquals( 3L, merged.getLong(2*stepLength + idLength + clockLength)); + assert Util.equalsNodeId(NodeId.fromInt(4), merged, hd + 2*stepLength); + assertEquals( 6L, merged.getLong(hd + 2*stepLength + idLength)); + assertEquals( 3L, merged.getLong(hd + 2*stepLength + idLength + clockLength)); - assertEquals( 5, merged.getInt( 3*stepLength)); - assertEquals( 5L, merged.getLong(3*stepLength + idLength)); - assertEquals( 5L, merged.getLong(3*stepLength + idLength + clockLength)); + assert Util.equalsNodeId(NodeId.fromInt(5), merged, hd + 3*stepLength); + assertEquals( 5L, merged.getLong(hd + 3*stepLength + idLength)); + assertEquals( 5L, merged.getLong(hd + 3*stepLength + idLength + clockLength)); - assertEquals( 2, merged.getInt( 1*stepLength)); - assertEquals( 2L, merged.getLong(1*stepLength + idLength)); - assertEquals( 2L, merged.getLong(1*stepLength + idLength + clockLength)); + assert Util.equalsNodeId(NodeId.fromInt(2), merged, hd + 1*stepLength); + assertEquals( 2L, merged.getLong(hd + 1*stepLength + idLength)); + assertEquals( 2L, merged.getLong(hd + 1*stepLength + idLength + clockLength)); - assertEquals( 1, merged.getInt( 0*stepLength)); - assertEquals( 1L, merged.getLong(0*stepLength + idLength)); - assertEquals( 1L, merged.getLong(0*stepLength + idLength + clockLength)); + assert Util.equalsNodeId(NodeId.fromInt(1), merged, hd + 0*stepLength); + assertEquals( 1L, merged.getLong(hd + 0*stepLength + idLength)); + assertEquals( 1L, merged.getLong(hd + 0*stepLength + idLength + clockLength)); } @Test public void testTotal() { - ByteBuffer left = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(left, 0 * stepLength, FBUtilities.toByteArray(1), 1L, 1L); - cc.writeElementAtOffset(left, 1 * stepLength, FBUtilities.toByteArray(2), 2L, 2L); - cc.writeElementAtOffset(left, 2 * stepLength, FBUtilities.toByteArray(4), 3L, 3L); - cc.writeElementAtOffset( - left, - 3 * stepLength, - FBUtilities.getLocalAddress().getAddress(), - 3L, - 3L); + ContextState left = ContextState.allocate(4, 1); + left.writeElement(NodeId.fromInt(1), 1L, 1L); + left.writeElement(NodeId.fromInt(2), 2L, 2L); + left.writeElement(NodeId.fromInt(4), 3L, 3L); + left.writeElement(NodeId.getLocalId(), 3L, 3L, true); - ByteBuffer right = ByteBuffer.allocate(3 * stepLength); - cc.writeElementAtOffset(right, 0 * stepLength, FBUtilities.toByteArray(4), 4L, 4L); - cc.writeElementAtOffset(right, 1 * stepLength, FBUtilities.toByteArray(5), 5L, 5L); - cc.writeElementAtOffset( - right, - 2 * stepLength, - FBUtilities.getLocalAddress().getAddress(), - 9L, - 9L); + ContextState right = ContextState.allocate(3, 1); + right.writeElement(NodeId.fromInt(4), 4L, 4L); + right.writeElement(NodeId.fromInt(5), 5L, 5L); + right.writeElement(NodeId.getLocalId(), 9L, 9L, true); - ByteBuffer merged = cc.merge(left, right); + ByteBuffer merged = cc.merge(left.context, right.context); // 127.0.0.1: 12 (3+9) // 0.0.0.1: 1 @@ -353,30 +317,100 @@ public class CounterContextTest } @Test - public void testCleanNodeCounts() throws UnknownHostException + public void testMergeOldShards() { - ByteBuffer bytes = ByteBuffer.allocate(4 * stepLength); - cc.writeElementAtOffset(bytes, 0 * stepLength, FBUtilities.toByteArray(1), 1L, 1L); - cc.writeElementAtOffset(bytes, 1 * stepLength, FBUtilities.toByteArray(2), 2L, 2L); - cc.writeElementAtOffset(bytes, 2 * stepLength, FBUtilities.toByteArray(4), 3L, 3L); - cc.writeElementAtOffset(bytes, 3 * stepLength, FBUtilities.toByteArray(8), 4L, 4L); + long now = System.currentTimeMillis(); + NodeId id1 = NodeId.fromInt(1); + NodeId id3 = NodeId.fromInt(3); + List records = new ArrayList(); + records.add(new NodeId.NodeIdRecord(id1, 2L)); + records.add(new NodeId.NodeIdRecord(id3, 4L)); - assertEquals(4, bytes.getInt( 2*stepLength)); - assertEquals(3L, bytes.getLong(2*stepLength + idLength)); + // Destination of merge is a delta + ContextState ctx = ContextState.allocate(5, 2); + ctx.writeElement(id1, 1L, 1L); + ctx.writeElement(NodeId.fromInt(2), 2L, 2L); + ctx.writeElement(id3, 3L, 3L, true); + ctx.writeElement(NodeId.fromInt(4), 6L, 3L); + ctx.writeElement(NodeId.fromInt(5), 7L, 3L, true); - bytes = cc.cleanNodeCounts(bytes, InetAddress.getByAddress(FBUtilities.toByteArray(4))); + ByteBuffer merger = cc.computeOldShardMerger(ctx.context, records); + ContextState m = new ContextState(merger); - // node: 0.0.0.4 should be removed - assertEquals(3 * stepLength, bytes.remaining()); + assert m.getNodeId().equals(id1); + assert m.getClock() <= -now; + assert m.getCount() == 0; + m.moveToNext(); + assert m.getNodeId().equals(id3); + assert m.getClock() == 4L; + assert m.getCount() == 1L; + assert cc.total(ctx.context) == cc.total(cc.merge(ctx.context, merger)); - // other nodes should be unaffected - assertEquals(1, bytes.getInt( 0*stepLength)); - assertEquals(1L, bytes.getLong(0*stepLength + idLength)); + // Source of merge is a delta + ctx = ContextState.allocate(4, 1); + ctx.writeElement(id1, 1L, 1L, true); + ctx.writeElement(NodeId.fromInt(2), 2L, 2L); + ctx.writeElement(id3, 3L, 3L); + ctx.writeElement(NodeId.fromInt(4), 6L, 3L); - assertEquals(2, bytes.getInt( 1*stepLength)); - assertEquals(2L, bytes.getLong(1*stepLength + idLength)); + merger = cc.computeOldShardMerger(ctx.context, records); + assert cc.total(ctx.context) == cc.total(cc.merge(ctx.context, merger)); + + // source and destination of merge are deltas + ctx = ContextState.allocate(4, 2); + ctx.writeElement(id1, 1L, 1L, true); + ctx.writeElement(NodeId.fromInt(2), 2L, 2L); + ctx.writeElement(id3, 3L, 3L, true); + ctx.writeElement(NodeId.fromInt(4), 6L, 3L); + + merger = cc.computeOldShardMerger(ctx.context, records); + assert cc.total(ctx.context) == cc.total(cc.merge(ctx.context, merger)); + + // none of source and destination of merge are deltas + ctx = ContextState.allocate(4, 0); + ctx.writeElement(id1, 1L, 1L); + ctx.writeElement(NodeId.fromInt(2), 2L, 2L); + ctx.writeElement(id3, 3L, 3L); + ctx.writeElement(NodeId.fromInt(4), 6L, 3L); + + merger = cc.computeOldShardMerger(ctx.context, records); + assert cc.total(ctx.context) == cc.total(cc.merge(ctx.context, merger)); + } + + @Test + public void testRemoveOldShards() + { + NodeId id1 = NodeId.fromInt(1); + NodeId id3 = NodeId.fromInt(3); + NodeId id6 = NodeId.fromInt(6); + List records = new ArrayList(); + records.add(new NodeId.NodeIdRecord(id1, 2L)); + records.add(new NodeId.NodeIdRecord(id3, 4L)); + records.add(new NodeId.NodeIdRecord(id6, 10L)); + + ContextState ctx = ContextState.allocate(6, 2); + ctx.writeElement(id1, 1L, 1L); + ctx.writeElement(NodeId.fromInt(2), 2L, 2L); + ctx.writeElement(id3, 3L, 3L, true); + ctx.writeElement(NodeId.fromInt(4), 6L, 3L); + ctx.writeElement(NodeId.fromInt(5), 7L, 3L, true); + ctx.writeElement(id6, 5L, 6L); + + ByteBuffer merger = cc.computeOldShardMerger(ctx.context, records); + ByteBuffer merged = cc.merge(ctx.context, merger); + assert cc.total(ctx.context) == cc.total(merged); + + ByteBuffer cleaned = cc.removeOldShards(merged, (int)(System.currentTimeMillis() / 1000) + 1); + assert cc.total(ctx.context) == cc.total(cleaned); + assert cleaned.remaining() == ctx.context.remaining() - stepLength; + + merger = cc.computeOldShardMerger(cleaned, records); + merged = cc.merge(cleaned, merger); + assert cc.total(ctx.context) == cc.total(merged); + + cleaned = cc.removeOldShards(merged, (int)(System.currentTimeMillis() / 1000) + 1); + assert cc.total(ctx.context) == cc.total(cleaned); + assert cleaned.remaining() == ctx.context.remaining() - 2 * stepLength - 2; - assertEquals(8, bytes.getInt( 2*stepLength)); - assertEquals(4L, bytes.getLong(2*stepLength + idLength)); } } diff --git a/test/unit/org/apache/cassandra/io/sstable/SSTableWriterAESCommutativeTest.java b/test/unit/org/apache/cassandra/io/sstable/SSTableWriterCommutativeTest.java similarity index 61% rename from test/unit/org/apache/cassandra/io/sstable/SSTableWriterAESCommutativeTest.java rename to test/unit/org/apache/cassandra/io/sstable/SSTableWriterCommutativeTest.java index 0d25ec4c30..c714266c7c 100644 --- a/test/unit/org/apache/cassandra/io/sstable/SSTableWriterAESCommutativeTest.java +++ b/test/unit/org/apache/cassandra/io/sstable/SSTableWriterCommutativeTest.java @@ -43,16 +43,17 @@ import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.streaming.OperationType; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.NodeId; import org.junit.Test; import org.apache.cassandra.utils.ByteBufferUtil; -public class SSTableWriterAESCommutativeTest extends CleanupHelper +public class SSTableWriterCommutativeTest extends CleanupHelper { private static final CounterContext cc = new CounterContext(); private static final CounterColumnType ctype = CounterColumnType.instance; @Test - public void testRecoverAndOpenAESCommutative() throws IOException, ExecutionException, InterruptedException, UnknownHostException + public void testRecoverAndOpenCommutative() throws IOException, ExecutionException, InterruptedException, UnknownHostException { String keyspace = "Keyspace1"; String cfname = "Counter1"; @@ -63,33 +64,25 @@ public class SSTableWriterAESCommutativeTest extends CleanupHelper DataOutputBuffer buffer; ColumnFamily cf = ColumnFamily.create(keyspace, cfname); - byte[] context; + ColumnFamily cfCleaned = ColumnFamily.create(keyspace, cfname); + CounterContext.ContextState state; // key: k - context = Util.concatByteArrays( - FBUtilities.getLocalAddress().getAddress(), - FBUtilities.toByteArray(9L), - FBUtilities.toByteArray(3L), - FBUtilities.toByteArray(2), FBUtilities.toByteArray(4L), FBUtilities.toByteArray(2L), - FBUtilities.toByteArray(4), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(3L), - FBUtilities.toByteArray(8), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(4L) - ); - cf.addColumn(new CounterColumn( - ByteBufferUtil.bytes("x"), - ByteBuffer.wrap(context), - 0L)); - context = Util.concatByteArrays( - FBUtilities.toByteArray(1), FBUtilities.toByteArray(7L), FBUtilities.toByteArray(12L), - FBUtilities.getLocalAddress().getAddress(), - FBUtilities.toByteArray(5L), - FBUtilities.toByteArray(3L), - FBUtilities.toByteArray(3), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(33L), - FBUtilities.toByteArray(9), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(24L) - ); - cf.addColumn(new CounterColumn( - ByteBufferUtil.bytes("y"), - ByteBuffer.wrap(context), - 0L)); + state = CounterContext.ContextState.allocate(4, 1); + state.writeElement(NodeId.fromInt(2), 9L, 3L, true); + state.writeElement(NodeId.fromInt(4), 4L, 2L); + state.writeElement(NodeId.fromInt(6), 3L, 3L); + state.writeElement(NodeId.fromInt(8), 2L, 4L); + cf.addColumn(new CounterColumn( ByteBufferUtil.bytes("x"), state.context, 0L)); + cfCleaned.addColumn(new CounterColumn( ByteBufferUtil.bytes("x"), cc.clearAllDelta(state.context), 0L)); + + state = CounterContext.ContextState.allocate(4, 1); + state.writeElement(NodeId.fromInt(1), 7L, 12L); + state.writeElement(NodeId.fromInt(2), 5L, 3L, true); + state.writeElement(NodeId.fromInt(3), 2L, 33L); + state.writeElement(NodeId.fromInt(9), 1L, 24L); + cf.addColumn(new CounterColumn( ByteBufferUtil.bytes("y"), state.context, 0L)); + cfCleaned.addColumn(new CounterColumn( ByteBufferUtil.bytes("y"), cc.clearAllDelta(state.context), 0L)); buffer = new DataOutputBuffer(); ColumnFamily.serializer().serializeWithIndexes(cf, buffer); @@ -98,38 +91,31 @@ public class SSTableWriterAESCommutativeTest extends CleanupHelper ByteBuffer.wrap(Arrays.copyOf(buffer.getData(), buffer.getLength())) ); - ctype.cleanContext(cf, FBUtilities.getLocalAddress()); buffer = new DataOutputBuffer(); - ColumnFamily.serializer().serializeWithIndexes(cf, buffer); + ColumnFamily.serializer().serializeWithIndexes(cfCleaned, buffer); cleanedEntries.put( ByteBufferUtil.bytes("k"), ByteBuffer.wrap(Arrays.copyOf(buffer.getData(), buffer.getLength())) ); - + cf.clear(); + cfCleaned.clear(); // key: l - context = Util.concatByteArrays( - FBUtilities.getLocalAddress().getAddress(), - FBUtilities.toByteArray(9L), - FBUtilities.toByteArray(3L), - FBUtilities.toByteArray(2), FBUtilities.toByteArray(4L), FBUtilities.toByteArray(2L), - FBUtilities.toByteArray(4), FBUtilities.toByteArray(3L), FBUtilities.toByteArray(3L), - FBUtilities.toByteArray(8), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(4L) - ); - cf.addColumn(new CounterColumn( - ByteBufferUtil.bytes("x"), - ByteBuffer.wrap(context), - 0L)); - context = Util.concatByteArrays( - FBUtilities.toByteArray(1), FBUtilities.toByteArray(7L), FBUtilities.toByteArray(12L), - FBUtilities.toByteArray(3), FBUtilities.toByteArray(2L), FBUtilities.toByteArray(33L), - FBUtilities.toByteArray(9), FBUtilities.toByteArray(1L), FBUtilities.toByteArray(24L) - ); - cf.addColumn(new CounterColumn( - ByteBufferUtil.bytes("y"), - ByteBuffer.wrap(context), - 0L)); + state = CounterContext.ContextState.allocate(4, 1); + state.writeElement(NodeId.fromInt(2), 9L, 3L, true); + state.writeElement(NodeId.fromInt(4), 4L, 2L); + state.writeElement(NodeId.fromInt(6), 3L, 3L); + state.writeElement(NodeId.fromInt(8), 2L, 4L); + cf.addColumn(new CounterColumn( ByteBufferUtil.bytes("x"), state.context, 0L)); + cfCleaned.addColumn(new CounterColumn( ByteBufferUtil.bytes("x"), cc.clearAllDelta(state.context), 0L)); + + state = CounterContext.ContextState.allocate(3, 0); + state.writeElement(NodeId.fromInt(1), 7L, 12L); + state.writeElement(NodeId.fromInt(3), 2L, 33L); + state.writeElement(NodeId.fromInt(9), 1L, 24L); + cf.addColumn(new CounterColumn( ByteBufferUtil.bytes("y"), state.context, 0L)); + cfCleaned.addColumn(new CounterColumn( ByteBufferUtil.bytes("y"), cc.clearAllDelta(state.context), 0L)); buffer = new DataOutputBuffer(); ColumnFamily.serializer().serializeWithIndexes(cf, buffer); @@ -138,15 +124,15 @@ public class SSTableWriterAESCommutativeTest extends CleanupHelper ByteBuffer.wrap(Arrays.copyOf(buffer.getData(), buffer.getLength())) ); - ctype.cleanContext(cf, FBUtilities.getLocalAddress()); buffer = new DataOutputBuffer(); - ColumnFamily.serializer().serializeWithIndexes(cf, buffer); + ColumnFamily.serializer().serializeWithIndexes(cfCleaned, buffer); cleanedEntries.put( ByteBufferUtil.bytes("l"), ByteBuffer.wrap(Arrays.copyOf(buffer.getData(), buffer.getLength())) ); cf.clear(); + cfCleaned.clear(); // write out unmodified CF SSTableReader orig = SSTableUtils.prepare().ks(keyspace).cf(cfname).generation(0).writeRaw(entries); diff --git a/test/unit/org/apache/cassandra/service/AntiEntropyServiceTest.java b/test/unit/org/apache/cassandra/service/AntiEntropyServiceTest.java index 7402233469..d3c3424cea 100644 --- a/test/unit/org/apache/cassandra/service/AntiEntropyServiceTest.java +++ b/test/unit/org/apache/cassandra/service/AntiEntropyServiceTest.java @@ -137,12 +137,10 @@ public class AntiEntropyServiceTest extends CleanupHelper validator.prepare(store); // add a row with the minimum token - validator.add(new PrecompactedRow(new DecoratedKey(min, ByteBufferUtil.bytes("nonsense!")), - new DataOutputBuffer())); + validator.add(new PrecompactedRow(new DecoratedKey(min, ByteBufferUtil.bytes("nonsense!")), null)); // and a row after it - validator.add(new PrecompactedRow(new DecoratedKey(mid, ByteBufferUtil.bytes("inconceivable!")), - new DataOutputBuffer())); + validator.add(new PrecompactedRow(new DecoratedKey(mid, ByteBufferUtil.bytes("inconceivable!")), null)); validator.complete(); // confirm that the tree was validated diff --git a/test/unit/org/apache/cassandra/service/AntiEntropyServiceTestAbstract.java b/test/unit/org/apache/cassandra/service/AntiEntropyServiceTestAbstract.java index a57574576a..6147e3ca75 100644 --- a/test/unit/org/apache/cassandra/service/AntiEntropyServiceTestAbstract.java +++ b/test/unit/org/apache/cassandra/service/AntiEntropyServiceTestAbstract.java @@ -151,12 +151,10 @@ public abstract class AntiEntropyServiceTestAbstract extends CleanupHelper validator.prepare(store); // add a row with the minimum token - validator.add(new PrecompactedRow(new DecoratedKey(min, ByteBufferUtil.bytes("nonsense!")), - new DataOutputBuffer())); + validator.add(new PrecompactedRow(new DecoratedKey(min, ByteBufferUtil.bytes("nonsense!")), null)); // and a row after it - validator.add(new PrecompactedRow(new DecoratedKey(mid, ByteBufferUtil.bytes("inconceivable!")), - new DataOutputBuffer())); + validator.add(new PrecompactedRow(new DecoratedKey(mid, ByteBufferUtil.bytes("inconceivable!")), null)); validator.complete(); // confirm that the tree was validated