From 791a78f92c8318991cd9207bbca99b470174044a Mon Sep 17 00:00:00 2001 From: Jonathan Ellis Date: Sun, 21 Aug 2011 04:50:55 +0000 Subject: [PATCH] Stop reading from sstables once we know we have the most recent columns patch by Daniel Lundin and jbellis for CASSANDRA-2498 git-svn-id: https://svn.apache.org/repos/asf/cassandra/trunk@1159942 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 3 + .../cassandra/db/CollationController.java | 231 ++++++++++++++++++ src/java/org/apache/cassandra/db/Column.java | 5 + .../cassandra/db/ColumnFamilyStore.java | 66 +---- .../org/apache/cassandra/db/DataTracker.java | 47 +++- src/java/org/apache/cassandra/db/IColumn.java | 8 +- .../org/apache/cassandra/db/SuperColumn.java | 11 +- .../columniterator/SSTableSliceIterator.java | 2 +- .../apache/cassandra/io/sstable/SSTable.java | 14 +- .../cassandra/db/ColumnFamilyStoreTest.java | 34 ++- .../db/compaction/CompactionsTest.java | 4 + 11 files changed, 345 insertions(+), 80 deletions(-) create mode 100644 src/java/org/apache/cassandra/db/CollationController.java diff --git a/CHANGES.txt b/CHANGES.txt index 12af6ea169..70281be06d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -35,6 +35,9 @@ * fix missing logging for some exceptions (CASSANDRA-2061) * refactor and optimize ColumnFamilyStore.files(...) and Descriptor.fromFilename(String) and few other places responsible for work with SSTable files (CASSANDRA-3040) + * Stop reading from sstables once we know we have the most recent columns, + for query-by-name requests (CASSANDRA-2498) + 0.8.5 * fix NPE when encryption_options is unspecified (CASSANDRA-3007) diff --git a/src/java/org/apache/cassandra/db/CollationController.java b/src/java/org/apache/cassandra/db/CollationController.java new file mode 100644 index 0000000000..e1f32bf228 --- /dev/null +++ b/src/java/org/apache/cassandra/db/CollationController.java @@ -0,0 +1,231 @@ +/** + * 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; + +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.db.columniterator.IColumnIterator; +import org.apache.cassandra.db.columniterator.SimpleAbstractColumnIterator; +import org.apache.cassandra.db.filter.NamesQueryFilter; +import org.apache.cassandra.db.filter.QueryFilter; +import org.apache.cassandra.db.marshal.CounterColumnType; +import org.apache.cassandra.io.sstable.SSTableReader; +import org.apache.cassandra.io.util.FileUtils; +import org.apache.cassandra.utils.CloseableIterator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.*; + +import com.google.common.collect.Iterables; + +public class CollationController +{ + private static Logger logger = LoggerFactory.getLogger(CollationController.class); + + private final DataTracker.View dataview; + private final ISortedColumns.Factory factory; + private final QueryFilter filter; + private final int gcBefore; + private final CFMetaData metadata; + + private int sstablesIterated = 0; + + public CollationController(DataTracker.View dataview, ISortedColumns.Factory factory, QueryFilter filter, CFMetaData metadata, int gcBefore) + { + this.dataview = dataview; + this.factory = factory; + this.filter = filter; + this.gcBefore = gcBefore; + this.metadata = metadata; + } + + public ColumnFamily getTopLevelColumns() + { + return filter.filter instanceof NamesQueryFilter && metadata.getDefaultValidator() != CounterColumnType.instance + ? collectTimeOrderedData() + : collectAllData(); + } + + /** + * Collects data in order of recency, using the sstable maxtimestamp data. + * Once we have data for all requests columns that is newer than the newest remaining maxtimestamp, + * we stop. + */ + private ColumnFamily collectTimeOrderedData() + { + logger.debug("collectTimeOrderedData"); + List iterators = new ArrayList(); + final ColumnFamily container = ColumnFamily.create(metadata, factory, filter.filter.isReversed()); + + try + { + for (Memtable memtable : Iterables.concat(dataview.memtablesPendingFlush, Collections.singleton(dataview.memtable))) + { + IColumnIterator iter = filter.getMemtableColumnIterator(memtable, metadata.comparator); + if (iter != null) + { + iterators.add(iter); + container.delete(iter.getColumnFamily()); + while (iter.hasNext()) + container.addColumn(iter.next()); + } + } + + // avoid changing the filter columns of the original filter + // (reduceNameFilter removes columns that are known to be irrelevant) + TreeSet filterColumns = new TreeSet(metadata.comparator); + filterColumns.addAll(((NamesQueryFilter) filter.filter).columns); + QueryFilter reducedFilter = new QueryFilter(filter.key, filter.path, new NamesQueryFilter(filterColumns)); + + // read sorted sstables + for (SSTableReader sstable : dataview.sstables) + { + long currentMaxTs = sstable.getMaxTimestamp(); + reduceNameFilter(reducedFilter, container, currentMaxTs); + if (((NamesQueryFilter) reducedFilter.filter).columns.isEmpty()) + break; + + IColumnIterator iter = reducedFilter.getSSTableColumnIterator(sstable); + iterators.add(iter); + if (iter.getColumnFamily() != null) + { + container.delete(iter.getColumnFamily()); + sstablesIterated++; + while (iter.hasNext()) + container.addColumn(iter.next()); + } + } + } + finally + { + for (IColumnIterator iter : iterators) + FileUtils.closeQuietly(iter); + } + + // we need to distinguish between "there is no data at all for this row" (BF will let us rebuild that efficiently) + // and "there used to be data, but it's gone now" (we should cache the empty CF so we don't need to rebuild that slower) + if (iterators.isEmpty()) + return null; + + // do a final collate. toCollate is boilerplate required to provide a CloseableIterator + CloseableIterator toCollate = new SimpleAbstractColumnIterator() + { + final Iterator iter = container.iterator(); + + protected IColumn computeNext() + { + return iter.hasNext() ? iter.next() : endOfData(); + } + + public ColumnFamily getColumnFamily() + { + return container; + } + + public DecoratedKey getKey() + { + return filter.key; + } + }; + ColumnFamily returnCF = container.cloneMeShallow(); + filter.collateColumns(returnCF, Collections.singletonList(toCollate), metadata.comparator, gcBefore); + + // Caller is responsible for final removeDeletedCF. This is important for cacheRow to work correctly: + return returnCF; + } + + /** + * remove columns from @param filter where we already have data in @param returnCF newer than @param sstableTimestamp + */ + private void reduceNameFilter(QueryFilter filter, ColumnFamily returnCF, long sstableTimestamp) + { + AbstractColumnContainer container = filter.path.superColumnName != null + ? (SuperColumn) returnCF.getColumn(filter.path.superColumnName) + : returnCF; + if (container == null) + return; + + for (Iterator iterator = ((NamesQueryFilter) filter.filter).columns.iterator(); iterator.hasNext(); ) + { + ByteBuffer filterColumn = iterator.next(); + IColumn column = container.getColumn(filterColumn); + if (column != null && column.minTimestamp() > sstableTimestamp) + iterator.remove(); + } + } + + /** + * Collects data the brute-force way: gets an iterator for the filter in question + * from every memtable and sstable, then merges them together. + */ + private ColumnFamily collectAllData() + { + logger.debug("collectAllData"); + List iterators = new ArrayList(); + ColumnFamily returnCF = ColumnFamily.create(metadata, factory, filter.filter.isReversed()); + + try + { + for (Memtable memtable : Iterables.concat(dataview.memtablesPendingFlush, Collections.singleton(dataview.memtable))) + { + IColumnIterator iter = filter.getMemtableColumnIterator(memtable, metadata.comparator); + if (iter != null) + { + returnCF.delete(iter.getColumnFamily()); + iterators.add(iter); + } + } + + /* add the SSTables on disk */ + for (SSTableReader sstable : dataview.sstables) + { + IColumnIterator iter = filter.getSSTableColumnIterator(sstable); + iterators.add(iter); + if (iter.getColumnFamily() != null) + { + returnCF.delete(iter.getColumnFamily()); + sstablesIterated++; + } + } + } + finally + { + for (IColumnIterator iter : iterators) + FileUtils.closeQuietly(iter); + } + + // we need to distinguish between "there is no data at all for this row" (BF will let us rebuild that efficiently) + // and "there used to be data, but it's gone now" (we should cache the empty CF so we don't need to rebuild that slower) + if (iterators.isEmpty()) + return null; + + filter.collateColumns(returnCF, iterators, metadata.comparator, gcBefore); + + // Caller is responsible for final removeDeletedCF. This is important for cacheRow to work correctly: + return returnCF; + } + + public int getSstablesIterated() + { + return sstablesIterated; + } +} diff --git a/src/java/org/apache/cassandra/db/Column.java b/src/java/org/apache/cassandra/db/Column.java index 376cb0d276..04c3372aa7 100644 --- a/src/java/org/apache/cassandra/db/Column.java +++ b/src/java/org/apache/cassandra/db/Column.java @@ -104,6 +104,11 @@ public class Column implements IColumn return timestamp; } + public long minTimestamp() + { + return timestamp; + } + public boolean isMarkedForDelete() { return false; diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index 5498dc9cd1..31bc9bbdc0 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -1278,73 +1278,17 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean private ColumnFamily getTopLevelColumns(QueryFilter filter, int gcBefore, ISortedColumns.Factory factory) { - // we are querying top-level columns, do a merging fetch with indexes. - List iterators = new ArrayList(); - final ColumnFamily returnCF = ColumnFamily.create(metadata, factory, filter.filter.isReversed()); DataTracker.View currentView = markCurrentViewReferenced(); try { - IColumnIterator iter; - int sstablesToIterate = 0; - - /* add the current memtable */ - iter = filter.getMemtableColumnIterator(currentView.memtable, getComparator()); - if (iter != null) - { - returnCF.delete(iter.getColumnFamily()); - iterators.add(iter); - } - - /* add the memtables being flushed */ - for (Memtable memtable : currentView.memtablesPendingFlush) - { - iter = filter.getMemtableColumnIterator(memtable, getComparator()); - if (iter != null) - { - returnCF.delete(iter.getColumnFamily()); - iterators.add(iter); - } - } - - /* add the SSTables on disk */ - for (SSTableReader sstable : currentView.sstables) - { - iter = filter.getSSTableColumnIterator(sstable); - if (iter.getColumnFamily() != null) - { - returnCF.delete(iter.getColumnFamily()); - iterators.add(iter); - sstablesToIterate++; - } - } - - recentSSTablesPerRead.add(sstablesToIterate); - sstablesPerRead.add(sstablesToIterate); - - // we need to distinguish between "there is no data at all for this row" (BF will let us rebuild that efficiently) - // and "there used to be data, but it's gone now" (we should cache the empty CF so we don't need to rebuild that slower) - if (iterators.size() == 0) - return null; - - filter.collateColumns(returnCF, iterators, getComparator(), gcBefore); - - // Caller is responsible for final removeDeletedCF. This is important for cacheRow to work correctly: - return returnCF; + CollationController controller = new CollationController(currentView, factory, filter, metadata, gcBefore); + ColumnFamily columns = controller.getTopLevelColumns(); + recentSSTablesPerRead.add(controller.getSstablesIterated()); + sstablesPerRead.add(controller.getSstablesIterated()); + return columns; } finally { - /* close all cursors */ - for (IColumnIterator ci : iterators) - { - try - { - ci.close(); - } - catch (Throwable th) - { - logger.error("error closing " + ci, th); - } - } SSTableReader.releaseReferences(currentView.sstables); } } diff --git a/src/java/org/apache/cassandra/db/DataTracker.java b/src/java/org/apache/cassandra/db/DataTracker.java index a540d30a1e..b3663b3ebc 100644 --- a/src/java/org/apache/cassandra/db/DataTracker.java +++ b/src/java/org/apache/cassandra/db/DataTracker.java @@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +36,7 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.cache.AutoSavingCache; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.io.sstable.Descriptor; +import org.apache.cassandra.io.sstable.SSTable; import org.apache.cassandra.io.sstable.SSTableReader; import org.apache.cassandra.utils.Pair; @@ -66,7 +69,7 @@ public class DataTracker return view.get().memtablesPendingFlush; } - public Set getSSTables() + public List getSSTables() { return view.get().sstables; } @@ -242,7 +245,7 @@ public class DataTracker { view.set(new View(new Memtable(cfstore), Collections.emptySet(), - Collections.emptySet(), + Collections.emptyList(), Collections.emptySet())); } @@ -461,10 +464,15 @@ public class DataTracker { public final Memtable memtable; public final Set memtablesPendingFlush; - public final Set sstables; public final Set compacting; + // We can't use a SortedSet here because "the ordering maintained by a sorted set (whether or not an + // explicit comparator is provided) must be consistent with equals." In particular, + // ImmutableSortedSet will ignore any objects that compare equally with an existing Set member. + // Obviously, dropping sstables whose max column timestamp happens to be equal to another's + // is not acceptable for us. So, we use a List instead. + public final List sstables; - View(Memtable memtable, Set pendingFlush, Set sstables, Set compacting) + View(Memtable memtable, Set pendingFlush, List sstables, Set compacting) { this.memtable = memtable; this.memtablesPendingFlush = pendingFlush; @@ -486,15 +494,14 @@ public class DataTracker public View replaceFlushed(Memtable flushedMemtable, SSTableReader newSSTable) { Set newPending = ImmutableSet.copyOf(Sets.difference(memtablesPendingFlush, Collections.singleton(flushedMemtable))); - Set newSSTables = ImmutableSet.builder().addAll(sstables).add(newSSTable).build(); - return new View(memtable, newPending, newSSTables, compacting); + List newSSTables = newSSTables(newSSTable); + return new View(memtable, newPending, Collections.unmodifiableList(newSSTables), compacting); } public View replace(Collection oldSSTables, Iterable replacements) { - Sets.SetView remaining = Sets.difference(sstables, ImmutableSet.copyOf(oldSSTables)); - Set newSSTables = ImmutableSet.builder().addAll(remaining).addAll(replacements).build(); - return new View(memtable, memtablesPendingFlush, newSSTables, compacting); + List newSSTables = newSSTables(oldSSTables, replacements); + return new View(memtable, memtablesPendingFlush, Collections.unmodifiableList(newSSTables), compacting); } public View markCompacting(Collection tomark) @@ -508,5 +515,27 @@ public class DataTracker Set compactingNew = ImmutableSet.copyOf(Sets.difference(compacting, ImmutableSet.copyOf(tounmark))); return new View(memtable, memtablesPendingFlush, sstables, compactingNew); } + + private List newSSTables(SSTableReader newSSTable) + { + // not performance-sensitive, don't obsess over doing a selection merge here + return newSSTables(Collections.emptyList(), Collections.singletonList(newSSTable)); + } + + private List newSSTables(Collection oldSSTables, Iterable replacements) + { + ImmutableSet oldSet = ImmutableSet.copyOf(oldSSTables); + int newSSTablesSize = sstables.size() - oldSSTables.size() + Iterables.size(replacements); + List newSSTables = new ArrayList(newSSTablesSize); + for (SSTableReader sstable : sstables) + { + if (!oldSet.contains(sstable)) + newSSTables.add(sstable); + } + Iterables.addAll(newSSTables, replacements); + assert newSSTables.size() == newSSTablesSize; + Collections.sort(newSSTables, SSTable.maxTimestampComparator); + return newSSTables; + } } } diff --git a/src/java/org/apache/cassandra/db/IColumn.java b/src/java/org/apache/cassandra/db/IColumn.java index d6fb656e86..cfe504f0d9 100644 --- a/src/java/org/apache/cassandra/db/IColumn.java +++ b/src/java/org/apache/cassandra/db/IColumn.java @@ -73,7 +73,13 @@ public interface IColumn /** * For a standard column, this is the same as timestamp(). - * For a super column, this is max the column value timestamp of the sub columns. + * For a super column, this is the max column timestamp of the sub columns. */ public long maxTimestamp(); + + /** + * For a standard column, this is the same as timestamp(). + * For a super column, this is the min column timestamp of the sub columns. + */ + public long minTimestamp(); } diff --git a/src/java/org/apache/cassandra/db/SuperColumn.java b/src/java/org/apache/cassandra/db/SuperColumn.java index d877464b32..e2204c1360 100644 --- a/src/java/org/apache/cassandra/db/SuperColumn.java +++ b/src/java/org/apache/cassandra/db/SuperColumn.java @@ -132,11 +132,18 @@ public class SuperColumn extends AbstractColumnContainer implements IColumn public long maxTimestamp() { - long maxTimestamp = Long.MIN_VALUE; + long maxTimestamp = getMarkedForDeleteAt(); for (IColumn subColumn : getSubColumns()) maxTimestamp = Math.max(maxTimestamp, subColumn.maxTimestamp()); + return maxTimestamp; + } - return Math.max(maxTimestamp, getMarkedForDeleteAt()); + public long minTimestamp() + { + long minTimestamp = getMarkedForDeleteAt(); + for (IColumn subColumn : getSubColumns()) + minTimestamp = Math.min(minTimestamp, subColumn.maxTimestamp()); + return minTimestamp; } public long mostRecentLiveChangeAt() diff --git a/src/java/org/apache/cassandra/db/columniterator/SSTableSliceIterator.java b/src/java/org/apache/cassandra/db/columniterator/SSTableSliceIterator.java index 2e85aca874..2e3575af5d 100644 --- a/src/java/org/apache/cassandra/db/columniterator/SSTableSliceIterator.java +++ b/src/java/org/apache/cassandra/db/columniterator/SSTableSliceIterator.java @@ -104,7 +104,7 @@ public class SSTableSliceIterator implements IColumnIterator public boolean hasNext() { - return reader.hasNext(); + return reader != null && reader.hasNext(); } public IColumn next() diff --git a/src/java/org/apache/cassandra/io/sstable/SSTable.java b/src/java/org/apache/cassandra/io/sstable/SSTable.java index 5137a51518..5504058dc7 100644 --- a/src/java/org/apache/cassandra/io/sstable/SSTable.java +++ b/src/java/org/apache/cassandra/io/sstable/SSTable.java @@ -22,9 +22,7 @@ package org.apache.cassandra.io.sstable; import java.io.File; import java.io.FileFilter; import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,6 +58,16 @@ public abstract class SSTable public static final String TEMPFILE_MARKER = "tmp"; + public static final Comparator maxTimestampComparator = new Comparator() + { + public int compare(SSTableReader o1, SSTableReader o2) + { + long ts1 = o1.getMaxTimestamp(); + long ts2 = o2.getMaxTimestamp(); + return (ts1 > ts2 ? -1 : (ts1 == ts2 ? 0 : 1)); + } + }; + public final Descriptor descriptor; protected final Set components; public final CFMetaData metadata; diff --git a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java index 0eca219287..8a81033eec 100644 --- a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java +++ b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java @@ -53,6 +53,7 @@ import static org.apache.cassandra.Util.column; import static org.apache.cassandra.Util.getBytes; import static org.junit.Assert.assertNull; +import org.junit.Assert; import org.junit.Test; public class ColumnFamilyStoreTest extends CleanupHelper @@ -68,22 +69,49 @@ public class ColumnFamilyStoreTest extends CleanupHelper random.nextBytes(bytes2); } + @Test + // create two sstables, and verify that we only deserialize data from the most recent one + public void testTimeSortedQuery() throws IOException, ExecutionException, InterruptedException + { + Table table = Table.open("Keyspace1"); + ColumnFamilyStore cfs = table.getColumnFamilyStore("Standard1"); + cfs.truncate().get(); + + RowMutation rm; + rm = new RowMutation("Keyspace1", ByteBufferUtil.bytes("key1")); + rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("Column1")), ByteBufferUtil.bytes("asdf"), 0); + rm.apply(); + cfs.forceBlockingFlush(); + + rm = new RowMutation("Keyspace1", ByteBufferUtil.bytes("key1")); + rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("Column1")), ByteBufferUtil.bytes("asdf"), 1); + rm.apply(); + cfs.forceBlockingFlush(); + + cfs.getRecentSSTablesPerReadHistogram(); // resets counts + cfs.getColumnFamily(QueryFilter.getNamesFilter(Util.dk("key1"), new QueryPath("Standard1", null), ByteBufferUtil.bytes("Column1"))); + assertEquals(1, cfs.getRecentSSTablesPerReadHistogram()[0]); + } + @Test public void testGetColumnWithWrongBF() throws IOException, ExecutionException, InterruptedException { + Table table = Table.open("Keyspace1"); + ColumnFamilyStore cfs = table.getColumnFamilyStore("Standard1"); + cfs.truncate().get(); + List rms = new LinkedList(); RowMutation rm; rm = new RowMutation("Keyspace1", ByteBufferUtil.bytes("key1")); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("Column1")), ByteBufferUtil.bytes("asdf"), 0); rm.add(new QueryPath("Standard1", null, ByteBufferUtil.bytes("Column2")), ByteBufferUtil.bytes("asdf"), 0); rms.add(rm); - ColumnFamilyStore store = Util.writeColumnFamily(rms); + Util.writeColumnFamily(rms); - Table table = Table.open("Keyspace1"); List ssTables = table.getAllSSTables(); assertEquals(1, ssTables.size()); ssTables.get(0).forceFilterFailures(); - ColumnFamily cf = store.getColumnFamily(QueryFilter.getIdentityFilter(Util.dk("key2"), new QueryPath("Standard1", null, ByteBufferUtil.bytes("Column1")))); + ColumnFamily cf = cfs.getColumnFamily(QueryFilter.getIdentityFilter(Util.dk("key2"), new QueryPath("Standard1", null, ByteBufferUtil.bytes("Column1")))); assertNull(cf); } diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionsTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionsTest.java index 5d757e8982..c5c76ea0fc 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionsTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionsTest.java @@ -185,6 +185,7 @@ public class CompactionsTest extends CleanupHelper store.forceBlockingFlush(); } Collection toCompact = store.getSSTables(); + assert toCompact.size() == 2; // Reinserting the same keys. We will compact only the previous sstable, but we need those new ones // to make sure we use EchoedRow, otherwise it won't be used because purge can be done. @@ -200,12 +201,15 @@ public class CompactionsTest extends CleanupHelper for (SSTableReader sstable : store.getSSTables()) if (!toCompact.contains(sstable)) tmpSSTable = sstable; + assert tmpSSTable != null; // Force compaction on first sstables. Since each row is in only one sstable, we will be using EchoedRow. Util.compact(store, toCompact, false); + assertEquals(2, store.getSSTables().size()); // Now, we remove the sstable that was just created to force the use of EchoedRow (so that it doesn't hide the problem) store.markCompacted(Collections.singleton(tmpSSTable)); + assertEquals(1, store.getSSTables().size()); // Now assert we do have the 4 keys assertEquals(4, Util.getRangeSlice(store).size());