From abbcfc5f72323d0c6040a3cc02aba8f2c0058d95 Mon Sep 17 00:00:00 2001 From: Marcus Eriksson Date: Fri, 14 Nov 2014 10:54:39 +0100 Subject: [PATCH] Do more aggressive ttl expiration checks to be able to drop more sstables Patch by Bjorn Hegerfors; reviewed by marcuse and slebresne for CASSANDRA-8243 --- CHANGES.txt | 1 + .../db/compaction/CompactionController.java | 20 ++++---- .../db/compaction/TTLExpiryTest.java | 50 +++++++++++++++++++ 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 6228893a33..2476d25003 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.1.3 + * Do more aggressive entire-sstable TTL expiry checks (CASSANDRA-8243) * Add more log info if readMeter is null (CASSANDRA-8238) * add check of the system wall clock time at startup (CASSANDRA-8305) * Support for frozen collections (CASSANDRA-7859) diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionController.java b/src/java/org/apache/cassandra/db/compaction/CompactionController.java index ef27805594..f23d39a019 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionController.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionController.java @@ -92,12 +92,11 @@ public class CompactionController implements AutoCloseable * Finds expired sstables * * works something like this; - * 1. find "global" minTimestamp of overlapping sstables (excluding the possibly droppable ones) - * 2. build a list of candidates to be dropped - * 3. sort the candidate list, biggest maxTimestamp first in list - * 4. check if the candidates to be dropped actually can be dropped (maxTimestamp < global minTimestamp) and it is included in the compaction - * - if not droppable, update global minTimestamp and remove from candidates - * 5. return candidates. + * 1. find "global" minTimestamp of overlapping sstables and compacting sstables containing any non-expired data + * 2. build a list of fully expired candidates + * 3. check if the candidates to be dropped actually can be dropped (maxTimestamp < global minTimestamp) + * - if not droppable, remove from candidates + * 4. return candidates. * * @param cfStore * @param compacting we take the drop-candidates from this set, it is usually the sstables included in the compaction @@ -127,10 +126,10 @@ public class CompactionController implements AutoCloseable minTimestamp = Math.min(minTimestamp, candidate.getMinTimestamp()); } - // we still need to keep candidates that might shadow something in a - // non-candidate sstable. And if we remove a sstable from the candidates, we - // must take it's timestamp into account (hence the sorting below). - Collections.sort(candidates, SSTableReader.maxTimestampComparator); + // At this point, minTimestamp denotes the lowest timestamp of any relevant + // SSTable that contains a constructive value. candidates contains all the + // candidates with no constructive values. The ones out of these that have + // (getMaxTimestamp() < minTimestamp) serve no purpose anymore. Iterator iterator = candidates.iterator(); while (iterator.hasNext()) @@ -138,7 +137,6 @@ public class CompactionController implements AutoCloseable SSTableReader candidate = iterator.next(); if (candidate.getMaxTimestamp() >= minTimestamp) { - minTimestamp = Math.min(candidate.getMinTimestamp(), minTimestamp); iterator.remove(); } else diff --git a/test/unit/org/apache/cassandra/db/compaction/TTLExpiryTest.java b/test/unit/org/apache/cassandra/db/compaction/TTLExpiryTest.java index b98af68ae5..4fe5cfb818 100644 --- a/test/unit/org/apache/cassandra/db/compaction/TTLExpiryTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/TTLExpiryTest.java @@ -20,6 +20,7 @@ package org.apache.cassandra.db.compaction; * */ +import com.google.common.collect.Sets; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,12 +32,61 @@ import org.apache.cassandra.db.columniterator.OnDiskAtomIterator; import org.apache.cassandra.io.sstable.SSTableReader; import org.apache.cassandra.io.sstable.SSTableScanner; import org.apache.cassandra.utils.ByteBufferUtil; + +import java.util.Collections; +import java.util.Set; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @RunWith(OrderedJUnit4ClassRunner.class) public class TTLExpiryTest extends SchemaLoader { + @Test + public void testAggressiveFullyExpired() + { + ColumnFamilyStore cfs = Keyspace.open("Keyspace1").getColumnFamilyStore("Standard1"); + cfs.disableAutoCompaction(); + cfs.metadata.gcGraceSeconds(0); + + DecoratedKey ttlKey = Util.dk("ttl"); + Mutation rm = new Mutation("Keyspace1", ttlKey.getKey()); + rm.add("Standard1", Util.cellname("col1"), ByteBufferUtil.EMPTY_BYTE_BUFFER, 1, 1); + rm.add("Standard1", Util.cellname("col2"), ByteBufferUtil.EMPTY_BYTE_BUFFER, 3, 1); + rm.applyUnsafe(); + cfs.forceBlockingFlush(); + + rm = new Mutation("Keyspace1", ttlKey.getKey()); + rm.add("Standard1", Util.cellname("col1"), ByteBufferUtil.EMPTY_BYTE_BUFFER, 2, 1); + rm.add("Standard1", Util.cellname("col2"), ByteBufferUtil.EMPTY_BYTE_BUFFER, 5, 1); + rm.applyUnsafe(); + cfs.forceBlockingFlush(); + + rm = new Mutation("Keyspace1", ttlKey.getKey()); + rm.add("Standard1", Util.cellname("col1"), ByteBufferUtil.EMPTY_BYTE_BUFFER, 4, 1); + rm.add("Standard1", Util.cellname("shadow"), ByteBufferUtil.EMPTY_BYTE_BUFFER, 7, 1); + rm.applyUnsafe(); + cfs.forceBlockingFlush(); + + rm = new Mutation("Keyspace1", ttlKey.getKey()); + rm.add("Standard1", Util.cellname("shadow"), ByteBufferUtil.EMPTY_BYTE_BUFFER, 6, 3); + rm.add("Standard1", Util.cellname("col2"), ByteBufferUtil.EMPTY_BYTE_BUFFER, 8, 1); + rm.applyUnsafe(); + cfs.forceBlockingFlush(); + + Set sstables = Sets.newHashSet(cfs.getSSTables()); + int now = (int)(System.currentTimeMillis() / 1000); + int gcBefore = now + 2; + Set expired = CompactionController.getFullyExpiredSSTables( + cfs, + sstables, + Collections.EMPTY_SET, + gcBefore); + assertEquals(2, expired.size()); + + cfs.clearUnsafe(); + } + @Test public void testSimpleExpire() throws InterruptedException {