/** * 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.test.costmodel; import com.foundationdb.ais.model.Index; import com.foundationdb.qp.expression.IndexBound; import com.foundationdb.qp.expression.IndexKeyRange; import com.foundationdb.qp.expression.RowBasedUnboundExpressions; import com.foundationdb.qp.operator.Cursor; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.operator.TimeOperator; import com.foundationdb.qp.row.BindableRow; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.qp.rowtype.Schema; import com.foundationdb.qp.rowtype.TableRowType; import com.foundationdb.qp.util.SchemaCache; import com.foundationdb.server.api.dml.SetColumnSelector; import com.foundationdb.server.error.InvalidOperationException; import com.foundationdb.server.test.ExpressionGenerators; import org.junit.Test; import java.util.Arrays; import java.util.Collections; import static com.foundationdb.qp.operator.API.*; public class Select_BloomFilterCT extends CostModelBase { @Test public void run() throws Exception { createSchema(); populateDB(); // Load-only run(WARMUP_RUNS, true, 0, false); run(MEASURED_RUNS, true, 0, true); // Load and scan. Vary the starting value to control the amount of overlap with the filter rows. run(WARMUP_RUNS, false, 0, false); for (int start = 0; start <= FILTER_ROWS; start += FILTER_ROWS / 10) { run(MEASURED_RUNS, false, start, true); } } private void createSchema() throws InvalidOperationException { // Schema is similar to that in Select_BloomFilterIT String schemaName = schemaName(); String dTableName = newTableName(); // Driving table String fTableName = newTableName(); // Filtering table d = createTable( schemaName, dTableName, "x int"); f = createTable( schemaName, fTableName, "x int"); Index dx = createIndex(schemaName, dTableName, "idx_dx", "x"); Index fx = createIndex(schemaName, fTableName, "idx_fx", "x"); schema = SchemaCache.globalSchema(ais()); dRowType = schema.tableRowType(table(d)); fRowType = schema.tableRowType(table(f)); dIndexRowType = dRowType.indexRowType(dx); fIndexRowType = fRowType.indexRowType(fx); adapter = newStoreAdapter(); queryContext = queryContext(adapter); queryBindings = queryContext.createBindings(); } protected void populateDB() { for (int x = 0; x < FILTER_ROWS; x++) { writeRow(f, x, x); // x, hidden_pk } for (int x = 0; x < DRIVING_ROWS; x++) { writeRow(d, x, x); // x, hidden_pk } } private void run(int runs, boolean loadOnly, int startScan, boolean report) { Operator plan = loadOnly ? planLoadOnly() : planLoadAndSelect(startScan); long start = System.nanoTime(); for (int r = 0; r < runs; r++) { Cursor cursor = cursor(plan, queryContext, queryBindings); cursor.openTopLevel(); Row row; while ((row = cursor.next()) != null) { // System.out.println(row); } } long stop = System.nanoTime(); long planNsec = stop - start; if (loadOnly) { planNsec -= timeFilterInput.elapsedNsec(); if (report) { double averageUsecPerRow = planNsec / (1000.0 * runs * FILTER_ROWS); System.out.println(String.format("load only: %s usec/row", averageUsecPerRow)); } } else { planNsec -= (timeFilterInput.elapsedNsec() + timeScanInput.elapsedNsec()); if (report) { double averageUsecPerRow = planNsec / (1000.0 * runs * (DRIVING_ROWS - startScan)); double selected = (double) (FILTER_ROWS - startScan) / DRIVING_ROWS; System.out.println(String.format("scan %s: %s usec/row", selected, averageUsecPerRow)); } } } public Operator planLoadOnly() { // filterInput loads the filter with F rows containing the given testId. Operator filterInput = project_DefaultTest( groupScan_Default(group(f)), fRowType, Arrays.asList(ExpressionGenerators.field(fRowType, 0))); timeFilterInput = new TimeOperator(filterInput); // For the index scan retrieving rows from the F(x) index given a D index row IndexBound fxBound = new IndexBound( new RowBasedUnboundExpressions( filterInput.rowType(), Arrays.asList(ExpressionGenerators.boundField(dIndexRowType, 0, 0)), true), new SetColumnSelector(0)); IndexKeyRange fKeyRange = IndexKeyRange.bounded(fIndexRowType, fxBound, true, fxBound, true); // Use a bloom filter loaded by filterInput. Then for each input row, check the filter (projecting // D rows on (x)), and, for positives, check F using an index scan keyed by D.x. Operator plan = using_BloomFilter( // filterInput timeFilterInput, // filterRowType filterInput.rowType(), // estimatedRowCount FILTER_ROWS, // filterBindingPosition 0, // streamInput select_BloomFilterTest( // input valuesScan_Default(Collections.<BindableRow>emptyList(), dIndexRowType), // onPositive indexScan_Default( fIndexRowType, fKeyRange, new Ordering()), // filterFields Arrays.asList(ExpressionGenerators.field(dIndexRowType, 0)), // filterBindingPosition, pipeline, depth 0, false, 1)); return plan; } public Operator planLoadAndSelect(int start) { // filterInput loads the filter with F rows containing the given testId. Operator filterInput = project_DefaultTest( groupScan_Default(group(f)), fRowType, Arrays.asList(ExpressionGenerators.field(fRowType, 0))); timeFilterInput = new TimeOperator(filterInput); // For the index scan retriving rows from the D(x) index IndexBound dxLo = new IndexBound(row(dIndexRowType, start), new SetColumnSelector(0)); IndexBound dxHi = new IndexBound(row(dIndexRowType, Integer.MAX_VALUE), new SetColumnSelector(0)); IndexKeyRange dKeyRange = IndexKeyRange.bounded(dIndexRowType, dxLo, true, dxHi, false); // For the index scan retrieving rows from the F(x) index given a D index row IndexBound fxBound = new IndexBound( new RowBasedUnboundExpressions( filterInput.rowType(), Arrays.asList(ExpressionGenerators.boundField(dIndexRowType, 0, 0)), true), new SetColumnSelector(0)); IndexKeyRange fKeyRange = IndexKeyRange.bounded(fIndexRowType, fxBound, true, fxBound, true); // Use a bloom filter loaded by filterInput. Then for each input row, check the filter (projecting // D rows on (x)), and, for positives, check F using an index scan keyed by D.x. Operator scanInput = indexScan_Default(dIndexRowType, dKeyRange, new Ordering()); timeScanInput = new TimeOperator(scanInput); Operator plan = using_BloomFilter( // filterInput timeFilterInput, // filterRowType filterInput.rowType(), // estimatedRowCount FILTER_ROWS, // filterBindingPosition 0, // streamInput select_BloomFilterTest( // input timeScanInput, // onPositive indexScan_Default( fIndexRowType, fKeyRange, new Ordering()), // filterFields Arrays.asList(ExpressionGenerators.field(dIndexRowType, 0)), // filterBindingPosition, pipeline, depth 0, false, 1)); return plan; } private static final int WARMUP_RUNS = 2; private static final int MEASURED_RUNS = 5; private static final long FILTER_ROWS = 100000; private static final long DRIVING_ROWS = 200000; private int d; private int f; private TableRowType dRowType; private TableRowType fRowType; private IndexRowType dIndexRowType; private IndexRowType fIndexRowType; private TimeOperator timeFilterInput; private TimeOperator timeScanInput; }