Merge branch 'cassandra-2.0' into cassandra-2.1 Conflicts: src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/5e90091d Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/5e90091d Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/5e90091d Branch: refs/heads/trunk Commit: 5e90091d1ef940bcba65a87bf14fc1e809503627 Parents: a3dc7b8 7801aab Author: Aleksey Yeschenko <alek...@apache.org> Authored: Tue Jun 17 17:42:32 2014 -0700 Committer: Aleksey Yeschenko <alek...@apache.org> Committed: Tue Jun 17 17:42:32 2014 -0700 ---------------------------------------------------------------------- CHANGES.txt | 4 +- .../db/index/SecondaryIndexManager.java | 28 +++++++++- .../cassandra/db/ColumnFamilyStoreTest.java | 55 ++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/5e90091d/CHANGES.txt ---------------------------------------------------------------------- diff --cc CHANGES.txt index fd7c62b,6a50186..4c9e69d --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,26 -1,25 +1,28 @@@ -2.0.9 +2.1.0 + * Avoid incremental compaction on Windows (CASSANDRA-7365) + * Fix exception when querying a composite-keyed table with a collection index + (CASSANDRA-7372) + * Use node's host id in place of counter ids (CASSANDRA-7366) * Fix native protocol CAS batches (CASSANDRA-7337) + * Reduce likelihood of contention on local paxos locking (CASSANDRA-7359) + * Upgrade to Pig 0.12.1 (CASSANDRA-6556) + * Make sure we clear out repair sessions from netstats (CASSANDRA-7329) + * Don't fail streams on failure detector downs (CASSANDRA-3569) + * Add optional keyspace to DROP INDEX statement (CASSANDRA-7314) + * Reduce run time for CQL tests (CASSANDRA-7327) + * Fix heap size calculation on Windows (CASSANDRA-7352) + * RefCount native frames from netty (CASSANDRA-7245) + * Use tarball dir instead of /var for default paths (CASSANDRA-7136) +Merged from 2.0: * Add per-CF range read request latency metrics (CASSANDRA-7338) * Fix NPE in StreamTransferTask.createMessageForRetry() (CASSANDRA-7323) - * Add conditional CREATE/DROP USER support (CASSANDRA-7264) - * Swap local and global default read repair chances (CASSANDRA-7320) - * Add missing iso8601 patterns for date strings (CASSANDRA-6973) - * Support selecting multiple rows in a partition using IN (CASSANDRA-6875) - * cqlsh: always emphasize the partition key in DESC output (CASSANDRA-7274) - * Copy compaction options to make sure they are reloaded (CASSANDRA-7290) - * Add option to do more aggressive tombstone compactions (CASSANDRA-6563) - * Don't try to compact already-compacting files in HHOM (CASSANDRA-7288) - * Add authentication support to shuffle (CASSANDRA-6484) - * Cqlsh counts non-empty lines for "Blank lines" warning (CASSANDRA-7325) * Make StreamSession#closeSession() idempotent (CASSANDRA-7262) * Fix infinite loop on exception while streaming (CASSANDRA-7330) - * Reference sstables before populating key cache (CASSANDRA-7234) Merged from 1.2: + * Don't insert tombstones that hide indexed values into 2i (CASSANDRA-7268) * Track metrics at a keyspace level (CASSANDRA-6539) - * Add replace_address_first_boot flag to only replace if not bootstrapped (CASSANDRA-7356) + * Add replace_address_first_boot flag to only replace if not bootstrapped + (CASSANDRA-7356) * Enable keepalive for native protocol (CASSANDRA-7380) * Check internal addresses for seeds (CASSANDRA-6523) * Fix potential / by 0 in HHOM page size calculation (CASSANDRA-7354) http://git-wip-us.apache.org/repos/asf/cassandra/blob/5e90091d/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java index 36c7e1e,2c0d611..f78dc86 --- a/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java +++ b/src/java/org/apache/cassandra/db/index/SecondaryIndexManager.java @@@ -710,10 -628,18 +710,20 @@@ public class SecondaryIndexManage { if (index instanceof PerColumnSecondaryIndex) { - // insert the new value before removing the old one, so we never have a period - // where the row is invisible to both queries (the opposite seems preferable); see CASSANDRA-5540 - if (!column.isMarkedForDelete(System.currentTimeMillis())) - ((PerColumnSecondaryIndex) index).insert(key.key, column); - - // Usually we want to delete the old value from the index, except when - // name/value/timestamp are all equal, but the columns themselves - // are not (as is the case when overwriting expiring columns with - // identical values and ttl) Then, we don't want to delete as the - // tombstone will hide the new value we just inserted; see CASSANDRA-7268 - if (shouldCleanupOldValue(oldColumn, column)) - ((PerColumnSecondaryIndex) index).delete(key.key, oldColumn); + if (cell.isLive()) ++ { + ((PerColumnSecondaryIndex) index).update(key.getKey(), oldCell, cell, opGroup); ++ } + else - ((PerColumnSecondaryIndex) index).delete(key.getKey(), oldCell, opGroup); ++ { ++ // Usually we want to delete the old value from the index, except when ++ // name/value/timestamp are all equal, but the columns themselves ++ // are not (as is the case when overwriting expiring columns with ++ // identical values and ttl) Then, we don't want to delete as the ++ // tombstone will hide the new value we just inserted; see CASSANDRA-7268 ++ if (shouldCleanupOldValue(oldCell, cell)) ++ ((PerColumnSecondaryIndex) index).delete(key.getKey(), oldCell, opGroup); ++ } } } } @@@ -731,7 -657,23 +741,23 @@@ public void updateRowLevelIndexes() { for (SecondaryIndex index : rowLevelIndexMap.values()) - ((PerRowSecondaryIndex) index).index(key.key, cf); + ((PerRowSecondaryIndex) index).index(key.getKey(), cf); } + - private boolean shouldCleanupOldValue(Column oldColumn, Column newColumn) ++ private boolean shouldCleanupOldValue(Cell oldCell, Cell newCell) + { + // If any one of name/value/timestamp are different, then we + // should delete from the index. If not, then we can infer that - // at least one of the columns is an ExpiringColumn and that the ++ // at least one of the cells is an ExpiringColumn and that the + // difference is in the expiry time. In this case, we don't want to + // delete the old value from the index as the tombstone we insert + // will just hide the inserted value. - // Completely identical columns (including expiring columns with ++ // Completely identical cells (including expiring columns with + // identical ttl & localExpirationTime) will not get this far due - // to the oldColumn.equals(newColumn) in StandardUpdater.update - return !oldColumn.name().equals(newColumn.name()) - || !oldColumn.value().equals(newColumn.value()) - || oldColumn.timestamp() != newColumn.timestamp(); ++ // to the oldCell.equals(newColumn) in StandardUpdater.update ++ return !oldCell.name().equals(newCell.name()) ++ || !oldCell.value().equals(newCell.value()) ++ || oldCell.timestamp() != newCell.timestamp(); + } } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/5e90091d/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java index b178e48,611d6af..dda9b65 --- a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java +++ b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java @@@ -22,22 -22,10 +22,23 @@@ import java.io.File import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; + import java.util.concurrent.TimeUnit; import com.google.common.base.Function; import com.google.common.collect.Iterables; @@@ -453,6 -425,60 +454,60 @@@ public class ColumnFamilyStoreTest exte } @Test + public void testIndexUpdateOverwritingExpiringColumns() throws Exception + { + // see CASSANDRA-7268 + Keyspace keyspace = Keyspace.open("Keyspace2"); + + // create a row and update the birthdate value with an expiring column - RowMutation rm; - rm = new RowMutation("Keyspace2", ByteBufferUtil.bytes("k100")); - rm.add("Indexed1", ByteBufferUtil.bytes("birthdate"), ByteBufferUtil.bytes(100L), 1, 1000); ++ Mutation rm; ++ rm = new Mutation("Keyspace2", ByteBufferUtil.bytes("k100")); ++ rm.add("Indexed1", cellname("birthdate"), ByteBufferUtil.bytes(100L), 1, 1000); + rm.apply(); + - IndexExpression expr = new IndexExpression(ByteBufferUtil.bytes("birthdate"), IndexOperator.EQ, ByteBufferUtil.bytes(100L)); ++ IndexExpression expr = new IndexExpression(ByteBufferUtil.bytes("birthdate"), IndexExpression.Operator.EQ, ByteBufferUtil.bytes(100L)); + List<IndexExpression> clause = Arrays.asList(expr); + IDiskAtomFilter filter = new IdentityQueryFilter(); + Range<RowPosition> range = Util.range("", ""); + List<Row> rows = keyspace.getColumnFamilyStore("Indexed1").search(range, clause, filter, 100); + assertEquals(1, rows.size()); + + // requires a 1s sleep because we calculate local expiry time as (now() / 1000) + ttl + TimeUnit.SECONDS.sleep(1); + + // now overwrite with the same name/value/ttl, but the local expiry time will be different - rm = new RowMutation("Keyspace2", ByteBufferUtil.bytes("k100")); - rm.add("Indexed1", ByteBufferUtil.bytes("birthdate"), ByteBufferUtil.bytes(100L), 1, 1000); ++ rm = new Mutation("Keyspace2", ByteBufferUtil.bytes("k100")); ++ rm.add("Indexed1", cellname("birthdate"), ByteBufferUtil.bytes(100L), 1, 1000); + rm.apply(); + + rows = keyspace.getColumnFamilyStore("Indexed1").search(range, clause, filter, 100); + assertEquals(1, rows.size()); + + // check that modifying the indexed value using the same timestamp behaves as expected - rm = new RowMutation("Keyspace2", ByteBufferUtil.bytes("k101")); - rm.add("Indexed1", ByteBufferUtil.bytes("birthdate"), ByteBufferUtil.bytes(101L), 1, 1000); ++ rm = new Mutation("Keyspace2", ByteBufferUtil.bytes("k101")); ++ rm.add("Indexed1", cellname("birthdate"), ByteBufferUtil.bytes(101L), 1, 1000); + rm.apply(); + - expr = new IndexExpression(ByteBufferUtil.bytes("birthdate"), IndexOperator.EQ, ByteBufferUtil.bytes(101L)); ++ expr = new IndexExpression(ByteBufferUtil.bytes("birthdate"), IndexExpression.Operator.EQ, ByteBufferUtil.bytes(101L)); + clause = Arrays.asList(expr); + rows = keyspace.getColumnFamilyStore("Indexed1").search(range, clause, filter, 100); + assertEquals(1, rows.size()); + + TimeUnit.SECONDS.sleep(1); - rm = new RowMutation("Keyspace2", ByteBufferUtil.bytes("k101")); - rm.add("Indexed1", ByteBufferUtil.bytes("birthdate"), ByteBufferUtil.bytes(102L), 1, 1000); ++ rm = new Mutation("Keyspace2", ByteBufferUtil.bytes("k101")); ++ rm.add("Indexed1", cellname("birthdate"), ByteBufferUtil.bytes(102L), 1, 1000); + rm.apply(); + // search for the old value + rows = keyspace.getColumnFamilyStore("Indexed1").search(range, clause, filter, 100); + assertEquals(0, rows.size()); + // and for the new - expr = new IndexExpression(ByteBufferUtil.bytes("birthdate"), IndexOperator.EQ, ByteBufferUtil.bytes(102L)); ++ expr = new IndexExpression(ByteBufferUtil.bytes("birthdate"), IndexExpression.Operator.EQ, ByteBufferUtil.bytes(102L)); + clause = Arrays.asList(expr); + rows = keyspace.getColumnFamilyStore("Indexed1").search(range, clause, filter, 100); + assertEquals(1, rows.size()); + } + + @Test public void testDeleteOfInconsistentValuesInKeysIndex() throws Exception { String keySpace = "Keyspace2";