/** * 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.qp.row.Row; import com.foundationdb.qp.row.ValuesHolderRow; import com.foundationdb.qp.rowtype.RowType; import com.foundationdb.qp.util.SchemaCache; import com.foundationdb.server.service.session.Session; import com.foundationdb.server.store.Store; import java.util.ArrayList; import static com.foundationdb.server.store.statistics.IndexStatisticsService.INDEX_STATISTICS_ENTRY_TABLE_NAME; import static com.foundationdb.server.store.statistics.IndexStatisticsService.INDEX_STATISTICS_TABLE_NAME; /** Manage index statistics for a Store * * About index_statistics_entry, single-column and multi-column histograms: * - Multi-column histograms were invented first. index_statistics_entry.column_count indicates * the number of columns represented by the entry, e.g. 1 for (a) and 2 for (a, b). * - Single-column histograms were added later. The single-column histogram for the leading column * of an index is identical to the multi-column histogram with column_count 1. column_count -2 is * for the second column. * So for an index (a, b, c), there are the following column_counts: * 1: (a) * 2: (a, b) * 3: (a, b, c) * -2: (b) * -3: (c) */ public abstract class AbstractStoreIndexStatistics<S extends Store> { private final S store; public AbstractStoreIndexStatistics(S store) { this.store = store; } protected S getStore() { return store; } public abstract IndexStatistics loadIndexStatistics(Session session, Index index); public abstract void removeStatistics(Session session, Index index); /** Sample index values and build statistics histograms. */ public abstract IndexStatistics computeIndexStatistics(Session session, Index index, long scanTimeLimit, long sleepTime); protected long estimateIndexRowCount(Session session, Index index) { switch(index.getIndexType()) { case TABLE: case GROUP: return index.leafMostTable().tableStatus().getApproximateRowCount(session); case FULL_TEXT: throw new UnsupportedOperationException("FullTextIndex row count"); default: throw new IllegalStateException("Unknown index type: " + index); } } /* Storage formats. * Keep in sync with IndexStatisticsServiceImpl */ private static final int ANALYSIS_TIMESTAMP_FIELD_INDEX = 2; private static final int ROW_COUNT_FIELD_INDEX = 3; private static final int SAMPLED_COUNT_FIELD_INDEX = 4; // Parent keys the same. private static final int COLUMN_COUNT_FIELD_INDEX = 2; private static final int ITEM_NUMBER_FIELD_INDEX = 3; private static final int KEY_STRING_FIELD_INDEX = 4; private static final int KEY_BYTES_FIELD_INDEX = 5; private static final int EQ_COUNT_FIELD_INDEX = 6; private static final int LT_COUNT_FIELD_INDEX = 7; private static final int DISTINCT_COUNT_FIELD_INDEX = 8; protected final IndexStatistics decodeIndexStatisticsRow(Row row, Index index) { long analysisTimeStamp = (long)row.value(ANALYSIS_TIMESTAMP_FIELD_INDEX).getInt32(); long rowCount = row.value(ROW_COUNT_FIELD_INDEX).getInt64(); long sampledCount = row.value(SAMPLED_COUNT_FIELD_INDEX).getInt64(); return new IndexStatistics(index, analysisTimeStamp * 1000, rowCount, sampledCount); } protected final void decodeIndexStatisticsEntryRow (Row row, IndexStatistics indexStatistics) { int columnCount = row.value(COLUMN_COUNT_FIELD_INDEX).getInt32(); //int itemNumber = row.value(ITEM_NUMBER_FIELD_INDEX).getInt32(); String keyString = row.value(KEY_STRING_FIELD_INDEX).getString(); byte[] keyBytes = row.value(KEY_BYTES_FIELD_INDEX).getBytes(); long eqCount = row.value(EQ_COUNT_FIELD_INDEX).getInt64(); long ltCount = row.value(LT_COUNT_FIELD_INDEX).getInt64(); long distinctCount = row.value(DISTINCT_COUNT_FIELD_INDEX).getInt64(); int firstColumn = 0; // Correct for multi-column if (columnCount < 0) { firstColumn = -columnCount - 1; columnCount = 1; } Histogram histogram = indexStatistics.getHistogram(firstColumn, columnCount); if (histogram == null) { histogram = new Histogram(firstColumn, columnCount, new ArrayList<HistogramEntry>()); indexStatistics.addHistogram(histogram); } histogram.getEntries().add(new HistogramEntry(keyString, keyBytes, eqCount, ltCount, distinctCount)); } /** Store statistics into database. */ public final void storeIndexStatistics(Session session, Index index, IndexStatistics indexStatistics) { int tableId = index.leafMostTable().getTableId(); RowType indexStatisticsRowType = SchemaCache.globalSchema(index.getAIS()).tableRowType(store.getAIS(session).getTable(INDEX_STATISTICS_TABLE_NAME)); RowType indexStatisticsEntryRowType = SchemaCache.globalSchema(index.getAIS()).tableRowType(store.getAIS(session).getTable(INDEX_STATISTICS_ENTRY_TABLE_NAME)); // Remove existing statistics for the index removeStatistics(session, index); // Parent header row. Row row = new ValuesHolderRow (indexStatisticsRowType, tableId, index.getIndexId(), indexStatistics.getAnalysisTimestamp() / 1000, indexStatistics.getRowCount(), indexStatistics.getSampledCount()); store.writeRow(session, row, null, null); // Multi-column for(int prefixColumns = 1; prefixColumns <= index.getKeyColumns().size(); prefixColumns++) { Histogram histogram = indexStatistics.getHistogram(0, prefixColumns); if(histogram != null) { storeIndexStatisticsEntry(session, index, tableId, histogram.getColumnCount(), indexStatisticsEntryRowType, histogram); } } // Single-column for(int columnPosition = 1; columnPosition < index.getKeyColumns().size(); columnPosition++) { Histogram histogram = indexStatistics.getHistogram(columnPosition, 1); if(histogram != null) { storeIndexStatisticsEntry(session, index, tableId, -histogram.getFirstColumn() - 1, indexStatisticsEntryRowType, histogram); } } } private void storeIndexStatisticsEntry(Session session, Index index, int tableId, int columnCount, RowType indexStatisticsEntryRowType, Histogram histogram) { if (histogram != null) { int itemNumber = 0; for (HistogramEntry entry : histogram.getEntries()) { Row row = new ValuesHolderRow (indexStatisticsEntryRowType, tableId, index.getIndexId(), columnCount, ++itemNumber, entry.getKeyString(), entry.getKeyBytes(), entry.getEqualCount(), entry.getLessCount(), entry.getDistinctCount()); store.writeRow(session, row, null, null); } } } }