com.gemstone.gemfire.internal.cache.persistence.soplog.SizeTieredCompactor Maven / Gradle / Ivy
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed 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. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.internal.cache.persistence.soplog;
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import com.gemstone.gemfire.internal.cache.persistence.soplog.SortedOplog.SortedOplogReader;
/**
* Implements a size-tiered compaction scheme in which the soplogs are organized
* by levels of increasing size. Each level is limited to a fixed number of
* files, M
. Given an initial size of N
the amount of
* disk space consumed by a level L
is M * N^(L+1)
.
*
* During compaction, this approach will temporarily double the amount of space
* consumed by the level. Compactions are performed on a background thread.
*
* Soplogs that have been compacted will be moved to the inactive list where they
* will be deleted once they are no longer in use.
*
* @author bakera
*/
public class SizeTieredCompactor extends AbstractCompactor {
/** restricts the number of soplogs per level */
private final int maxFilesPerLevel;
// TODO consider relaxing the upper bound so the levels are created dynamically
/** restricts the number of levels; files in maxLevel are not compacted */
private final int maxLevels;
public SizeTieredCompactor(SortedOplogFactory factory,
Fileset fileset, CompactionTracker tracker,
Executor exec, int maxFilesPerLevel, int maxLevels)
throws IOException {
super(factory, fileset, tracker, exec);
assert maxFilesPerLevel > 0;
assert maxLevels > 0;
this.maxFilesPerLevel = maxFilesPerLevel;
this.maxLevels = maxLevels;
if (logger.fineEnabled()) {
logger.fine("Creating size-tiered compactor");
}
for (int i = 0; i < maxLevels; i++) {
levels.add(new OrderedLevel(i));
}
for (Map.Entry> entry : fileset.recover().entrySet()) {
int level = Math.min(maxLevels - 1, entry.getKey());
for (File f : entry.getValue()) {
if (logger.fineEnabled()) {
logger.fine("Adding " + f + " to level " + level);
}
levels.get(level).add(factory.createSortedOplog(f));
}
}
}
@Override
public String toString() {
return String.format("%s <%d/%d>", factory.getConfiguration().getName(), maxFilesPerLevel, maxLevels);
}
/**
* Organizes a set of soplogs for a given level. All operations on the
* soplogs are synchronized via the instance monitor.
*/
protected class OrderedLevel extends Level {
/** the ordered set of soplog readers */
private final Deque> soplogs;
/** true if the level is being compacted */
private final AtomicBoolean isCompacting;
public OrderedLevel(int level) {
super(level);
soplogs = new ArrayDeque>(maxFilesPerLevel);
isCompacting = new AtomicBoolean(false);
}
@Override
protected synchronized boolean needsCompaction() {
// TODO this is safe but overly conservative...we need to allow parallel
// compaction of a level such that we guarantee completion order and handle
// errors
return !isCompacting.get()
&& soplogs.size() >= maxFilesPerLevel
&& level != maxLevels - 1;
}
@Override
protected List> getSnapshot(byte[] start, byte[] end) {
// ignoring range limits since keys are stored in overlapping files
List> snap;
synchronized (this) {
snap = new ArrayList>(soplogs);
}
for (TrackedReference tr : snap) {
tr.increment();
}
return snap;
}
@Override
protected synchronized void clear() throws IOException {
for (TrackedReference tr : soplogs) {
tr.get().close();
}
markAsInactive(soplogs, level);
soplogs.clear();
}
@Override
protected synchronized void close() throws IOException {
for (TrackedReference tr : soplogs) {
tr.get().close();
factory.getConfiguration().getStatistics().incActiveFiles(-1);
}
soplogs.clear();
}
@Override
protected void add(SortedOplog soplog) throws IOException {
SortedOplogReader rdr = soplog.createReader();
synchronized (this) {
soplogs.addFirst(new TrackedReference(rdr));
}
if (logger.fineEnabled()) {
logger.fine(String.format("Added file %s to level %d", rdr, level));
}
tracker.fileAdded(rdr.getFile(), level);
factory.getConfiguration().getStatistics().incActiveFiles(1);
}
@Override
protected boolean compact(AtomicBoolean aborted) throws IOException {
assert level < maxLevels : "Can't compact level: " + level;
if (!isCompacting.compareAndSet(false, true)) {
// another thread won so gracefully bow out
return false;
}
try {
List> snapshot = getSnapshot(null, null);
try {
SortedOplog merged = merge(snapshot, level == maxLevels - 1, aborted);
synchronized (this) {
if (merged != null) {
levels.get(Math.min(level + 1, maxLevels - 1)).add(merged);
}
markAsInactive(snapshot, level);
soplogs.removeAll(snapshot);
}
} catch (InterruptedIOException e) {
if (logger.fineEnabled()) {
logger.fine("Aborting compaction of level " + level);
}
return false;
}
return true;
} finally {
boolean set = isCompacting.compareAndSet(true, false);
assert set;
}
}
}
}