Backport CASSANDRA-7386

This commit is contained in:
Robert Stupp 2014-11-25 19:45:34 -06:00 committed by Yuki Morishita
parent 41469ecf6a
commit 8b5cf64043
13 changed files with 295 additions and 152 deletions

View File

@ -25,6 +25,7 @@
CQLSSTableWriter (CASSANDRA-7463)
* Fix totalDiskSpaceUsed calculation (CASSANDRA-8205)
* Add DC-aware sequential repair (CASSANDRA-8193)
* Improve JBOD disk utilization (CASSANDRA-7386)
2.0.11:

View File

@ -1879,11 +1879,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
Directories.clearSnapshot(snapshotName, snapshotDirs);
}
public boolean hasUnreclaimedSpace()
{
return getLiveDiskSpaceUsed() < getTotalDiskSpaceUsed();
}
public long getTotalDiskSpaceUsed()
{
return metric.totalDiskSpaceUsed.count();

View File

@ -22,8 +22,7 @@ import java.io.FileFilter;
import java.io.IOError;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import com.google.common.collect.ImmutableMap;
@ -199,74 +198,114 @@ public class Directories
* @return SSTable location
*/
public File getLocationForDisk(DataDirectory dataDirectory)
{
if (dataDirectory != null)
for (File dir : sstableDirectories)
if (dir.getAbsolutePath().startsWith(dataDirectory.location.getAbsolutePath()))
return dir;
return null;
}
public Descriptor find(String filename)
{
for (File dir : sstableDirectories)
{
if (dir.getAbsolutePath().startsWith(dataDirectory.location.getAbsolutePath()))
return dir;
if (new File(dir, filename).exists())
return Descriptor.fromFilename(dir, filename).left;
}
return null;
}
public File getDirectoryForNewSSTables()
{
File path = getWriteableLocationAsFile();
// Requesting GC has a chance to free space only if we're using mmap and a non SUN jvm
if (path == null
&& (DatabaseDescriptor.getDiskAccessMode() == Config.DiskAccessMode.mmap || DatabaseDescriptor.getIndexAccessMode() == Config.DiskAccessMode.mmap)
&& !FileUtils.isCleanerAvailable())
{
logger.info("Forcing GC to free up disk space. Upgrade to the Oracle JVM to avoid this");
StorageService.instance.requestGC();
// retry after GCing has forced unmap of compacted SSTables so they can be deleted
// Note: GCInspector will do this already, but only sun JVM supports GCInspector so far
SSTableDeletingTask.rescheduleFailedTasks();
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
path = getWriteableLocationAsFile();
}
return path;
}
public File getWriteableLocationAsFile()
{
return getLocationForDisk(getWriteableLocation());
}
/**
* @return a non-blacklisted directory with the most free space and least current tasks.
* Basically the same as calling {@link #getWriteableLocationAsFile(long)} with an unknown size ({@code -1L}),
* which may return any non-blacklisted directory - even a data directory that has no usable space.
* Do not use this method in production code.
*
* @throws IOError if all directories are blacklisted.
*/
public DataDirectory getWriteableLocation()
public File getDirectoryForNewSSTables()
{
List<DataDirectory> candidates = new ArrayList<DataDirectory>();
return getWriteableLocationAsFile(-1L);
}
/**
* Returns a non-blacklisted data directory that _currently_ has {@code writeSize} bytes as usable space.
*
* @throws IOError if all directories are blacklisted.
*/
public File getWriteableLocationAsFile(long writeSize)
{
return getLocationForDisk(getWriteableLocation(writeSize));
}
/**
* Returns a non-blacklisted data directory that _currently_ has {@code writeSize} bytes as usable space.
*
* @throws IOError if all directories are blacklisted.
*/
public DataDirectory getWriteableLocation(long writeSize)
{
List<DataDirectoryCandidate> candidates = new ArrayList<>();
long totalAvailable = 0L;
// pick directories with enough space and so that resulting sstable dirs aren't blacklisted for writes.
boolean tooBig = false;
for (DataDirectory dataDir : dataFileLocations)
{
if (BlacklistedDirectories.isUnwritable(getLocationForDisk(dataDir)))
continue;
candidates.add(dataDir);
DataDirectoryCandidate candidate = new DataDirectoryCandidate(dataDir);
// exclude directory if its total writeSize does not fit to data directory
if (candidate.availableSpace < writeSize)
{
tooBig = true;
continue;
}
candidates.add(candidate);
totalAvailable += candidate.availableSpace;
}
if (candidates.isEmpty())
throw new IOError(new IOException("All configured data directories have been blacklisted as unwritable for erroring out"));
if (tooBig)
return null;
else
throw new IOError(new IOException("All configured data directories have been blacklisted as unwritable for erroring out"));
// sort directories by free space, in _descending_ order.
Collections.sort(candidates);
// shortcut for single data directory systems
if (candidates.size() == 1)
return candidates.get(0).dataDirectory;
// sort directories by load, in _ascending_ order.
Collections.sort(candidates, new Comparator<DataDirectory>()
sortWriteableCandidates(candidates, totalAvailable);
return pickWriteableDirectory(candidates);
}
// separated for unit testing
static DataDirectory pickWriteableDirectory(List<DataDirectoryCandidate> candidates)
{
// weighted random
double rnd = ThreadLocalRandom.current().nextDouble();
for (DataDirectoryCandidate candidate : candidates)
{
public int compare(DataDirectory a, DataDirectory b)
{
return a.currentTasks.get() - b.currentTasks.get();
}
});
rnd -= candidate.perc;
if (rnd <= 0)
return candidate.dataDirectory;
}
return candidates.get(0);
// last resort
return candidates.get(0).dataDirectory;
}
// separated for unit testing
static void sortWriteableCandidates(List<DataDirectoryCandidate> candidates, long totalAvailable)
{
// calculate free-space-percentage
for (DataDirectoryCandidate candidate : candidates)
candidate.calcFreePerc(totalAvailable);
// sort directories by perc
Collections.sort(candidates);
}
@ -285,31 +324,50 @@ public class Directories
return new SSTableLister();
}
public static class DataDirectory implements Comparable<DataDirectory>
public static class DataDirectory
{
public final File location;
public final AtomicInteger currentTasks = new AtomicInteger();
public final AtomicLong estimatedWorkingSize = new AtomicLong();
public DataDirectory(File location)
{
this.location = location;
}
/**
* @return estimated available disk space for bounded directory,
* excluding the expected size written by tasks in the queue.
*/
public long getEstimatedAvailableSpace()
public long getAvailableSpace()
{
// Load factor of 0.9 we do not want to use the entire disk that is too risky.
return location.getUsableSpace() - estimatedWorkingSize.get();
return location.getUsableSpace();
}
}
static final class DataDirectoryCandidate implements Comparable<DataDirectoryCandidate>
{
final DataDirectory dataDirectory;
final long availableSpace;
double perc;
public DataDirectoryCandidate(DataDirectory dataDirectory)
{
this.dataDirectory = dataDirectory;
this.availableSpace = dataDirectory.getAvailableSpace();
}
public int compareTo(DataDirectory o)
void calcFreePerc(long totalAvailableSpace)
{
// we want to sort by free space in descending order
return -1 * Longs.compare(getEstimatedAvailableSpace(), o.getEstimatedAvailableSpace());
double w = availableSpace;
w /= totalAvailableSpace;
perc = w;
}
public int compareTo(DataDirectoryCandidate o)
{
if (this == o)
return 0;
int r = Double.compare(perc, o.perc);
if (r != 0)
return -r;
// last resort
return System.identityHashCode(this) - System.identityHashCode(o);
}
}

View File

@ -342,17 +342,9 @@ public class Memtable
Directories.DataDirectory dataDirectory = getWriteDirectory(writeSize);
File sstableDirectory = cfs.directories.getLocationForDisk(dataDirectory);
assert sstableDirectory != null : "Flush task is not bound to any disk";
try
{
SSTableReader sstable = writeSortedContents(context, sstableDirectory);
cfs.replaceFlushed(Memtable.this, sstable);
latch.countDown();
}
finally
{
if (dataDirectory != null)
returnWriteDirectory(dataDirectory, writeSize);
}
SSTableReader sstable = writeSortedContents(context, sstableDirectory);
cfs.replaceFlushed(Memtable.this, sstable);
latch.countDown();
}
protected Directories getDirectories()

View File

@ -555,8 +555,7 @@ public class CompactionManager implements CompactionManagerMBean
logger.debug("Expected bloom filter size : " + expectedBloomFilterSize);
logger.info("Cleaning up " + sstable);
File compactionFileLocation = cfs.directories.getDirectoryForNewSSTables();
File compactionFileLocation = cfs.directories.getWriteableLocationAsFile(cfs.getExpectedCompactedFileSize(sstables, OperationType.CLEANUP));
if (compactionFileLocation == null)
throw new IOException("disk full");

View File

@ -153,55 +153,44 @@ public class CompactionTask extends AbstractCompactionTask
Directories.DataDirectory dataDirectory = getWriteDirectory(writeSize);
SSTableWriter writer = createCompactionWriter(cfs.directories.getLocationForDisk(dataDirectory), keysPerSSTable);
writers.add(writer);
try
while (iter.hasNext())
{
while (iter.hasNext())
if (ci.isStopRequested())
throw new CompactionInterruptedException(ci.getCompactionInfo());
AbstractCompactedRow row = iter.next();
RowIndexEntry indexEntry = writer.append(row);
if (indexEntry == null)
{
if (ci.isStopRequested())
throw new CompactionInterruptedException(ci.getCompactionInfo());
controller.invalidateCachedRow(row.key);
row.close();
continue;
}
AbstractCompactedRow row = iter.next();
RowIndexEntry indexEntry = writer.append(row);
if (indexEntry == null)
totalkeysWritten++;
if (DatabaseDescriptor.getPreheatKeyCache())
{
for (SSTableReader sstable : actuallyCompact)
{
controller.invalidateCachedRow(row.key);
row.close();
continue;
}
totalkeysWritten++;
if (DatabaseDescriptor.getPreheatKeyCache())
{
for (SSTableReader sstable : actuallyCompact)
if (sstable.getCachedPosition(row.key, false) != null)
{
if (sstable.getCachedPosition(row.key, false) != null)
{
cachedKeys.put(row.key, indexEntry);
break;
}
cachedKeys.put(row.key, indexEntry);
break;
}
}
if (newSSTableSegmentThresholdReached(writer))
{
// tmp = false because later we want to query it with descriptor from SSTableReader
cachedKeyMap.put(writer.descriptor.asTemporary(false), cachedKeys);
returnWriteDirectory(dataDirectory, writeSize);
// make sure we don't try to call returnWriteDirectory in finally {..} if we throw exception in getWriteDirectory() below:
dataDirectory = null;
writeSize = getExpectedWriteSize() / estimatedSSTables;
dataDirectory = getWriteDirectory(writeSize);
writer = createCompactionWriter(cfs.directories.getLocationForDisk(dataDirectory), keysPerSSTable);
writers.add(writer);
cachedKeys = new HashMap<DecoratedKey, RowIndexEntry>();
}
}
}
finally
{
if (dataDirectory != null)
returnWriteDirectory(dataDirectory, writeSize);
if (newSSTableSegmentThresholdReached(writer))
{
// tmp = false because later we want to query it with descriptor from SSTableReader
cachedKeyMap.put(writer.descriptor.asTemporary(false), cachedKeys);
writeSize = getExpectedWriteSize() / estimatedSSTables;
dataDirectory = getWriteDirectory(writeSize);
writer = createCompactionWriter(cfs.directories.getLocationForDisk(dataDirectory), keysPerSSTable);
writers.add(writer);
cachedKeys = new HashMap<>();
}
}
if (writer.getFilePointer() > 0)

View File

@ -76,12 +76,13 @@ public class Scrubber implements Closeable
this.outputHandler = outputHandler;
this.skipCorrupted = skipCorrupted;
List<SSTableReader> toScrub = Collections.singletonList(sstable);
// Calculate the expected compacted filesize
this.destination = cfs.directories.getDirectoryForNewSSTables();
this.destination = cfs.directories.getWriteableLocationAsFile(cfs.getExpectedCompactedFileSize(toScrub, OperationType.SCRUB));
if (destination == null)
throw new IOException("disk full");
List<SSTableReader> toScrub = Collections.singletonList(sstable);
// If we run scrub offline, we should never purge tombstone, as we cannot know if other sstable have data that the tombstone deletes.
this.controller = isOffline
? new ScrubController(cfs)

View File

@ -27,24 +27,16 @@ public abstract class DiskAwareRunnable extends WrappedRunnable
Directories.DataDirectory directory;
while (true)
{
directory = getDirectories().getWriteableLocation();
directory = getDirectories().getWriteableLocation(writeSize);
if (directory != null || !reduceScopeForLimitedSpace())
break;
}
if (directory == null)
throw new RuntimeException("Insufficient disk space to write " + writeSize + " bytes");
directory.currentTasks.incrementAndGet();
directory.estimatedWorkingSize.addAndGet(writeSize);
return directory;
}
protected void returnWriteDirectory(Directories.DataDirectory directory, long writeSize)
{
directory.estimatedWorkingSize.addAndGet(-1 * writeSize);
directory.currentTasks.decrementAndGet();
}
/**
* Get sstable directories for the CF.
* @return Directories instance for the CF.

View File

@ -3378,26 +3378,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
return isClientMode;
}
public synchronized void requestGC()
{
if (hasUnreclaimedSpace())
{
logger.info("requesting GC to free disk space");
System.gc();
Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
}
}
private boolean hasUnreclaimedSpace()
{
for (ColumnFamilyStore cfs : ColumnFamilyStore.all())
{
if (cfs.hasUnreclaimedSpace())
return true;
}
return false;
}
public String getOperationMode()
{
return operationMode.toString();

View File

@ -108,7 +108,7 @@ public class StreamReader
protected SSTableWriter createWriter(ColumnFamilyStore cfs, long totalSize) throws IOException
{
Directories.DataDirectory localDir = cfs.directories.getWriteableLocation();
Directories.DataDirectory localDir = cfs.directories.getWriteableLocation(totalSize);
if (localDir == null)
throw new IOException("Insufficient disk space to store " + totalSize + " bytes");
desc = Descriptor.fromFilename(cfs.getTempSSTablePath(cfs.directories.getLocationForDisk(localDir)));

View File

@ -17,6 +17,9 @@
*/
package org.apache.cassandra.streaming;
import java.io.File;
import java.io.IOError;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -113,7 +116,10 @@ public class StreamReceiveTask extends StreamTask
}
ColumnFamilyStore cfs = Keyspace.open(kscf.left).getColumnFamilyStore(kscf.right);
StreamLockfile lockfile = new StreamLockfile(cfs.directories.getWriteableLocationAsFile(), UUID.randomUUID());
File lockfiledir = cfs.directories.getWriteableLocationAsFile(task.sstables.size() * 256);
if (lockfiledir == null)
throw new IOError(new IOException("All disks full"));
StreamLockfile lockfile = new StreamLockfile(lockfiledir, UUID.randomUUID());
lockfile.create(task.sstables);
List<SSTableReader> readers = new ArrayList<>();
for (SSTableWriter writer : task.sstables)

View File

@ -235,7 +235,7 @@ public class CommitLogTest extends SchemaLoader
String newCLName = "CommitLog-" + CommitLogDescriptor.current_version + "-1340512736956320000.log";
Assert.assertEquals(MessagingService.current_version, CommitLogDescriptor.fromFileName(newCLName).getMessagingVersion());
}
/*
@Test
public void testCommitFailurePolicy_stop()
{
@ -262,7 +262,7 @@ public class CommitLogTest extends SchemaLoader
commitDir.setWritable(true);
}
}
*/
@Test
public void testTruncateWithoutSnapshot() throws ExecutionException, InterruptedException
{

View File

@ -35,6 +35,12 @@ import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.util.FileUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class DirectoriesTest
{
private static File tempDataDir;
@ -231,4 +237,128 @@ public class DirectoriesTest
}
}
}
@Test
public void testDiskFreeSpace()
{
DataDirectory[] dataDirectories = new DataDirectory[]
{
new DataDirectory(new File("/nearlyFullDir1"))
{
public long getAvailableSpace()
{
return 11L;
}
},
new DataDirectory(new File("/nearlyFullDir2"))
{
public long getAvailableSpace()
{
return 10L;
}
},
new DataDirectory(new File("/uniformDir1"))
{
public long getAvailableSpace()
{
return 1000L;
}
},
new DataDirectory(new File("/uniformDir2"))
{
public long getAvailableSpace()
{
return 999L;
}
},
new DataDirectory(new File("/veryFullDir"))
{
public long getAvailableSpace()
{
return 4L;
}
}
};
// directories should be sorted
// 1. by their free space ratio
// before weighted random is applied
List<Directories.DataDirectoryCandidate> candidates = getWriteableDirectories(dataDirectories, 0L);
assertSame(dataDirectories[2], candidates.get(0).dataDirectory); // available: 1000
assertSame(dataDirectories[3], candidates.get(1).dataDirectory); // available: 999
assertSame(dataDirectories[0], candidates.get(2).dataDirectory); // available: 11
assertSame(dataDirectories[1], candidates.get(3).dataDirectory); // available: 10
// check for writeSize == 5
Map<DataDirectory, DataDirectory> testMap = new IdentityHashMap<>();
for (int i=0; ; i++)
{
candidates = getWriteableDirectories(dataDirectories, 5L);
assertEquals(4, candidates.size());
DataDirectory dir = Directories.pickWriteableDirectory(candidates);
testMap.put(dir, dir);
assertFalse(testMap.size() > 4);
if (testMap.size() == 4)
{
// at least (rule of thumb) 100 iterations to see whether there are more (wrong) directories returned
if (i >= 100)
break;
}
// random weighted writeable directory algorithm fails to return all possible directories after
// many tries
if (i >= 10000000)
fail();
}
// check for writeSize == 11
testMap.clear();
for (int i=0; ; i++)
{
candidates = getWriteableDirectories(dataDirectories, 11L);
assertEquals(3, candidates.size());
for (Directories.DataDirectoryCandidate candidate : candidates)
assertTrue(candidate.dataDirectory.getAvailableSpace() >= 11L);
DataDirectory dir = Directories.pickWriteableDirectory(candidates);
testMap.put(dir, dir);
assertFalse(testMap.size() > 3);
if (testMap.size() == 3)
{
// at least (rule of thumb) 100 iterations
if (i >= 100)
break;
}
// random weighted writeable directory algorithm fails to return all possible directories after
// many tries
if (i >= 10000000)
fail();
}
}
private List<Directories.DataDirectoryCandidate> getWriteableDirectories(DataDirectory[] dataDirectories, long writeSize)
{
// copied from Directories.getWriteableLocation(long)
List<Directories.DataDirectoryCandidate> candidates = new ArrayList<>();
long totalAvailable = 0L;
for (DataDirectory dataDir : dataDirectories)
{
Directories.DataDirectoryCandidate candidate = new Directories.DataDirectoryCandidate(dataDir);
// exclude directory if its total writeSize does not fit to data directory
if (candidate.availableSpace < writeSize)
continue;
candidates.add(candidate);
totalAvailable += candidate.availableSpace;
}
Directories.sortWriteableCandidates(candidates, totalAvailable);
return candidates;
}
}