Repository: cassandra Updated Branches: refs/heads/cassandra-2.0 1a096efeb -> 7110d980c
Track min/max timestamps and maxLocalDeletionTime correctly Patch by marcuse; reviewed by tjake for CASSANDRA-7969 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/7110d980 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/7110d980 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/7110d980 Branch: refs/heads/cassandra-2.0 Commit: 7110d980c31ea23f0b6933bc247964cec75af9cb Parents: 1a096ef Author: Marcus Eriksson <[email protected]> Authored: Thu Sep 18 09:02:52 2014 +0200 Committer: Marcus Eriksson <[email protected]> Committed: Thu Oct 2 10:05:53 2014 +0200 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../org/apache/cassandra/db/ColumnFamily.java | 27 ++- .../org/apache/cassandra/db/DeletionTime.java | 10 + .../db/compaction/LazilyCompactedRow.java | 36 ++- .../cassandra/io/sstable/ColumnStats.java | 67 ++++++ .../cassandra/io/sstable/SSTableWriter.java | 32 +-- .../cql3/SSTableMetadataTrackingTest.java | 223 +++++++++++++++++++ .../apache/cassandra/db/RangeTombstoneTest.java | 94 ++++++++ 8 files changed, 455 insertions(+), 35 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/7110d980/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 3454928..c9b4e59 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ 2.0.11: * Better validation of collection values (CASSANDRA-7833) + * Track min/max timestamps correctly (CASSANDRA-7969) * Fix possible overflow while sorting CL segments for replay (CASSANDRA-7992) * Increase nodetool Xmx (CASSANDRA-7956) * Archive any commitlog segments present at startup (CASSANDRA-6904) http://git-wip-us.apache.org/repos/asf/cassandra/blob/7110d980/src/java/org/apache/cassandra/db/ColumnFamily.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/ColumnFamily.java b/src/java/org/apache/cassandra/db/ColumnFamily.java index 95e5645..7edf825 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamily.java +++ b/src/java/org/apache/cassandra/db/ColumnFamily.java @@ -412,38 +412,47 @@ public abstract class ColumnFamily implements Iterable<Column>, IRowCacheEntry public ColumnStats getColumnStats() { - long minTimestampSeen = deletionInfo().isLive() ? Long.MAX_VALUE : deletionInfo().minTimestamp(); - long maxTimestampSeen = deletionInfo().maxTimestamp(); + // note that we default to MIN_VALUE/MAX_VALUE here to be able to override them later in this method + // we are checking row/range tombstones and actual cells - there should always be data that overrides + // these with actual values + ColumnStats.MinTracker<Long> minTimestampTracker = new ColumnStats.MinTracker<>(Long.MIN_VALUE); + ColumnStats.MaxTracker<Long> maxTimestampTracker = new ColumnStats.MaxTracker<>(Long.MAX_VALUE); StreamingHistogram tombstones = new StreamingHistogram(SSTable.TOMBSTONE_HISTOGRAM_BIN_SIZE); - int maxLocalDeletionTime = Integer.MIN_VALUE; + ColumnStats.MaxTracker<Integer> maxDeletionTimeTracker = new ColumnStats.MaxTracker<>(Integer.MAX_VALUE); List<ByteBuffer> minColumnNamesSeen = Collections.emptyList(); List<ByteBuffer> maxColumnNamesSeen = Collections.emptyList(); if (deletionInfo().getTopLevelDeletion().localDeletionTime < Integer.MAX_VALUE) + { tombstones.update(deletionInfo().getTopLevelDeletion().localDeletionTime); + maxDeletionTimeTracker.update(deletionInfo().getTopLevelDeletion().localDeletionTime); + minTimestampTracker.update(deletionInfo().getTopLevelDeletion().markedForDeleteAt); + maxTimestampTracker.update(deletionInfo().getTopLevelDeletion().markedForDeleteAt); + } Iterator<RangeTombstone> it = deletionInfo().rangeIterator(); while (it.hasNext()) { RangeTombstone rangeTombstone = it.next(); tombstones.update(rangeTombstone.getLocalDeletionTime()); - minTimestampSeen = Math.min(minTimestampSeen, rangeTombstone.minTimestamp()); - maxTimestampSeen = Math.max(maxTimestampSeen, rangeTombstone.maxTimestamp()); + minTimestampTracker.update(rangeTombstone.minTimestamp()); + maxTimestampTracker.update(rangeTombstone.maxTimestamp()); + maxDeletionTimeTracker.update(rangeTombstone.getLocalDeletionTime()); minColumnNamesSeen = ColumnNameHelper.minComponents(minColumnNamesSeen, rangeTombstone.min, metadata.comparator); maxColumnNamesSeen = ColumnNameHelper.maxComponents(maxColumnNamesSeen, rangeTombstone.max, metadata.comparator); } for (Column column : this) { - minTimestampSeen = Math.min(minTimestampSeen, column.minTimestamp()); - maxTimestampSeen = Math.max(maxTimestampSeen, column.maxTimestamp()); - maxLocalDeletionTime = Math.max(maxLocalDeletionTime, column.getLocalDeletionTime()); + minTimestampTracker.update(column.minTimestamp()); + maxTimestampTracker.update(column.maxTimestamp()); + maxDeletionTimeTracker.update(column.getLocalDeletionTime()); int deletionTime = column.getLocalDeletionTime(); if (deletionTime < Integer.MAX_VALUE) tombstones.update(deletionTime); minColumnNamesSeen = ColumnNameHelper.minComponents(minColumnNamesSeen, column.name, metadata.comparator); maxColumnNamesSeen = ColumnNameHelper.maxComponents(maxColumnNamesSeen, column.name, metadata.comparator); } - return new ColumnStats(getColumnCount(), minTimestampSeen, maxTimestampSeen, maxLocalDeletionTime, tombstones, minColumnNamesSeen, maxColumnNamesSeen); + return new ColumnStats(getColumnCount(), minTimestampTracker.get(), maxTimestampTracker.get(), maxDeletionTimeTracker.get(), tombstones, minColumnNamesSeen, maxColumnNamesSeen); } public boolean isMarkedForDelete() http://git-wip-us.apache.org/repos/asf/cassandra/blob/7110d980/src/java/org/apache/cassandra/db/DeletionTime.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/DeletionTime.java b/src/java/org/apache/cassandra/db/DeletionTime.java index a1b9f17..dd2ccaf 100644 --- a/src/java/org/apache/cassandra/db/DeletionTime.java +++ b/src/java/org/apache/cassandra/db/DeletionTime.java @@ -26,6 +26,7 @@ import com.google.common.base.Objects; import org.apache.cassandra.io.ISerializer; import org.apache.cassandra.utils.ObjectSizes; +import org.codehaus.jackson.annotate.JsonIgnore; /** * A top-level (row) tombstone. @@ -59,6 +60,15 @@ public class DeletionTime implements Comparable<DeletionTime> this.localDeletionTime = localDeletionTime; } + /** + * Returns whether this DeletionTime is live, that is deletes no columns. + */ + @JsonIgnore + public boolean isLive() + { + return markedForDeleteAt == Long.MIN_VALUE && localDeletionTime == Integer.MAX_VALUE; + } + @Override public boolean equals(Object o) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/7110d980/src/java/org/apache/cassandra/db/compaction/LazilyCompactedRow.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/compaction/LazilyCompactedRow.java b/src/java/org/apache/cassandra/db/compaction/LazilyCompactedRow.java index 9573874..2757411 100644 --- a/src/java/org/apache/cassandra/db/compaction/LazilyCompactedRow.java +++ b/src/java/org/apache/cassandra/db/compaction/LazilyCompactedRow.java @@ -111,9 +111,9 @@ public class LazilyCompactedRow extends AbstractCompactedRow implements Iterable // reach into the reducer (created during iteration) to get column count, size, max column timestamp // (however, if there are zero columns, iterator() will not be called by ColumnIndexer and reducer will be null) columnStats = new ColumnStats(reducer == null ? 0 : reducer.columns, - reducer == null ? Long.MAX_VALUE : reducer.minTimestampSeen, - reducer == null ? emptyColumnFamily.maxTimestamp() : Math.max(emptyColumnFamily.maxTimestamp(), reducer.maxTimestampSeen), - reducer == null ? Integer.MIN_VALUE : reducer.maxLocalDeletionTimeSeen, + reducer == null ? Long.MAX_VALUE : reducer.minTimestampTracker.get(), + reducer == null ? emptyColumnFamily.maxTimestamp() : Math.max(emptyColumnFamily.maxTimestamp(), reducer.maxTimestampTracker.get()), + reducer == null ? Integer.MIN_VALUE : reducer.maxDeletionTimeTracker.get(), reducer == null ? new StreamingHistogram(SSTable.TOMBSTONE_HISTOGRAM_BIN_SIZE) : reducer.tombstones, reducer == null ? Collections.<ByteBuffer>emptyList() : reducer.minColumnNameSeen, reducer == null ? Collections.<ByteBuffer>emptyList() : reducer.maxColumnNameSeen @@ -201,13 +201,26 @@ public class LazilyCompactedRow extends AbstractCompactedRow implements Iterable RangeTombstone tombstone; int columns = 0; - long minTimestampSeen = Long.MAX_VALUE; - long maxTimestampSeen = Long.MIN_VALUE; - int maxLocalDeletionTimeSeen = Integer.MIN_VALUE; + // if the row tombstone is 'live' we need to set timestamp to MAX_VALUE to be able to overwrite it later + // markedForDeleteAt is MIN_VALUE for 'live' row tombstones (which we use to default maxTimestampSeen) + + ColumnStats.MinTracker<Long> minTimestampTracker = new ColumnStats.MinTracker<>(Long.MIN_VALUE); + ColumnStats.MaxTracker<Long> maxTimestampTracker = new ColumnStats.MaxTracker<>(Long.MAX_VALUE); + // we need to set MIN_VALUE if we are 'live' since we want to overwrite it later + // we are bound to have either a RangeTombstone or standard cells will set this properly: + ColumnStats.MaxTracker<Integer> maxDeletionTimeTracker = new ColumnStats.MaxTracker<>(Integer.MAX_VALUE); + StreamingHistogram tombstones = new StreamingHistogram(SSTable.TOMBSTONE_HISTOGRAM_BIN_SIZE); List<ByteBuffer> minColumnNameSeen = Collections.emptyList(); List<ByteBuffer> maxColumnNameSeen = Collections.emptyList(); + public Reducer() + { + minTimestampTracker.update(maxRowTombstone.isLive() ? Long.MAX_VALUE : maxRowTombstone.markedForDeleteAt); + maxTimestampTracker.update(maxRowTombstone.markedForDeleteAt); + maxDeletionTimeTracker.update(maxRowTombstone.isLive() ? Integer.MIN_VALUE : maxRowTombstone.localDeletionTime); + } + /** * Called once per version of a cell that we need to merge, after which getReduced() is called. In other words, * this will be called one or more times with cells that share the same column name. @@ -254,8 +267,9 @@ public class LazilyCompactedRow extends AbstractCompactedRow implements Iterable else { tombstones.update(t.getLocalDeletionTime()); - minTimestampSeen = Math.min(minTimestampSeen, t.minTimestamp()); - maxTimestampSeen = Math.max(maxTimestampSeen, t.maxTimestamp()); + minTimestampTracker.update(t.minTimestamp()); + maxTimestampTracker.update(t.maxTimestamp()); + maxDeletionTimeTracker.update(t.getLocalDeletionTime()); minColumnNameSeen = ColumnNameHelper.minComponents(minColumnNameSeen, t.min, controller.cfs.metadata.comparator); maxColumnNameSeen = ColumnNameHelper.maxComponents(maxColumnNameSeen, t.max, controller.cfs.metadata.comparator); @@ -287,9 +301,9 @@ public class LazilyCompactedRow extends AbstractCompactedRow implements Iterable if (localDeletionTime < Integer.MAX_VALUE) tombstones.update(localDeletionTime); columns++; - minTimestampSeen = Math.min(minTimestampSeen, reduced.minTimestamp()); - maxTimestampSeen = Math.max(maxTimestampSeen, reduced.maxTimestamp()); - maxLocalDeletionTimeSeen = Math.max(maxLocalDeletionTimeSeen, reduced.getLocalDeletionTime()); + minTimestampTracker.update(reduced.minTimestamp()); + maxTimestampTracker.update(reduced.maxTimestamp()); + maxDeletionTimeTracker.update(reduced.getLocalDeletionTime()); minColumnNameSeen = ColumnNameHelper.minComponents(minColumnNameSeen, reduced.name(), controller.cfs.metadata.comparator); maxColumnNameSeen = ColumnNameHelper.maxComponents(maxColumnNameSeen, reduced.name(), controller.cfs.metadata.comparator); http://git-wip-us.apache.org/repos/asf/cassandra/blob/7110d980/src/java/org/apache/cassandra/io/sstable/ColumnStats.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/io/sstable/ColumnStats.java b/src/java/org/apache/cassandra/io/sstable/ColumnStats.java index bd3bd1c..446c41c 100644 --- a/src/java/org/apache/cassandra/io/sstable/ColumnStats.java +++ b/src/java/org/apache/cassandra/io/sstable/ColumnStats.java @@ -18,6 +18,7 @@ package org.apache.cassandra.io.sstable; import java.nio.ByteBuffer; +import java.util.Comparator; import java.util.List; import org.apache.cassandra.utils.StreamingHistogram; @@ -51,4 +52,70 @@ public class ColumnStats this.minColumnNames = minColumnNames; this.maxColumnNames = maxColumnNames; } + + public static class MinTracker<T extends Comparable<T>> + { + private final T defaultValue; + private boolean isSet = false; + private T value; + + public MinTracker(T defaultValue) + { + this.defaultValue = defaultValue; + } + + public void update(T value) + { + if (!isSet) + { + this.value = value; + isSet = true; + } + else + { + if (value.compareTo(this.value) < 0) + this.value = value; + } + } + + public T get() + { + if (isSet) + return value; + return defaultValue; + } + } + + public static class MaxTracker<T extends Comparable<T>> + { + private final T defaultValue; + private boolean isSet = false; + private T value; + + public MaxTracker(T defaultValue) + { + this.defaultValue = defaultValue; + } + + public void update(T value) + { + if (!isSet) + { + this.value = value; + isSet = true; + } + else + { + if (value.compareTo(this.value) > 0) + this.value = value; + } + } + + public T get() + { + if (isSet) + return value; + return defaultValue; + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/7110d980/src/java/org/apache/cassandra/io/sstable/SSTableWriter.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/io/sstable/SSTableWriter.java b/src/java/org/apache/cassandra/io/sstable/SSTableWriter.java index 8a9bb18..4619ddc 100644 --- a/src/java/org/apache/cassandra/io/sstable/SSTableWriter.java +++ b/src/java/org/apache/cassandra/io/sstable/SSTableWriter.java @@ -213,10 +213,9 @@ public class SSTableWriter extends SSTable { long currentPosition = beforeAppend(key); - // deserialize each column to obtain maxTimestamp and immediately serialize it. - long minTimestamp = Long.MAX_VALUE; - long maxTimestamp = Long.MIN_VALUE; - int maxLocalDeletionTime = Integer.MIN_VALUE; + ColumnStats.MaxTracker<Long> maxTimestampTracker = new ColumnStats.MaxTracker<>(Long.MAX_VALUE); + ColumnStats.MinTracker<Long> minTimestampTracker = new ColumnStats.MinTracker<>(Long.MIN_VALUE); + ColumnStats.MaxTracker<Integer> maxDeletionTimeTracker = new ColumnStats.MaxTracker<>(Integer.MAX_VALUE); List<ByteBuffer> minColumnNames = Collections.emptyList(); List<ByteBuffer> maxColumnNames = Collections.emptyList(); StreamingHistogram tombstones = new StreamingHistogram(TOMBSTONE_HISTOGRAM_BIN_SIZE); @@ -236,16 +235,21 @@ public class SSTableWriter extends SSTable columnCount = in.readInt(); if (cf.deletionInfo().getTopLevelDeletion().localDeletionTime < Integer.MAX_VALUE) + { tombstones.update(cf.deletionInfo().getTopLevelDeletion().localDeletionTime); + maxDeletionTimeTracker.update(cf.deletionInfo().getTopLevelDeletion().localDeletionTime); + minTimestampTracker.update(cf.deletionInfo().getTopLevelDeletion().markedForDeleteAt); + maxTimestampTracker.update(cf.deletionInfo().getTopLevelDeletion().markedForDeleteAt); + } Iterator<RangeTombstone> rangeTombstoneIterator = cf.deletionInfo().rangeIterator(); while (rangeTombstoneIterator.hasNext()) { RangeTombstone rangeTombstone = rangeTombstoneIterator.next(); tombstones.update(rangeTombstone.getLocalDeletionTime()); - minTimestamp = Math.min(minTimestamp, rangeTombstone.minTimestamp()); - maxTimestamp = Math.max(maxTimestamp, rangeTombstone.maxTimestamp()); - + minTimestampTracker.update(rangeTombstone.minTimestamp()); + maxTimestampTracker.update(rangeTombstone.maxTimestamp()); + maxDeletionTimeTracker.update(rangeTombstone.getLocalDeletionTime()); minColumnNames = ColumnNameHelper.minComponents(minColumnNames, rangeTombstone.min, metadata.comparator); maxColumnNames = ColumnNameHelper.maxComponents(maxColumnNames, rangeTombstone.max, metadata.comparator); } @@ -255,8 +259,6 @@ public class SSTableWriter extends SSTable { while (iter.hasNext()) { - // deserialize column with PRESERVE_SIZE because we've written the dataSize based on the - // data size received, so we must reserialize the exact same data OnDiskAtom atom = iter.next(); if (atom == null) break; @@ -268,11 +270,11 @@ public class SSTableWriter extends SSTable { tombstones.update(deletionTime); } - minTimestamp = Math.min(minTimestamp, atom.minTimestamp()); - maxTimestamp = Math.max(maxTimestamp, atom.maxTimestamp()); + minTimestampTracker.update(atom.minTimestamp()); + maxTimestampTracker.update(atom.maxTimestamp()); minColumnNames = ColumnNameHelper.minComponents(minColumnNames, atom.name(), metadata.comparator); maxColumnNames = ColumnNameHelper.maxComponents(maxColumnNames, atom.name(), metadata.comparator); - maxLocalDeletionTime = Math.max(maxLocalDeletionTime, atom.getLocalDeletionTime()); + maxDeletionTimeTracker.update(atom.getLocalDeletionTime()); columnIndexer.add(atom); // This write the atom on disk too } @@ -285,9 +287,9 @@ public class SSTableWriter extends SSTable throw new FSWriteError(e, dataFile.getPath()); } - sstableMetadataCollector.updateMinTimestamp(minTimestamp); - sstableMetadataCollector.updateMaxTimestamp(maxTimestamp); - sstableMetadataCollector.updateMaxLocalDeletionTime(maxLocalDeletionTime); + sstableMetadataCollector.updateMinTimestamp(minTimestampTracker.get()); + sstableMetadataCollector.updateMaxTimestamp(maxTimestampTracker.get()); + sstableMetadataCollector.updateMaxLocalDeletionTime(maxDeletionTimeTracker.get()); sstableMetadataCollector.addRowSize(dataFile.getFilePointer() - currentPosition); sstableMetadataCollector.addColumnCount(columnIndexer.writtenAtomCount()); sstableMetadataCollector.mergeTombstoneHistogram(tombstones); http://git-wip-us.apache.org/repos/asf/cassandra/blob/7110d980/test/unit/org/apache/cassandra/cql3/SSTableMetadataTrackingTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/SSTableMetadataTrackingTest.java b/test/unit/org/apache/cassandra/cql3/SSTableMetadataTrackingTest.java new file mode 100644 index 0000000..9104269 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/SSTableMetadataTrackingTest.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.cql3; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.gms.Gossiper; +import org.apache.cassandra.io.sstable.SSTableMetadata; +import org.apache.cassandra.service.ClientState; +import static org.apache.cassandra.cql3.QueryProcessor.process; +import static org.apache.cassandra.cql3.QueryProcessor.processInternal; +import static org.junit.Assert.assertEquals; + +public class SSTableMetadataTrackingTest +{ + private static String keyspace = "sstable_metadata_tracking_test"; + private static ClientState clientState; + + @BeforeClass + public static void setup() throws Throwable + { + SchemaLoader.loadSchema(); + createKeyspace("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}"); + clientState = ClientState.forInternalCalls(); + } + + + @Test + public void baseCheck() throws Throwable + { + createTable("CREATE TABLE %s.basecheck (a int, b int, c text, PRIMARY KEY (a, b))"); + ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore("basecheck"); + execute("INSERT INTO %s.basecheck (a,b,c) VALUES (1,1,'1') using timestamp 9999"); + cfs.forceBlockingFlush(); + SSTableMetadata metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(Integer.MAX_VALUE, metadata.maxLocalDeletionTime); + cfs.forceMajorCompaction(); + metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(Integer.MAX_VALUE, metadata.maxLocalDeletionTime); + } + + @Test + public void testMinMaxtimestampRange() throws Throwable + { + createTable("CREATE TABLE %s.minmaxtsrange (a int, b int, c text, PRIMARY KEY (a, b))"); + ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore("minmaxtsrange"); + execute("INSERT INTO %s.minmaxtsrange (a,b,c) VALUES (1,1,'1') using timestamp 10000"); + execute("DELETE FROM %s.minmaxtsrange USING TIMESTAMP 9999 WHERE a = 1 and b = 1"); + cfs.forceBlockingFlush(); + SSTableMetadata metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(10000, metadata.maxTimestamp); + assertEquals(Integer.MAX_VALUE, metadata.maxLocalDeletionTime, 5); + cfs.forceMajorCompaction(); + metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(10000, metadata.maxTimestamp); + assertEquals(Integer.MAX_VALUE, metadata.maxLocalDeletionTime, 5); + } + + @Test + public void testMinMaxtimestampRow() throws Throwable + { + createTable("CREATE TABLE %s.minmaxtsrow (a int, b int, c text, PRIMARY KEY (a, b))"); + ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore("minmaxtsrow"); + execute("INSERT INTO %s.minmaxtsrow (a,b,c) VALUES (1,1,'1') using timestamp 10000"); + execute("DELETE FROM %s.minmaxtsrow USING TIMESTAMP 9999 WHERE a = 1"); + cfs.forceBlockingFlush(); + SSTableMetadata metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(10000, metadata.maxTimestamp); + assertEquals(Integer.MAX_VALUE, metadata.maxLocalDeletionTime, 5); + cfs.forceMajorCompaction(); + metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(10000, metadata.maxTimestamp); + assertEquals(Integer.MAX_VALUE, metadata.maxLocalDeletionTime, 5); + } + + + @Test + public void testTrackMetadata_rangeTombstone() throws Throwable + { + createTable("CREATE TABLE %s.rangetombstone (a int, b int, c text, PRIMARY KEY (a, b)) WITH gc_grace_seconds = 10000"); + ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore("rangetombstone"); + execute("DELETE FROM %s.rangetombstone USING TIMESTAMP 9999 WHERE a = 1 and b = 1"); + cfs.forceBlockingFlush(); + assertEquals(1, cfs.getSSTables().size()); + SSTableMetadata metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(9999, metadata.maxTimestamp); + assertEquals(System.currentTimeMillis()/1000, metadata.maxLocalDeletionTime, 5); + cfs.forceMajorCompaction(); + SSTableMetadata metadata2 = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(metadata.maxLocalDeletionTime, metadata2.maxLocalDeletionTime); + assertEquals(metadata.minTimestamp, metadata2.minTimestamp); + assertEquals(metadata.maxTimestamp, metadata2.maxTimestamp); + } + + @Test + public void testTrackMetadata_rowTombstone() throws Throwable + { + createTable("CREATE TABLE %s.rowtombstone (a int, b int, c text, PRIMARY KEY (a, b))"); + ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore("rowtombstone"); + execute("DELETE FROM %s.rowtombstone USING TIMESTAMP 9999 WHERE a = 1"); + + cfs.forceBlockingFlush(); + assertEquals(1, cfs.getSSTables().size()); + SSTableMetadata metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(9999, metadata.maxTimestamp); + assertEquals(System.currentTimeMillis()/1000, metadata.maxLocalDeletionTime, 5); + cfs.forceMajorCompaction(); + SSTableMetadata metadata2 = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(metadata.maxLocalDeletionTime, metadata2.maxLocalDeletionTime); + assertEquals(metadata.minTimestamp, metadata2.minTimestamp); + assertEquals(metadata.maxTimestamp, metadata2.maxTimestamp); + } + + @Test + public void testTrackMetadata_rowMarker() throws Throwable + { + createTable("CREATE TABLE %s.rowmarker (a int, PRIMARY KEY (a))"); + ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore("rowmarker"); + execute("INSERT INTO %s.rowmarker (a) VALUES (1) USING TIMESTAMP 9999"); + + cfs.forceBlockingFlush(); + assertEquals(1, cfs.getSSTables().size()); + SSTableMetadata metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(9999, metadata.maxTimestamp); + assertEquals(Integer.MAX_VALUE, metadata.maxLocalDeletionTime); + cfs.forceMajorCompaction(); + SSTableMetadata metadata2 = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(metadata.maxLocalDeletionTime, metadata2.maxLocalDeletionTime); + assertEquals(metadata.minTimestamp, metadata2.minTimestamp); + assertEquals(metadata.maxTimestamp, metadata2.maxTimestamp); + } + @Test + public void testTrackMetadata_rowMarkerDelete() throws Throwable + { + createTable("CREATE TABLE %s.rowmarkerdel (a int, PRIMARY KEY (a))"); + ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore("rowmarkerdel"); + execute("DELETE FROM %s.rowmarkerdel USING TIMESTAMP 9999 WHERE a=1"); + cfs.forceBlockingFlush(); + assertEquals(1, cfs.getSSTables().size()); + SSTableMetadata metadata = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(9999, metadata.minTimestamp); + assertEquals(9999, metadata.maxTimestamp); + assertEquals(System.currentTimeMillis()/1000, metadata.maxLocalDeletionTime, 5); + cfs.forceMajorCompaction(); + SSTableMetadata metadata2 = cfs.getSSTables().iterator().next().getSSTableMetadata(); + assertEquals(metadata.maxLocalDeletionTime, metadata2.maxLocalDeletionTime); + assertEquals(metadata.minTimestamp, metadata2.minTimestamp); + assertEquals(metadata.maxTimestamp, metadata2.maxTimestamp); + } + + @AfterClass + public static void stopGossiper() + { + Gossiper.instance.stop(); + } + + private static void createKeyspace(String query) throws Throwable + { + try + { + process(String.format(query, keyspace), ConsistencyLevel.ONE); + } catch (RuntimeException exc) + { + throw exc.getCause(); + } + } + + + private void createTable(String query) throws Throwable + { + try + { + process(String.format(query, keyspace), ConsistencyLevel.ONE); + } catch (RuntimeException exc) + { + throw exc.getCause(); + } + } + + private UntypedResultSet execute(String query) throws Throwable + { + try + { + return processInternal(String.format(query, keyspace)); + } catch (RuntimeException exc) + { + if (exc.getCause() != null) + throw exc.getCause(); + throw exc; + } + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/7110d980/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java b/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java index 7f1238f..4dc7c0b 100644 --- a/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java +++ b/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java @@ -30,6 +30,7 @@ import org.apache.cassandra.db.columniterator.OnDiskAtomIterator; import org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy; import org.apache.cassandra.db.index.*; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.io.sstable.SSTableMetadata; import org.apache.cassandra.io.sstable.SSTableReader; import org.apache.cassandra.thrift.IndexType; @@ -110,6 +111,99 @@ public class RangeTombstoneTest extends SchemaLoader assert !isLive(cf, cf.getColumn(b(i))) : "Column " + i + " shouldn't be live"; } + @Test + public void testTrackTimesRowTombstone() throws ExecutionException, InterruptedException + { + Keyspace ks = Keyspace.open(KSNAME); + ColumnFamilyStore cfs = ks.getColumnFamilyStore(CFNAME); + cfs.truncateBlocking(); + String key = "rt_times"; + RowMutation rm = new RowMutation(KSNAME, ByteBufferUtil.bytes(key)); + ColumnFamily cf = rm.addOrGet(CFNAME); + long timestamp = System.currentTimeMillis(); + cf.delete(new DeletionInfo(1000, (int)(timestamp/1000))); + rm.apply(); + cfs.forceBlockingFlush(); + SSTableReader sstable = cfs.getSSTables().iterator().next(); + assertTimes(sstable.getSSTableMetadata(), 1000, 1000, (int)(timestamp/1000)); + cfs.forceMajorCompaction(); + sstable = cfs.getSSTables().iterator().next(); + assertTimes(sstable.getSSTableMetadata(), 1000, 1000, (int)(timestamp/1000)); + } + + @Test + public void testTrackTimesRowTombstoneWithData() throws ExecutionException, InterruptedException + { + Keyspace ks = Keyspace.open(KSNAME); + ColumnFamilyStore cfs = ks.getColumnFamilyStore(CFNAME); + cfs.truncateBlocking(); + String key = "rt_times"; + RowMutation rm = new RowMutation(KSNAME, ByteBufferUtil.bytes(key)); + add(rm, 5, 999); + rm.apply(); + key = "rt_times2"; + rm = new RowMutation(KSNAME, ByteBufferUtil.bytes(key)); + ColumnFamily cf = rm.addOrGet(CFNAME); + int timestamp = (int)(System.currentTimeMillis()/1000); + cf.delete(new DeletionInfo(1000, timestamp)); + rm.apply(); + cfs.forceBlockingFlush(); + SSTableReader sstable = cfs.getSSTables().iterator().next(); + assertTimes(sstable.getSSTableMetadata(), 999, 1000, Integer.MAX_VALUE); + cfs.forceMajorCompaction(); + sstable = cfs.getSSTables().iterator().next(); + assertTimes(sstable.getSSTableMetadata(), 999, 1000, Integer.MAX_VALUE); + } + @Test + public void testTrackTimesRangeTombstone() throws ExecutionException, InterruptedException + { + Keyspace ks = Keyspace.open(KSNAME); + ColumnFamilyStore cfs = ks.getColumnFamilyStore(CFNAME); + cfs.truncateBlocking(); + String key = "rt_times"; + RowMutation rm = new RowMutation(KSNAME, ByteBufferUtil.bytes(key)); + ColumnFamily cf = rm.addOrGet(CFNAME); + long timestamp = System.currentTimeMillis(); + cf.delete(new DeletionInfo(b(1), b(2), cfs.getComparator(), 1000, (int)(timestamp/1000))); + rm.apply(); + cfs.forceBlockingFlush(); + SSTableReader sstable = cfs.getSSTables().iterator().next(); + assertTimes(sstable.getSSTableMetadata(), 1000, 1000, (int)(timestamp/1000)); + cfs.forceMajorCompaction(); + sstable = cfs.getSSTables().iterator().next(); + assertTimes(sstable.getSSTableMetadata(), 1000, 1000, (int)(timestamp/1000)); + } + + @Test + public void testTrackTimesRangeTombstoneWithData() throws ExecutionException, InterruptedException + { + Keyspace ks = Keyspace.open(KSNAME); + ColumnFamilyStore cfs = ks.getColumnFamilyStore(CFNAME); + cfs.truncateBlocking(); + String key = "rt_times"; + RowMutation rm = new RowMutation(KSNAME, ByteBufferUtil.bytes(key)); + add(rm, 5, 999); + rm.apply(); + key = "rt_times2"; + rm = new RowMutation(KSNAME, ByteBufferUtil.bytes(key)); + ColumnFamily cf = rm.addOrGet(CFNAME); + int timestamp = (int)(System.currentTimeMillis()/1000); + cf.delete(new DeletionInfo(b(1), b(2), cfs.getComparator(), 1000, timestamp)); + rm.apply(); + cfs.forceBlockingFlush(); + SSTableReader sstable = cfs.getSSTables().iterator().next(); + assertTimes(sstable.getSSTableMetadata(), 999, 1000, Integer.MAX_VALUE); + cfs.forceMajorCompaction(); + sstable = cfs.getSSTables().iterator().next(); + assertTimes(sstable.getSSTableMetadata(), 999, 1000, Integer.MAX_VALUE); + } + + private void assertTimes(SSTableMetadata metadata, long min, long max, int localDeletionTime) + { + assertEquals(min, metadata.minTimestamp); + assertEquals(max, metadata.maxTimestamp); + assertEquals(localDeletionTime, metadata.maxLocalDeletionTime); + } @Test public void test7810() throws ExecutionException, InterruptedException, IOException
