Scrub (recover) sstables even when -Index.db is missing patch by mck; reviewed by stefania for CASSANDRA-9591
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/452d6a44 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/452d6a44 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/452d6a44 Branch: refs/heads/cassandra-2.2 Commit: 452d6a445a6935d3e7d0e0fdf59e87d2a2ff95e7 Parents: 62714a9 Author: Stefania Alborghetti <stefania.alborghe...@datastax.com> Authored: Mon Jun 15 16:49:03 2015 +0800 Committer: Benedict Elliott Smith <bened...@apache.org> Committed: Tue Jul 7 14:27:25 2015 +0100 ---------------------------------------------------------------------- CHANGES.txt | 4 ++ .../cassandra/db/compaction/Scrubber.java | 36 +++++++++++---- .../cassandra/io/sstable/SSTableReader.java | 47 ++++++++++++++++---- .../cassandra/tools/StandaloneScrubber.java | 2 +- .../unit/org/apache/cassandra/db/ScrubTest.java | 25 +++++++++++ 5 files changed, 96 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/452d6a44/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index ca4d4b5..bd1db92 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +2.0.18 +* Scrub (recover) sstables even when -Index.db is missing, (CASSANDRA-9591) + + 2.0.17 * Avoid NPE in AuthSuccess#decode (CASSANDRA-9727) * Add listen_address to system.local (CASSANDRA-9603) http://git-wip-us.apache.org/repos/asf/cassandra/blob/452d6a44/src/java/org/apache/cassandra/db/compaction/Scrubber.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/compaction/Scrubber.java b/src/java/org/apache/cassandra/db/compaction/Scrubber.java index ea10855..dc60efa 100644 --- a/src/java/org/apache/cassandra/db/compaction/Scrubber.java +++ b/src/java/org/apache/cassandra/db/compaction/Scrubber.java @@ -96,7 +96,16 @@ public class Scrubber implements Closeable ? new ScrubController(cfs) : new CompactionController(cfs, Collections.singleton(sstable), CompactionManager.getDefaultGcBefore(cfs)); this.isCommutative = cfs.metadata.getDefaultValidator().isCommutative(); - this.expectedBloomFilterSize = Math.max(cfs.metadata.getIndexInterval(), (int)(SSTableReader.getApproximateKeyCount(toScrub,cfs.metadata))); + + boolean hasIndexFile = (new File(sstable.descriptor.filenameFor(Component.PRIMARY_INDEX))).exists(); + if (!hasIndexFile) + { + // if there's any corruption in the -Data.db then rows can't be skipped over. but it's worth a shot. + outputHandler.warn("Missing component: " + sstable.descriptor.filenameFor(Component.PRIMARY_INDEX)); + } + + this.expectedBloomFilterSize = Math.max(cfs.metadata.getIndexInterval(), + hasIndexFile ? (int)(SSTableReader.getApproximateKeyCount(toScrub,cfs.metadata)) : 0); // loop through each row, deserializing to check for damage. // we'll also loop through the index at the same time, using the position from the index to recover if the @@ -105,7 +114,11 @@ public class Scrubber implements Closeable this.dataFile = isOffline ? sstable.openDataReader() : sstable.openDataReader(CompactionManager.instance.getRateLimiter()); - this.indexFile = RandomAccessReader.open(new File(sstable.descriptor.filenameFor(Component.PRIMARY_INDEX))); + + this.indexFile = hasIndexFile + ? RandomAccessReader.open(new File(sstable.descriptor.filenameFor(Component.PRIMARY_INDEX))) + : null; + this.scrubInfo = new ScrubInfo(dataFile, sstable); this.currentRowPositionFromIndex = 0; @@ -117,7 +130,8 @@ public class Scrubber implements Closeable outputHandler.output(String.format("Scrubbing %s (%s bytes)", sstable, dataFile.length())); try { - nextIndexKey = ByteBufferUtil.readWithShortLength(indexFile); + nextIndexKey = indexAvailable() ? ByteBufferUtil.readWithShortLength(indexFile) : null; + if (indexAvailable()) { // throw away variable so we don't have a side effect in the assert long firstRowPositionFromIndex = RowIndexEntry.serializer.deserialize(indexFile, sstable.descriptor.version).position; @@ -181,7 +195,7 @@ public class Scrubber implements Closeable outputHandler.debug(String.format("Index doublecheck: row %s is %s bytes", ByteBufferUtil.bytesToHex(currentIndexKey), dataSizeFromIndex)); } - assert currentIndexKey != null || indexFile.isEOF(); + assert currentIndexKey != null || !indexAvailable(); writer.mark(); try @@ -198,10 +212,10 @@ public class Scrubber implements Closeable if (dataSize > dataFile.length()) throw new IOError(new IOException("Impossible row size (greater than file length): " + dataSize)); - if (dataStart != dataStartFromIndex) + if (indexFile != null && dataStart != dataStartFromIndex) outputHandler.warn(String.format("Data file row position %d differs from index file row position %d", dataStart, dataStartFromIndex)); - if (dataSize != dataSizeFromIndex) + if (indexFile != null && dataSize != dataSizeFromIndex) outputHandler.warn(String.format("Data file row size %d differs from index file row size %d", dataSize, dataSizeFromIndex)); SSTableIdentityIterator atoms = new SSTableIdentityIterator(sstable, dataFile, key, dataSize, validateColumns); @@ -317,8 +331,9 @@ public class Scrubber implements Closeable currentRowPositionFromIndex = nextRowPositionFromIndex; try { - nextIndexKey = indexFile.isEOF() ? null : ByteBufferUtil.readWithShortLength(indexFile); - nextRowPositionFromIndex = indexFile.isEOF() + nextIndexKey = !indexAvailable() ? null : ByteBufferUtil.readWithShortLength(indexFile); + + nextRowPositionFromIndex = !indexAvailable() ? dataFile.length() : RowIndexEntry.serializer.deserialize(indexFile, sstable.descriptor.version).position; } @@ -330,6 +345,11 @@ public class Scrubber implements Closeable } } + private boolean indexAvailable() + { + return indexFile != null && !indexFile.isEOF(); + } + private void seekToNextRow() { while(nextRowPositionFromIndex < dataFile.length()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/452d6a44/src/java/org/apache/cassandra/io/sstable/SSTableReader.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/io/sstable/SSTableReader.java b/src/java/org/apache/cassandra/io/sstable/SSTableReader.java index 15808e8..8919a09 100644 --- a/src/java/org/apache/cassandra/io/sstable/SSTableReader.java +++ b/src/java/org/apache/cassandra/io/sstable/SSTableReader.java @@ -157,7 +157,7 @@ public class SSTableReader extends SSTable implements Closeable public static SSTableReader openForBatch(Descriptor descriptor, Set<Component> components, CFMetaData metadata, IPartitioner partitioner) throws IOException { - SSTableMetadata sstableMetadata = openMetadata(descriptor, components, partitioner); + SSTableMetadata sstableMetadata = openMetadata(descriptor, components, partitioner, true); SSTableReader sstable = new SSTableReader(descriptor, components, metadata, @@ -191,7 +191,7 @@ public class SSTableReader extends SSTable implements Closeable boolean validate) throws IOException { long start = System.nanoTime(); - SSTableMetadata sstableMetadata = openMetadata(descriptor, components, partitioner); + SSTableMetadata sstableMetadata = openMetadata(descriptor, components, partitioner, validate); SSTableReader sstable = new SSTableReader(descriptor, components, @@ -213,12 +213,17 @@ public class SSTableReader extends SSTable implements Closeable return sstable; } - private static SSTableMetadata openMetadata(Descriptor descriptor, Set<Component> components, IPartitioner partitioner) throws IOException + private static SSTableMetadata openMetadata(Descriptor descriptor, + Set<Component> components, + IPartitioner partitioner, + boolean primaryIndexRequired) throws IOException { assert partitioner != null; // Minimum components without which we can't do anything assert components.contains(Component.DATA) : "Data component is missing for sstable" + descriptor; - assert components.contains(Component.PRIMARY_INDEX) : "Primary index component is missing for sstable " + descriptor; + + assert !primaryIndexRequired || components.contains(Component.PRIMARY_INDEX) + : "Primary index component is missing for sstable " + descriptor; logger.info("Opening {} ({} bytes)", descriptor, new File(descriptor.filenameFor(COMPONENT_DATA)).length()); @@ -382,11 +387,17 @@ public class SSTableReader extends SSTable implements Closeable readMeterSyncFuture.cancel(false); // Force finalizing mmapping if necessary - ifile.cleanup(); + + if (null != ifile) + ifile.cleanup(); + dfile.cleanup(); // close the BF so it can be opened later. - bf.close(); - indexSummary.close(); + if (null != bf) + bf.close(); + + if (null != indexSummary) + indexSummary.close(); } public void setTrackedBy(DataTracker tracker) @@ -406,6 +417,12 @@ public class SSTableReader extends SSTable implements Closeable load(false, true); bf = FilterFactory.AlwaysPresent; } + else if (!components.contains(Component.PRIMARY_INDEX)) + { + // avoid any reading of the missing primary index component. + // this should only happen during StandaloneScrubber + load(false, false); + } else if (!components.contains(Component.FILTER)) { // bf is enabled, but filter component is missing. @@ -454,7 +471,9 @@ public class SSTableReader extends SSTable implements Closeable if (recreateBloomFilter || !summaryLoaded) buildSummary(recreateBloomFilter, ibuilder, dbuilder, summaryLoaded); - ifile = ibuilder.complete(descriptor.filenameFor(Component.PRIMARY_INDEX)); + if (components.contains(Component.PRIMARY_INDEX)) + ifile = ibuilder.complete(descriptor.filenameFor(Component.PRIMARY_INDEX)); + dfile = dbuilder.complete(descriptor.filenameFor(Component.DATA)); if (saveSummaryIfCreated && (recreateBloomFilter || !summaryLoaded)) // save summary information to disk saveSummary(this, ibuilder, dbuilder); @@ -462,6 +481,9 @@ public class SSTableReader extends SSTable implements Closeable private void buildSummary(boolean recreateBloomFilter, SegmentedFile.Builder ibuilder, SegmentedFile.Builder dbuilder, boolean summaryLoaded) throws IOException { + if (!components.contains(Component.PRIMARY_INDEX)) + return; + // we read the positions in a BRAF so we don't have to worry about an entry spanning a mmap boundary. RandomAccessReader primaryIndex = RandomAccessReader.open(new File(descriptor.filenameFor(Component.PRIMARY_INDEX))); @@ -965,6 +987,9 @@ public class SSTableReader extends SSTable implements Closeable } } + if (ifile == null) + return null; + // scan the on-disk index, starting at the nearest sampled position. // The check against IndexInterval is to be exit the loop in the EQ case when the key looked for is not present // (bloom filter false positive). But note that for non-EQ cases, we might need to check the first key of the @@ -1063,6 +1088,9 @@ public class SSTableReader extends SSTable implements Closeable if (sampledPosition == -1) sampledPosition = 0; + if (ifile == null) + return null; + Iterator<FileDataInput> segments = ifile.iterator(sampledPosition); while (segments.hasNext()) { @@ -1480,7 +1508,8 @@ public class SSTableReader extends SSTable implements Closeable private void dropPageCache() { dropPageCache(dfile.path); - dropPageCache(ifile.path); + if (null != ifile) + dropPageCache(ifile.path); } private void dropPageCache(String filePath) http://git-wip-us.apache.org/repos/asf/cassandra/blob/452d6a44/src/java/org/apache/cassandra/tools/StandaloneScrubber.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/tools/StandaloneScrubber.java b/src/java/org/apache/cassandra/tools/StandaloneScrubber.java index 26768f3..8ace228 100644 --- a/src/java/org/apache/cassandra/tools/StandaloneScrubber.java +++ b/src/java/org/apache/cassandra/tools/StandaloneScrubber.java @@ -79,7 +79,7 @@ public class StandaloneScrubber for (Map.Entry<Descriptor, Set<Component>> entry : lister.list().entrySet()) { Set<Component> components = entry.getValue(); - if (!components.contains(Component.DATA) || !components.contains(Component.PRIMARY_INDEX)) + if (!components.contains(Component.DATA)) continue; try http://git-wip-us.apache.org/repos/asf/cassandra/blob/452d6a44/test/unit/org/apache/cassandra/db/ScrubTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/db/ScrubTest.java b/test/unit/org/apache/cassandra/db/ScrubTest.java index 94c7e34..978b3e8 100644 --- a/test/unit/org/apache/cassandra/db/ScrubTest.java +++ b/test/unit/org/apache/cassandra/db/ScrubTest.java @@ -270,6 +270,31 @@ public class ScrubTest extends SchemaLoader } @Test + public void testScrubNoIndex() throws IOException, ExecutionException, InterruptedException, ConfigurationException + { + CompactionManager.instance.disableAutoCompaction(); + Keyspace keyspace = Keyspace.open(KEYSPACE); + ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF); + cfs.clearUnsafe(); + + List<Row> rows; + + // insert data and verify we get it back w/ range query + fillCF(cfs, 10); + rows = cfs.getRangeSlice(Util.range("", ""), null, new IdentityQueryFilter(), 1000); + assertEquals(10, rows.size()); + + for (SSTableReader sstable : cfs.getSSTables()) + new File(sstable.descriptor.filenameFor(Component.PRIMARY_INDEX)).delete(); + + CompactionManager.instance.performScrub(cfs, false, true); + + // check data is still there + rows = cfs.getRangeSlice(Util.range("", ""), null, new IdentityQueryFilter(), 1000); + assertEquals(10, rows.size()); + } + + @Test public void testScrubOutOfOrder() throws Exception { CompactionManager.instance.disableAutoCompaction();