Keep persistent row size and column count statistics. Patch by brandonwilliams and jbellis; reviewed by jbellis for CASSANDRA-1155

git-svn-id: https://svn.apache.org/repos/asf/cassandra/trunk@967213 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Brandon Williams 2010-07-23 19:22:58 +00:00
parent 7a926e5bff
commit c00a5c1f58
16 changed files with 377 additions and 58 deletions

View File

@ -44,6 +44,7 @@ dev
* significantly faster reads from row cache (CASSANDRA-1267) * significantly faster reads from row cache (CASSANDRA-1267)
* take advantage of row cache during range queries (CASSANDRA-1302) * take advantage of row cache during range queries (CASSANDRA-1302)
* make GCGraceSeconds a per-ColumnFamily value (CASSANDRA-1276) * make GCGraceSeconds a per-ColumnFamily value (CASSANDRA-1276)
* keep persistent row size and column count statistics (CASSANDRA-1155)
0.6.4 0.6.4

View File

@ -35,6 +35,7 @@ import org.apache.cassandra.db.clock.AbstractReconciler;
import org.apache.cassandra.db.clock.TimestampReconciler; import org.apache.cassandra.db.clock.TimestampReconciler;
import org.apache.cassandra.db.HintedHandOffManager; import org.apache.cassandra.db.HintedHandOffManager;
import org.apache.cassandra.db.SystemTable; import org.apache.cassandra.db.SystemTable;
import org.apache.cassandra.db.StatisticsTable;
import org.apache.cassandra.db.Table; import org.apache.cassandra.db.Table;
import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.BytesType;
@ -64,6 +65,7 @@ public final class CFMetaData
public static final CFMetaData HintsCf = new CFMetaData(Table.SYSTEM_TABLE, HintedHandOffManager.HINTS_CF, ColumnFamilyType.Super, ClockType.Timestamp, UTF8Type.instance, BytesType.instance, new TimestampReconciler(), "hinted handoff data", 0, false, 0.01, DEFAULT_GC_GRACE_SECONDS, 1, Collections.<byte[], ColumnDefinition>emptyMap()); public static final CFMetaData HintsCf = new CFMetaData(Table.SYSTEM_TABLE, HintedHandOffManager.HINTS_CF, ColumnFamilyType.Super, ClockType.Timestamp, UTF8Type.instance, BytesType.instance, new TimestampReconciler(), "hinted handoff data", 0, false, 0.01, DEFAULT_GC_GRACE_SECONDS, 1, Collections.<byte[], ColumnDefinition>emptyMap());
public static final CFMetaData MigrationsCf = new CFMetaData(Table.SYSTEM_TABLE, Migration.MIGRATIONS_CF, ColumnFamilyType.Standard, ClockType.Timestamp, TimeUUIDType.instance, null, new TimestampReconciler(), "individual schema mutations", 0, false, 0.01, DEFAULT_GC_GRACE_SECONDS, 2, Collections.<byte[], ColumnDefinition>emptyMap()); public static final CFMetaData MigrationsCf = new CFMetaData(Table.SYSTEM_TABLE, Migration.MIGRATIONS_CF, ColumnFamilyType.Standard, ClockType.Timestamp, TimeUUIDType.instance, null, new TimestampReconciler(), "individual schema mutations", 0, false, 0.01, DEFAULT_GC_GRACE_SECONDS, 2, Collections.<byte[], ColumnDefinition>emptyMap());
public static final CFMetaData SchemaCf = new CFMetaData(Table.SYSTEM_TABLE, Migration.SCHEMA_CF, ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, new TimestampReconciler(), "current state of the schema", 0, false, 0.01, DEFAULT_GC_GRACE_SECONDS, 3, Collections. <byte[], ColumnDefinition>emptyMap()); public static final CFMetaData SchemaCf = new CFMetaData(Table.SYSTEM_TABLE, Migration.SCHEMA_CF, ColumnFamilyType.Standard, ClockType.Timestamp, UTF8Type.instance, null, new TimestampReconciler(), "current state of the schema", 0, false, 0.01, DEFAULT_GC_GRACE_SECONDS, 3, Collections. <byte[], ColumnDefinition>emptyMap());
public static final CFMetaData StatisticsCf = new CFMetaData(Table.SYSTEM_TABLE, StatisticsTable.STATISTICS_CF, ColumnFamilyType.Super, ClockType.Timestamp, UTF8Type.instance, BytesType.instance, new TimestampReconciler(), "persistent CF statistics for the local node", 0, false, 0.01, DEFAULT_GC_GRACE_SECONDS, 4, Collections.<byte[], ColumnDefinition>emptyMap());
/** /**
* @return An immutable mapping of (ksname,cfname) to id. * @return An immutable mapping of (ksname,cfname) to id.

View File

@ -348,12 +348,14 @@ public class DatabaseDescriptor
KSMetaData systemMeta = new KSMetaData(Table.SYSTEM_TABLE, LocalStrategy.class, 1, new CFMetaData[]{CFMetaData.StatusCf, KSMetaData systemMeta = new KSMetaData(Table.SYSTEM_TABLE, LocalStrategy.class, 1, new CFMetaData[]{CFMetaData.StatusCf,
CFMetaData.HintsCf, CFMetaData.HintsCf,
CFMetaData.MigrationsCf, CFMetaData.MigrationsCf,
CFMetaData.SchemaCf CFMetaData.SchemaCf,
CFMetaData.StatisticsCf
}); });
CFMetaData.map(CFMetaData.StatusCf); CFMetaData.map(CFMetaData.StatusCf);
CFMetaData.map(CFMetaData.HintsCf); CFMetaData.map(CFMetaData.HintsCf);
CFMetaData.map(CFMetaData.MigrationsCf); CFMetaData.map(CFMetaData.MigrationsCf);
CFMetaData.map(CFMetaData.SchemaCf); CFMetaData.map(CFMetaData.SchemaCf);
CFMetaData.map(CFMetaData.StatisticsCf);
tables.put(Table.SYSTEM_TABLE, systemMeta); tables.put(Table.SYSTEM_TABLE, systemMeta);
/* Load the seeds for node contact points */ /* Load the seeds for node contact points */

View File

@ -69,18 +69,20 @@ public class ColumnFamilySerializer implements ICompactSerializer2<ColumnFamily>
serializeForSSTable(columnFamily, dos); serializeForSSTable(columnFamily, dos);
} }
public void serializeForSSTable(ColumnFamily columnFamily, DataOutput dos) public int serializeForSSTable(ColumnFamily columnFamily, DataOutput dos)
{ {
try try
{ {
serializeCFInfo(columnFamily, dos); serializeCFInfo(columnFamily, dos);
Collection<IColumn> columns = columnFamily.getSortedColumns(); Collection<IColumn> columns = columnFamily.getSortedColumns();
dos.writeInt(columns.size()); int count = columns.size();
dos.writeInt(count);
for (IColumn column : columns) for (IColumn column : columns)
{ {
columnFamily.getColumnSerializer().serialize(column, dos); columnFamily.getColumnSerializer().serialize(column, dos);
} }
return count;
} }
catch (IOException e) catch (IOException e)
{ {
@ -95,10 +97,10 @@ public class ColumnFamilySerializer implements ICompactSerializer2<ColumnFamily>
columnFamily.getClockType().serializer().serialize(_markedForDeleteAt, dos); columnFamily.getClockType().serializer().serialize(_markedForDeleteAt, dos);
} }
public void serializeWithIndexes(ColumnFamily columnFamily, DataOutput dos) public int serializeWithIndexes(ColumnFamily columnFamily, DataOutput dos)
{ {
ColumnIndexer.serialize(columnFamily, dos); ColumnIndexer.serialize(columnFamily, dos);
serializeForSSTable(columnFamily, dos); return serializeForSSTable(columnFamily, dos);
} }
public ColumnFamily deserialize(DataInput dis) throws IOException public ColumnFamily deserialize(DataInput dis) throws IOException

View File

@ -98,7 +98,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
TimeUnit.SECONDS, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(DatabaseDescriptor.getFlushWriters()), new LinkedBlockingQueue<Runnable>(DatabaseDescriptor.getFlushWriters()),
new NamedThreadFactory("FLUSH-WRITER-POOL")); new NamedThreadFactory("FLUSH-WRITER-POOL"));
private static ExecutorService commitLogUpdater_ = new JMXEnabledThreadPoolExecutor("MEMTABLE-POST-FLUSHER"); private static ExecutorService postFlushExecutor_ = new JMXEnabledThreadPoolExecutor("MEMTABLE-POST-FLUSHER");
private static final FilenameFilter DB_NAME_FILTER = new FilenameFilter() private static final FilenameFilter DB_NAME_FILTER = new FilenameFilter()
{ {
@ -133,10 +133,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
private LatencyTracker readStats_ = new LatencyTracker(); private LatencyTracker readStats_ = new LatencyTracker();
private LatencyTracker writeStats_ = new LatencyTracker(); private LatencyTracker writeStats_ = new LatencyTracker();
private long minRowCompactedSize = 0L;
private long maxRowCompactedSize = 0L;
private long rowsCompactedTotalSize = 0L;
private long rowsCompactedCount = 0L;
final CFMetaData metadata; final CFMetaData metadata;
ColumnFamilyStore(String table, String columnFamilyName, IPartitioner partitioner, int generation, CFMetaData metadata) ColumnFamilyStore(String table, String columnFamilyName, IPartitioner partitioner, int generation, CFMetaData metadata)
@ -257,32 +253,41 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
} }
} }
public void addToCompactedRowStats(long rowsize)
{
if (minRowCompactedSize < 1 || rowsize < minRowCompactedSize)
minRowCompactedSize = rowsize;
if (rowsize > maxRowCompactedSize)
maxRowCompactedSize = rowsize;
rowsCompactedCount++;
rowsCompactedTotalSize += rowsize;
}
public long getMinRowCompactedSize() public long getMinRowCompactedSize()
{ {
return minRowCompactedSize; long min = 0;
for (SSTableReader sstable : ssTables_)
{
if (min == 0 || sstable.getEstimatedRowSize().min() < min)
min = sstable.getEstimatedRowSize().min();
}
return min;
} }
public long getMaxRowCompactedSize() public long getMaxRowCompactedSize()
{ {
return maxRowCompactedSize; long max = 0;
for (SSTableReader sstable : ssTables_)
{
if (sstable.getEstimatedRowSize().max() > max)
max = sstable.getEstimatedRowSize().max();
}
return max;
} }
public long getMeanRowCompactedSize() public long getMeanRowCompactedSize()
{ {
if (rowsCompactedCount > 0) long sum = 0;
return rowsCompactedTotalSize / rowsCompactedCount; long count = 0;
else for (SSTableReader sstable : ssTables_)
return 0L; {
if (sstable.getEstimatedRowSize().median() > 0)
{
sum += sstable.getEstimatedRowSize().median();
count++;
}
}
return count > 0 ? sum / count : 0;
} }
public static ColumnFamilyStore createColumnFamilyStore(String table, String columnFamily) public static ColumnFamilyStore createColumnFamilyStore(String table, String columnFamily)
@ -414,7 +419,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
// when all the memtables have been written, including for indexes, mark the flush in the commitlog header. // when all the memtables have been written, including for indexes, mark the flush in the commitlog header.
// a second executor makes sure the onMemtableFlushes get called in the right order, // a second executor makes sure the onMemtableFlushes get called in the right order,
// while keeping the wait-for-flush (future.get) out of anything latency-sensitive. // while keeping the wait-for-flush (future.get) out of anything latency-sensitive.
return commitLogUpdater_.submit(new WrappedRunnable() return postFlushExecutor_.submit(new WrappedRunnable()
{ {
public void runMayThrow() throws InterruptedException, IOException public void runMayThrow() throws InterruptedException, IOException
{ {
@ -1295,7 +1300,12 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
} }
}; };
return commitLogUpdater_.submit(runnable); return postFlushExecutor_.submit(runnable);
}
public static Future<?> submitPostFlush(Runnable runnable)
{
return postFlushExecutor_.submit(runnable);
} }
public long getBloomFilterFalsePositives() public long getBloomFilterFalsePositives()

View File

@ -348,9 +348,6 @@ public class CompactionManager implements CompactionManagerMBean
writer.append(row); writer.append(row);
totalkeysWritten++; totalkeysWritten++;
long rowsize = writer.getFilePointer() - prevpos;
cfs.addToCompactedRowStats(rowsize);
} }
} }
finally finally

View File

@ -0,0 +1,129 @@
/**
* 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 java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.db.filter.QueryFilter;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.EstimatedHistogram;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.service.StorageService;
import static com.google.common.base.Charsets.UTF_8;
public class StatisticsTable
{
private static Logger logger = LoggerFactory.getLogger(StatisticsTable.class);
public static final String STATISTICS_CF = "Statistics";
public static final byte[] ROWSIZE_SC = "RowSize".getBytes(UTF_8);
public static final byte[] COLUMNCOUNT_SC = "ColumnCount".getBytes(UTF_8);
private static DecoratedKey decorate(byte[] key)
{
return StorageService.getPartitioner().decorateKey(key);
}
public static void persistSSTableStatistics(Descriptor desc, EstimatedHistogram rowsizes, EstimatedHistogram columncounts) throws IOException
{
String filename = getRelativePath(desc.filenameFor(SSTable.COMPONENT_DATA));
if (isTemporary(filename) || desc.ksname.equals(Table.SYSTEM_TABLE))
return;
long[] rowbuckets = rowsizes.getBucketOffsets();
long[] rowvalues = rowsizes.get(false);
long[] columnbuckets = columncounts.getBucketOffsets();
long[] columnvalues = columncounts.get(false);
RowMutation rm = new RowMutation(Table.SYSTEM_TABLE, filename.getBytes(UTF_8));
for (int i=0; i<rowbuckets.length; i++)
{
QueryPath path = new QueryPath(STATISTICS_CF, ROWSIZE_SC, FBUtilities.toByteArray(rowbuckets[i]));
rm.add(path, FBUtilities.toByteArray(rowvalues[i]), new TimestampClock(System.currentTimeMillis()));
}
for (int i=0; i<columnbuckets.length; i++)
{
QueryPath path = new QueryPath(STATISTICS_CF, COLUMNCOUNT_SC, FBUtilities.toByteArray(columnbuckets[i]));
rm.add(path, FBUtilities.toByteArray(columnvalues[i]), new TimestampClock(System.currentTimeMillis()));
}
rm.apply();
if (logger.isDebugEnabled())
logger.debug("Recorded SSTable statistics for " + filename);
}
public static void deleteSSTableStatistics(String filepath) throws IOException
{
String filename = getRelativePath(filepath);
RowMutation rm = new RowMutation(Table.SYSTEM_TABLE, filename.getBytes(UTF_8));
QueryPath path = new QueryPath(STATISTICS_CF);
rm.delete(path, new TimestampClock(System.currentTimeMillis()));
rm.apply();
if (logger.isDebugEnabled())
logger.debug("Deleted SSTable statistics for " + filename);
}
private static long[] getSSTableStatistics(String filepath, byte[] superCol) throws IOException
{
long[] rv;
String filename = getRelativePath(filepath);
QueryPath path = new QueryPath(STATISTICS_CF);
QueryFilter filter = QueryFilter.getNamesFilter(decorate(filename.getBytes(UTF_8)), path, superCol);
ColumnFamily cf = Table.open(Table.SYSTEM_TABLE).getColumnFamilyStore(STATISTICS_CF).getColumnFamily(filter);
if (cf == null)
return new long[0];
IColumn scolumn = cf.getColumn(superCol);
rv = new long[scolumn.getSubColumns().size()];
int i = 0;
for (IColumn col : scolumn.getSubColumns()) {
rv[i] = ByteBuffer.wrap(col.value()).getLong();
i++;
}
return rv;
}
public static long [] getSSTableRowSizeStatistics(String filename) throws IOException
{
return getSSTableStatistics(filename, ROWSIZE_SC);
}
public static long [] getSSTableColumnCountStatistics(String filename) throws IOException
{
return getSSTableStatistics(filename, COLUMNCOUNT_SC);
}
private static String getRelativePath(String filename)
{
for (String prefix : DatabaseDescriptor.getAllDataFileLocations())
{
if (filename.startsWith(prefix))
return filename.substring(prefix.length());
}
return filename;
}
private static boolean isTemporary(String filename)
{
return filename.contains("-" + SSTable.TEMPFILE_MARKER);
}
}

View File

@ -25,4 +25,6 @@ public abstract class AbstractCompactedRow
public abstract void update(MessageDigest digest); public abstract void update(MessageDigest digest);
public abstract boolean isEmpty(); public abstract boolean isEmpty();
public abstract int columnCount();
} }

View File

@ -151,6 +151,11 @@ public class LazilyCompactedRow extends AbstractCompactedRow implements IIterabl
return Iterators.filter(iter, Predicates.notNull()); return Iterators.filter(iter, Predicates.notNull());
} }
public int columnCount()
{
return columnCount;
}
private class LazyColumnIterator extends ReducingIterator<IColumn, IColumn> private class LazyColumnIterator extends ReducingIterator<IColumn, IColumn>
{ {
ColumnFamily container = emptyColumnFamily.cloneMeShallow(); ColumnFamily container = emptyColumnFamily.cloneMeShallow();

View File

@ -22,6 +22,7 @@ public class PrecompactedRow extends AbstractCompactedRow
private static Logger logger = LoggerFactory.getLogger(PrecompactedRow.class); private static Logger logger = LoggerFactory.getLogger(PrecompactedRow.class);
private final DataOutputBuffer buffer; private final DataOutputBuffer buffer;
private int columnCount = 0;
public PrecompactedRow(DecoratedKey key, DataOutputBuffer buffer) public PrecompactedRow(DecoratedKey key, DataOutputBuffer buffer)
{ {
@ -61,7 +62,7 @@ public class PrecompactedRow extends AbstractCompactedRow
ColumnFamily cfPurged = major ? ColumnFamilyStore.removeDeleted(cf, gcBefore) : cf; ColumnFamily cfPurged = major ? ColumnFamilyStore.removeDeleted(cf, gcBefore) : cf;
if (cfPurged == null) if (cfPurged == null)
return; return;
ColumnFamily.serializer().serializeWithIndexes(cfPurged, buffer); columnCount = ColumnFamily.serializer().serializeWithIndexes(cfPurged, buffer);
} }
else else
{ {
@ -69,6 +70,7 @@ public class PrecompactedRow extends AbstractCompactedRow
try try
{ {
rows.get(0).echoData(buffer); rows.get(0).echoData(buffer);
columnCount = rows.get(0).getColumnCount();
} }
catch (IOException e) catch (IOException e)
{ {
@ -92,4 +94,9 @@ public class PrecompactedRow extends AbstractCompactedRow
{ {
return buffer.getLength() == 0; return buffer.getLength() == 0;
} }
public int columnCount()
{
return columnCount;
}
} }

View File

@ -31,6 +31,8 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.db.StatisticsTable;
import org.apache.cassandra.utils.EstimatedHistogram;
/** /**
* This class is built on top of the SequenceFile. It stores * This class is built on top of the SequenceFile. It stores
@ -61,6 +63,8 @@ public abstract class SSTable
public static final String TEMPFILE_MARKER = "tmp"; public static final String TEMPFILE_MARKER = "tmp";
public static List<String> components = Collections.unmodifiableList(Arrays.asList(COMPONENT_FILTER, COMPONENT_INDEX, COMPONENT_DATA)); public static List<String> components = Collections.unmodifiableList(Arrays.asList(COMPONENT_FILTER, COMPONENT_INDEX, COMPONENT_DATA));
protected EstimatedHistogram estimatedRowSize = new EstimatedHistogram(130);
protected EstimatedHistogram estimatedColumnCount = new EstimatedHistogram(112);
protected SSTable(String filename, IPartitioner partitioner) protected SSTable(String filename, IPartitioner partitioner)
{ {
@ -75,6 +79,16 @@ public abstract class SSTable
this.partitioner = partitioner; this.partitioner = partitioner;
} }
public EstimatedHistogram getEstimatedRowSize()
{
return estimatedRowSize;
}
public EstimatedHistogram getEstimatedColumnCount()
{
return estimatedColumnCount;
}
public IPartitioner getPartitioner() public IPartitioner getPartitioner()
{ {
return partitioner; return partitioner;
@ -119,6 +133,7 @@ public abstract class SSTable
FileUtils.deleteWithConfirm(new File(SSTable.indexFilename(dataFilename))); FileUtils.deleteWithConfirm(new File(SSTable.indexFilename(dataFilename)));
FileUtils.deleteWithConfirm(new File(SSTable.filterFilename(dataFilename))); FileUtils.deleteWithConfirm(new File(SSTable.filterFilename(dataFilename)));
FileUtils.deleteWithConfirm(new File(SSTable.compactedFilename(dataFilename))); FileUtils.deleteWithConfirm(new File(SSTable.compactedFilename(dataFilename)));
StatisticsTable.deleteSSTableStatistics(dataFilename);
} }
catch (IOException e) catch (IOException e)
{ {

View File

@ -26,11 +26,13 @@ import java.lang.ref.Reference;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import org.apache.cassandra.db.StatisticsTable;
import org.apache.cassandra.io.util.BufferedRandomAccessFile; import org.apache.cassandra.io.util.BufferedRandomAccessFile;
import org.apache.cassandra.io.util.SegmentedFile; import org.apache.cassandra.io.util.SegmentedFile;
import org.apache.cassandra.utils.BloomFilter; import org.apache.cassandra.utils.BloomFilter;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.EstimatedHistogram;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -140,6 +142,22 @@ public class SSTableReader extends SSTable implements Comparable<SSTableReader>
return count; return count;
} }
private void loadStatistics(Descriptor desc) throws IOException
{
// skip loading stats for the system table, or we will infinitely recurse
if (desc.ksname.equals(Table.SYSTEM_TABLE))
return;
if (logger.isDebugEnabled())
logger.debug("Load statistics for " + desc);
long[] rowsizes = StatisticsTable.getSSTableRowSizeStatistics(desc.filenameFor(SSTable.COMPONENT_DATA));
long[] colcounts = StatisticsTable.getSSTableColumnCountStatistics(desc.filenameFor(SSTable.COMPONENT_DATA));
if (rowsizes.length > 0)
{
estimatedRowSize = new EstimatedHistogram(rowsizes);
estimatedColumnCount = new EstimatedHistogram(colcounts);
}
}
public static SSTableReader open(String dataFileName) throws IOException public static SSTableReader open(String dataFileName) throws IOException
{ {
return open(Descriptor.fromFilename(dataFileName)); return open(Descriptor.fromFilename(dataFileName));
@ -191,17 +209,38 @@ public class SSTableReader extends SSTable implements Comparable<SSTableReader>
sstable.load(false); sstable.load(false);
sstable.loadBloomFilter(); sstable.loadBloomFilter();
} }
sstable.loadStatistics(desc);
return sstable; return sstable;
} }
SSTableReader(Descriptor desc,
IPartitioner partitioner,
SegmentedFile ifile,
SegmentedFile dfile,
IndexSummary indexSummary,
BloomFilter bloomFilter,
long maxDataAge)
throws IOException
{
super(desc, partitioner);
this.maxDataAge = maxDataAge;
this.ifile = ifile;
this.dfile = dfile;
this.indexSummary = indexSummary;
this.bf = bloomFilter;
}
/** /**
* Open a RowIndexedReader which already has its state initialized (by SSTableWriter). * Open a RowIndexedReader which already has its state initialized (by SSTableWriter).
*/ */
static SSTableReader internalOpen(Descriptor desc, IPartitioner partitioner, SegmentedFile ifile, SegmentedFile dfile, IndexSummary isummary, BloomFilter bf, long maxDataAge) throws IOException static SSTableReader internalOpen(Descriptor desc, IPartitioner partitioner, SegmentedFile ifile, SegmentedFile dfile, IndexSummary isummary, BloomFilter bf, long maxDataAge, EstimatedHistogram rowsize,
EstimatedHistogram columncount) throws IOException
{ {
assert desc != null && partitioner != null && ifile != null && dfile != null && isummary != null && bf != null; assert desc != null && partitioner != null && ifile != null && dfile != null && isummary != null && bf != null;
return new SSTableReader(desc, partitioner, ifile, dfile, isummary, bf, maxDataAge); return new SSTableReader(desc, partitioner, ifile, dfile, isummary, bf, maxDataAge, rowsize, columncount);
} }
SSTableReader(Descriptor desc, SSTableReader(Descriptor desc,
@ -210,7 +249,9 @@ public class SSTableReader extends SSTable implements Comparable<SSTableReader>
SegmentedFile dfile, SegmentedFile dfile,
IndexSummary indexSummary, IndexSummary indexSummary,
BloomFilter bloomFilter, BloomFilter bloomFilter,
long maxDataAge) long maxDataAge,
EstimatedHistogram rowsize,
EstimatedHistogram columncount)
throws IOException throws IOException
{ {
super(desc, partitioner); super(desc, partitioner);
@ -221,6 +262,8 @@ public class SSTableReader extends SSTable implements Comparable<SSTableReader>
this.dfile = dfile; this.dfile = dfile;
this.indexSummary = indexSummary; this.indexSummary = indexSummary;
this.bf = bloomFilter; this.bf = bloomFilter;
estimatedRowSize = rowsize;
estimatedColumnCount = columncount;
} }
public void setTrackedBy(SSTableTracker tracker) public void setTrackedBy(SSTableTracker tracker)

View File

@ -26,7 +26,9 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamily; import org.apache.cassandra.db.ColumnFamily;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.StatisticsTable;
import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.io.AbstractCompactedRow; import org.apache.cassandra.io.AbstractCompactedRow;
import org.apache.cassandra.io.util.BufferedRandomAccessFile; import org.apache.cassandra.io.util.BufferedRandomAccessFile;
@ -34,6 +36,7 @@ import org.apache.cassandra.io.util.SegmentedFile;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.BloomFilter; import org.apache.cassandra.utils.BloomFilter;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.WrappedRunnable;
public class SSTableWriter extends SSTable public class SSTableWriter extends SSTable
{ {
@ -83,6 +86,8 @@ public class SSTableWriter extends SSTable
long currentPosition = beforeAppend(row.key); long currentPosition = beforeAppend(row.key);
FBUtilities.writeShortByteArray(row.key.key, dataFile); FBUtilities.writeShortByteArray(row.key.key, dataFile);
row.write(dataFile); row.write(dataFile);
estimatedRowSize.add(dataFile.getFilePointer() - currentPosition);
estimatedColumnCount.add(row.columnCount());
afterAppend(row.key, currentPosition); afterAppend(row.key, currentPosition);
} }
@ -94,7 +99,7 @@ public class SSTableWriter extends SSTable
long sizePosition = dataFile.getFilePointer(); long sizePosition = dataFile.getFilePointer();
dataFile.writeLong(-1); dataFile.writeLong(-1);
// write out row data // write out row data
ColumnFamily.serializer().serializeWithIndexes(cf, dataFile); int columnCount = ColumnFamily.serializer().serializeWithIndexes(cf, dataFile);
// seek back and write the row size (not including the size Long itself) // seek back and write the row size (not including the size Long itself)
long endPosition = dataFile.getFilePointer(); long endPosition = dataFile.getFilePointer();
dataFile.seek(sizePosition); dataFile.seek(sizePosition);
@ -102,6 +107,8 @@ public class SSTableWriter extends SSTable
// finally, reset for next row // finally, reset for next row
dataFile.seek(endPosition); dataFile.seek(endPosition);
afterAppend(decoratedKey, startPosition); afterAppend(decoratedKey, startPosition);
estimatedRowSize.add(endPosition - startPosition);
estimatedColumnCount.add(columnCount);
} }
public void append(DecoratedKey decoratedKey, byte[] value) throws IOException public void append(DecoratedKey decoratedKey, byte[] value) throws IOException
@ -128,12 +135,21 @@ public class SSTableWriter extends SSTable
dataFile.close(); // calls force dataFile.close(); // calls force
// remove the 'tmp' marker from all components // remove the 'tmp' marker from all components
Descriptor newdesc = rename(desc); final Descriptor newdesc = rename(desc);
Runnable runnable = new WrappedRunnable()
{
protected void runMayThrow() throws IOException
{
StatisticsTable.persistSSTableStatistics(newdesc, estimatedRowSize, estimatedColumnCount);
}
};
ColumnFamilyStore.submitPostFlush(runnable);
// finalize in-memory state for the reader // finalize in-memory state for the reader
SegmentedFile ifile = iwriter.builder.complete(newdesc.filenameFor(SSTable.COMPONENT_INDEX)); SegmentedFile ifile = iwriter.builder.complete(newdesc.filenameFor(SSTable.COMPONENT_INDEX));
SegmentedFile dfile = dbuilder.complete(newdesc.filenameFor(SSTable.COMPONENT_DATA)); SegmentedFile dfile = dbuilder.complete(newdesc.filenameFor(SSTable.COMPONENT_DATA));
SSTableReader sstable = SSTableReader.internalOpen(newdesc, partitioner, ifile, dfile, iwriter.summary, iwriter.bf, maxDataAge); SSTableReader sstable = SSTableReader.internalOpen(newdesc, partitioner, ifile, dfile, iwriter.summary, iwriter.bf, maxDataAge, estimatedRowSize, estimatedColumnCount);
iwriter = null; iwriter = null;
dbuilder = null; dbuilder = null;
return sstable; return sstable;

View File

@ -25,27 +25,58 @@ public class EstimatedHistogram
{ {
/** /**
* This series starts at 1 and grows by 1.2 each time (rounding down and removing duplicates). It goes from 1 * The series of values to which the counts in `buckets` correspond:
* to around 30M, which will give us timing resolution from microseconds to 30 seconds, with less precision * 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15, 18, 22, etc.
* as the numbers get larger. * Thus, a `buckets` of [0, 0, 1, 10] would mean we had seen one value of 3 and 10 values of 4.
*
* The series starts at 1 and grows by 1.2 each time (rounding and removing duplicates). It goes from 1
* to around 36M by default (creating 90+1 buckets), which will give us timing resolution from microseconds to
* 36 seconds, with less precision as the numbers get larger.
*/ */
private static final long[] bucketOffsets = { private long[] bucketOffsets;
1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15, 18, 22, 26, 31, 38, 46, 55, 66, 79, 95, 114, 137, 164, 197, 237, 284, 341, 410, 492, 590, private int numBuckets;
708, 850, 1020, 1224, 1469, 1763, 2116, 2539, 3047, 3657, 4388, 5266, 6319, 7583, 9100, 10920, 13104, 15725, 18870, 22644,
27173, 32608, 39130, 46956, 56347, 67617, 81140, 97368, 116842, 140210, 168252, 201903, 242283, 290740, 348888, 418666,
502400, 602880, 723456, 868147, 1041776, 1250132, 1500158, 1800190, 2160228, 2592274, 3110728, 3732874, 4479449, 5375339,
6450407, 7740489, 9288586, 11146304, 13375565, 16050678, 19260813, 23112976, 27735572, 33282686
};
private static final int numBuckets = bucketOffsets.length + 1;
final AtomicLongArray buckets; final AtomicLongArray buckets;
public EstimatedHistogram() public EstimatedHistogram()
{ {
makeOffsets(90);
buckets = new AtomicLongArray(numBuckets); buckets = new AtomicLongArray(numBuckets);
} }
public EstimatedHistogram(int bucketCount)
{
makeOffsets(bucketCount);
buckets = new AtomicLongArray(numBuckets);
}
public EstimatedHistogram(long[] bucketData)
{
makeOffsets(bucketData.length - 1);
buckets = new AtomicLongArray(bucketData);
}
private void makeOffsets(int size)
{
bucketOffsets = new long[size];
long last = 1;
bucketOffsets[0] = last;
for(int i = 1; i < size; i++)
{
long next = Math.round(last * 1.2);
if (next == last)
next++;
bucketOffsets[i] = next;
last = next;
}
numBuckets = bucketOffsets.length + 1;
}
public long[] getBucketOffsets()
{
return bucketOffsets;
}
public void add(long n) public void add(long n)
{ {
int index = Arrays.binarySearch(bucketOffsets, n); int index = Arrays.binarySearch(bucketOffsets, n);
@ -74,4 +105,40 @@ public class EstimatedHistogram
return rv; return rv;
} }
public long min()
{
for (int i = 0; i < numBuckets; i++)
{
if (buckets.get(i) > 0)
return bucketOffsets[i == 0 ? 0 : i - 1];
}
return 0;
}
public long max()
{
for (int i = numBuckets - 1; i >= 0; i--)
{
if (buckets.get(i) > 0)
return bucketOffsets[i == 0 ? 0 : i - 1];
}
return 0;
}
public long median()
{
long max = 0;
long median = 0;
for (int i = 0; i < numBuckets; i++)
{
if (max < 1 || buckets.get(i) > max)
{
max = buckets.get(i);
if (max > 0)
median = bucketOffsets[i == 0 ? 0 : i - 1];
}
}
return median;
}
} }

View File

@ -110,4 +110,22 @@ public class SSTableReaderTest extends CleanupHelper
assert sstable.getPosition(dk, SSTableReader.Operator.EQ) == -1; assert sstable.getPosition(dk, SSTableReader.Operator.EQ) == -1;
} }
} }
@Test
public void testPersistentStatistics() throws IOException, ExecutionException, InterruptedException
{
Table table = Table.open("Keyspace1");
ColumnFamilyStore store = table.getColumnFamilyStore("Standard1");
for (int j = 0; j < 100; j += 2)
{
byte[] key = String.valueOf(j).getBytes();
RowMutation rm = new RowMutation("Keyspace1", key);
rm.add(new QueryPath("Standard1", null, "0".getBytes()), new byte[0], new TimestampClock(j));
rm.apply();
}
store.forceBlockingFlush();
assert store.getMaxRowCompactedSize() != 0;
}
} }

View File

@ -31,20 +31,23 @@ public class EstimatedHistogramTest
EstimatedHistogram histogram = new EstimatedHistogram(); EstimatedHistogram histogram = new EstimatedHistogram();
histogram.add(0L); histogram.add(0L);
assertEquals(1, histogram.get(true)[0]); assertEquals(1, histogram.get(false)[0]);
histogram.add(33282687); histogram.add(33282687);
assertEquals(1, histogram.get(true)[histogram.buckets.length()-1]); assertEquals(1, histogram.get(false)[histogram.buckets.length()-1]);
histogram.add(1); histogram.add(1);
assertEquals(1, histogram.get(true)[1]); assertEquals(1, histogram.get(false)[1]);
histogram.add(9); histogram.add(9);
assertEquals(1, histogram.get(true)[8]); assertEquals(1, histogram.get(false)[8]);
histogram.add(23); histogram.add(20);
histogram.add(24); histogram.add(21);
histogram.add(25); histogram.add(22);
assertEquals(3, histogram.get(true)[13]); assertEquals(3, histogram.get(false)[13]);
assertEquals(1, histogram.min());
assertEquals(25109160, histogram.max());
assertEquals(20, histogram.median());
} }
} }