/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.store.statistics;
import com.foundationdb.ais.model.Index;
import com.foundationdb.ais.model.IndexColumn;
import com.foundationdb.ais.model.Table;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.rowtype.Schema;
import com.foundationdb.qp.util.SchemaCache;
import com.foundationdb.server.service.config.ConfigurationService;
import com.foundationdb.server.service.session.Session;
import com.foundationdb.server.store.FDBScanTransactionOptions;
import com.foundationdb.server.store.FDBStore;
import com.foundationdb.server.store.FDBStoreData;
import com.foundationdb.server.store.FDBStoreDataHelper;
import com.persistit.Key;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.foundationdb.server.store.statistics.IndexStatisticsService.INDEX_STATISTICS_TABLE_NAME;
import static com.foundationdb.server.store.statistics.IndexStatisticsVisitor.VisitorCreator;
public class FDBStoreIndexStatistics extends AbstractStoreIndexStatistics<FDBStore> implements VisitorCreator<Key,byte[]> {
public static final String SAMPLER_COUNT_LIMIT_PROPERTY = "fdbsql.index_statistics.sampler_count_limit";
private static final Logger logger = LoggerFactory.getLogger(FDBStoreIndexStatistics.class);
private final IndexStatisticsService indexStatisticsService;
private final long samplerCountLimit;
public FDBStoreIndexStatistics(FDBStore store, IndexStatisticsService indexStatisticsService, ConfigurationService configurationService) {
super(store);
this.indexStatisticsService = indexStatisticsService;
this.samplerCountLimit = Long.parseLong(configurationService.getProperty(SAMPLER_COUNT_LIMIT_PROPERTY));
}
//
// AbstractStoreIndexStatistics
//
@Override
public IndexStatistics loadIndexStatistics(Session session, Index index) {
Table indexStatisticsTable = getStore().getAIS(session).getTable(INDEX_STATISTICS_TABLE_NAME);
Table indexTable = index.leafMostTable();
Schema schema = SchemaCache.globalSchema(getStore().getAIS(session));
FDBStoreData storeData = getStore().createStoreData(session, indexStatisticsTable.getGroup());
storeData.persistitKey.append(indexStatisticsTable.getOrdinal())
.append((long) indexTable.getTableId())
.append((long) index.getIndexId());
IndexStatistics result = null;
getStore().groupKeyAndDescendantsIterator(session, storeData, FDBScanTransactionOptions.SNAPSHOT);
while(storeData.next()) {
FDBStoreDataHelper.unpackKey(storeData);
if(result == null) {
result = decodeHeader(session, storeData, index, schema);
} else {
decodeEntry(session, storeData, result, schema);
}
}
if ((result != null) && logger.isDebugEnabled()) {
logger.debug("Loaded: " + result.toString(index));
}
return result;
}
@Override
public void removeStatistics(Session session, Index index) {
Table table = getStore().getAIS(session).getTable(INDEX_STATISTICS_TABLE_NAME);
Table indexTable = index.leafMostTable();
FDBStoreData storeData = getStore().createStoreData(session, table.getGroup());
storeData.persistitKey.clear();
storeData.persistitKey.append(table.getOrdinal())
.append((long) indexTable.getTableId())
.append((long) index.getIndexId());
getStore().groupKeyAndDescendantsIterator(session, storeData, FDBScanTransactionOptions.NORMAL);
while(storeData.next()) {
FDBStoreDataHelper.unpackKey(storeData);
Row row = getStore().expandRow(session, storeData, SchemaCache.globalSchema(index.getAIS()));
getStore().deleteRow(session, row, false);
}
}
@Override
public IndexStatistics computeIndexStatistics(Session session, Index index, long scanTimeLimit, long sleepTime) {
FDBScanTransactionOptions transactionOptions;
if (scanTimeLimit > 0) {
transactionOptions = new FDBScanTransactionOptions(true, -1,
scanTimeLimit, sleepTime);
}
else {
transactionOptions = FDBScanTransactionOptions.SNAPSHOT;
}
long indexRowCount = estimateIndexRowCount(session, index);
long expectedSampleCount = indexRowCount;
int sampleRate = 1, skippedSamples = 0;
int nSingle = index.getKeyColumns().size() - 1;
if (nSingle > 0) {
// Multi-column index might need sampling. In the worst case, the visitor
// will need to retain one copy of the key for each non-leading column for
// each sampled row. Keep that below samplerCountLimit by sampling every few
// rows. We could still send everything for the leading column, except that
// the sample count applies to the whole, not per histograms.
sampleRate = (int)((indexRowCount * nSingle + samplerCountLimit - 1) / samplerCountLimit); // Round up.
if (sampleRate > 1) {
expectedSampleCount = indexRowCount / sampleRate;
logger.debug("Sampling rate for {} is {}", index, sampleRate);
}
}
IndexStatisticsVisitor<Key,byte[]> visitor = new IndexStatisticsVisitor<>(session, index, indexRowCount, expectedSampleCount, this);
int bucketCount = indexStatisticsService.bucketCount();
visitor.init(bucketCount);
FDBStoreData storeData = getStore().createStoreData(session, index);
// Whole index, forward.
getStore().indexIterator(session, storeData, false, false, true, false,
transactionOptions);
while(storeData.next()) {
if (++skippedSamples < sampleRate)
continue; // This value not sampled.
skippedSamples = 0;
FDBStoreDataHelper.unpackKey(storeData);
// TODO: Does anything look at rawValue?
visitor.visit(storeData.persistitKey, storeData.rawValue);
}
visitor.finish(bucketCount);
IndexStatistics indexStatistics = visitor.getIndexStatistics();
if (logger.isDebugEnabled()) {
logger.debug("Analyzed: " + indexStatistics.toString(index));
}
return indexStatistics;
}
//
// VisitorCreator
//
@Override
public IndexStatisticsGenerator<Key,byte[]> multiColumnVisitor(Index index) {
return new FDBMultiColumnIndexStatisticsVisitor(index, getStore());
}
@Override
public IndexStatisticsGenerator<Key,byte[]> singleColumnVisitor(Session session, IndexColumn indexColumn) {
return new FDBSingleColumnIndexStatisticsVisitor(getStore(), indexColumn);
}
//
// Internal
//
protected IndexStatistics decodeHeader(Session session,
FDBStoreData storeData,
Index index,
Schema schema) {
return decodeIndexStatisticsRow(getStore().expandRow(session, storeData, schema), index);
}
protected void decodeEntry(Session session,
FDBStoreData storeData,
IndexStatistics indexStatistics,
Schema schema) {
decodeIndexStatisticsEntryRow(getStore().expandRow(session, storeData, schema), indexStatistics);
}
}