Created
May 17, 2020 03:26
-
-
Save frozenspider/beb80b651b62b7f27467a43254c43be0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) YugaByte, Inc. | |
// | |
// 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. | |
// | |
package org.yb.pgsql; | |
import static org.yb.AssertionWrappers.assertEquals; | |
import static org.yb.AssertionWrappers.assertTrue; | |
import java.sql.PreparedStatement; | |
import java.sql.Statement; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.List; | |
import org.junit.Rule; | |
import org.junit.Test; | |
import org.junit.rules.TestName; | |
import org.junit.runner.RunWith; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.yb.util.YBTestRunnerNonTsanOnly; | |
@RunWith(value = YBTestRunnerNonTsanOnly.class) | |
public class TestPgPerformance extends BasePgSQLTest { | |
private static final Logger LOG = LoggerFactory.getLogger(TestPgPerformance.class); | |
@Rule | |
public TestName testName = new TestName(); | |
@Test | |
public void in_primaryKeySimple() throws Exception { | |
// Table "public.in_pk_pushdown_simple" | |
// Column | Type | Collation | Nullable | Default | |
// --------+---------+-----------+----------+--------- | |
// h | integer | | not null | | |
// v | integer | | | | |
// Indexes: | |
// "in_pk_pushdown_simple_pkey" PRIMARY KEY, lsm (h HASH) | |
String tableName = "in_pk_pushdown_simple"; | |
String tableSpec = "h int PRIMARY KEY, v int"; | |
int numRowsToInsert = 10000; | |
PerfTester tester = new PerfTester(tableName, tableSpec) { | |
@Override | |
void fillTable(Statement stmt) throws Exception { | |
StringBuilder sb = new StringBuilder("INSERT INTO " + tableName + " (h, v) VALUES "); | |
for (int i = 0; i < numRowsToInsert; ++i) { | |
sb.append(String.format("(%d, %d),", i, i)); | |
} | |
stmt.executeUpdate(sb.substring(0, sb.length() - 1)); // Drop trailing comma | |
} | |
@Override | |
List<Row> getExpectedRows() { | |
return Arrays.asList( | |
new Row(10, 10), | |
new Row(20, 20), | |
new Row(30, 30)); | |
} | |
@Override | |
String getOptimizedSelectQuery() { | |
return String.format( | |
"SELECT * FROM %s WHERE h IN (%d, %d, %d)", | |
tableName, 10, 20, 30); | |
} | |
@Override | |
String getOptimizedPreparedSelectQueryString() { | |
return String.format( | |
"SELECT * FROM %s WHERE h IN (?, ?, ?)", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareSelectOrDeleteQuery(String queryString) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
pquery.setInt(1, 10); | |
pquery.setInt(2, 20); | |
pquery.setInt(3, 30); | |
return pquery; | |
} | |
@Override | |
String getOptimizedUpdateQuery(int valueToSet) { | |
return String.format( | |
"UPDATE %s SET v = %d WHERE h IN (%d, %d, %d)", | |
tableName, valueToSet, 10, 20, 30); | |
} | |
@Override | |
String getOptimizedPreparedUpdateQueryString() { | |
return String.format( | |
"UPDATE %s SET v = ? WHERE h IN (?, ?, ?)", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareUpdateQuery(String queryString, int valueToSet) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
pquery.setInt(1, valueToSet); | |
pquery.setInt(2, 10); | |
pquery.setInt(3, 20); | |
pquery.setInt(4, 30); | |
return pquery; | |
} | |
@Override | |
String getResetQuery() { | |
return String.format("UPDATE %s SET v = h", tableName); | |
} | |
@Override | |
String getOptimizedDeleteQuery() { | |
return String.format( | |
"DELETE FROM %s WHERE h IN (%d, %d, %d)", | |
tableName, 10, 20, 30); | |
} | |
@Override | |
String getOptimizedPreparedDeleteQueryString() { | |
return String.format( | |
"DELETE FROM %s WHERE h IN (?, ?, ?)", | |
tableName); | |
} | |
}; | |
tester.test(); | |
} | |
@Test | |
public void in_primaryKeyTwoHashOneRange() throws Exception { | |
// Table "public.in_pk_pushdown_hash1_hash2_range" | |
// Column | Type | Collation | Nullable | Default | |
// --------+---------+-----------+----------+--------- | |
// h1 | integer | | not null | | |
// h2 | integer | | not null | | |
// r | integer | | not null | | |
// v | integer | | | | |
// Indexes: | |
// "in_pk_pushdown_hash1_hash2_range_pkey" PRIMARY KEY, lsm ((h1, h2) HASH, r) | |
String tableName = "in_pk_pushdown_hash1_hash2_range"; | |
String tableSpec = "h1 int, h2 int, r int, v int, PRIMARY KEY ((h1, h2) HASH, r ASC)"; | |
int maxIntToInsert = 20; | |
PerfTester tester = new PerfTester(tableName, tableSpec) { | |
@Override | |
void fillTable(Statement stmt) throws Exception { | |
// Set the value to be the concatenation of row keys | |
StringBuilder sb = new StringBuilder( | |
"INSERT INTO " + tableName + " (h1, h2, r, v) VALUES "); | |
for (int h1 = 0; h1 < maxIntToInsert; ++h1) { | |
for (int h2 = 0; h2 < maxIntToInsert; ++h2) { | |
for (int r = 0; r < maxIntToInsert; ++r) { | |
sb.append(String.format("(%d, %d, %d, %d),", | |
h1, h2, r, | |
(h1 * 10000 + h2 * 100 + r))); | |
} | |
} | |
} | |
stmt.executeUpdate(sb.substring(0, sb.length() - 1)); // Drop trailing comma | |
} | |
@Override | |
List<Row> getExpectedRows() { | |
// h1 in [1, 2], h2 in [3, 4], r in [5, 6] | |
// v is (kind of) the concatenation of [h1, h2, r] | |
List<Row> expectedResult = new ArrayList<>(); | |
for (int h1 : Arrays.asList(1, 2)) { | |
for (int h2 : Arrays.asList(3, 4)) { | |
for (int r : Arrays.asList(5, 6)) { | |
expectedResult.add(new Row(h1, h2, r, (h1 * 10000 + h2 * 100 + r))); | |
} | |
} | |
} | |
return expectedResult; | |
} | |
@Override | |
String getOptimizedSelectQuery() { | |
return String.format( | |
"SELECT * FROM %s WHERE" | |
+ " h1 IN (%d, %d) AND" | |
+ " h2 IN (%d, %d) AND" | |
+ " r IN (%d, %d)", | |
tableName, 1, 2, 3, 4, 5, 6); | |
} | |
@Override | |
String getOptimizedPreparedSelectQueryString() { | |
return String.format( | |
"SELECT * FROM %s WHERE" | |
+ " h1 IN (?, ?) AND" | |
+ " h2 IN (?, ?) AND" | |
+ " r IN (?, ?)", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareSelectOrDeleteQuery(String queryString) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
for (int i = 0; i < 3; ++i) { | |
pquery.setInt(i * 2 + 1, i * 2 + 1); | |
pquery.setInt(i * 2 + 2, i * 2 + 2); | |
} | |
return pquery; | |
} | |
@Override | |
String getOptimizedUpdateQuery(int valueToSet) { | |
return String.format("UPDATE %s SET v = %d WHERE" | |
+ " h1 IN (%d, %d) AND" | |
+ " h2 IN (%d, %d) AND" | |
+ " r IN (%d, %d)", | |
tableName, valueToSet, 1, 2, 3, 4, 5, 6); | |
} | |
@Override | |
String getOptimizedPreparedUpdateQueryString() { | |
return String.format("UPDATE %s SET v = ? WHERE" | |
+ " h1 IN (?, ?) AND" | |
+ " h2 IN (?, ?) AND" | |
+ " r IN (?, ?)", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareUpdateQuery(String queryString, int valueToSet) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
pquery.setInt(1, valueToSet); | |
for (int i = 0; i < 3; ++i) { | |
pquery.setInt(i * 2 + 2, i * 2 + 1); | |
pquery.setInt(i * 2 + 3, i * 2 + 2); | |
} | |
return pquery; | |
} | |
@Override | |
String getResetQuery() { | |
return String.format("UPDATE %s SET v = h1 * 10000 + h2 * 100 + r", tableName); | |
} | |
@Override | |
String getOptimizedDeleteQuery() { | |
return String.format( | |
"DELETE FROM %s WHERE" | |
+ " h1 IN (%d, %d) AND" | |
+ " h2 IN (%d, %d) AND" | |
+ " r IN (%d, %d)", | |
tableName, 1, 2, 3, 4, 5, 6); | |
} | |
@Override | |
String getOptimizedPreparedDeleteQueryString() { | |
return String.format( | |
"DELETE FROM %s WHERE" | |
+ " h1 IN (?, ?) AND" | |
+ " h2 IN (?, ?) AND" | |
+ " r IN (?, ?)", | |
tableName); | |
} | |
}; | |
tester.test(); | |
} | |
/** | |
* Making sure we don't care about column declaration order nor PK order, thus not running into | |
* something like https://github.com/yugabyte/yugabyte-db/issues/3302. For this we're using | |
* mismatching key orders everywhere: | |
* <ol> | |
* <li>(h1, h2, h3) - in table schema | |
* <li>(h3, h1, h3) - in PK declaration | |
* <li>(h2, h3, h1) - in queries | |
* </ol> | |
*/ | |
@Test | |
public void in_primaryKeyReordered() throws Exception { | |
// Table "public.in_pk_pushdown_reordered" | |
// Column | Type | Collation | Nullable | Default | |
// --------+---------+-----------+----------+--------- | |
// h1 | integer | | not null | | |
// h2 | integer | | not null | | |
// h3 | integer | | not null | | |
// v | integer | | | | |
// Indexes: | |
// "in_pk_pushdown_reordered_pkey" PRIMARY KEY, lsm ((h3, h1, h2) HASH) | |
String tableName = "in_pk_pushdown_reordered"; | |
String tableSpec = "h1 int, h2 int, h3 int, v int, PRIMARY KEY ((h3, h1, h2) HASH)"; | |
int maxIntToInsert = 20; // 8000 rows | |
PerfTester tester = new PerfTester(tableName, tableSpec) { | |
@Override | |
void fillTable(Statement stmt) throws Exception { | |
// Set the value to be the concatenation of row keys | |
StringBuilder sb = new StringBuilder( | |
"INSERT INTO " + tableName + " (h2, h3, h1, v) VALUES "); | |
for (int h2 = 0; h2 < maxIntToInsert; ++h2) { | |
for (int h3 = 0; h3 < maxIntToInsert; ++h3) { | |
for (int h1 = 0; h1 < maxIntToInsert; ++h1) { | |
sb.append(String.format("(%d, %d, %d, %d),", | |
h2, h3, h1, | |
(h1 * 10000 + h2 * 100 + h3))); | |
} | |
} | |
} | |
stmt.executeUpdate(sb.substring(0, sb.length() - 1)); // Drop trailing comma | |
} | |
@Override | |
List<Row> getExpectedRows() { | |
// h1 in [1, 2], h2 in [3, 4], h3 in [5, 6] | |
// v is (kind of) the concatenation of [h1, h2, h3] | |
List<Row> expectedResult = new ArrayList<>(); | |
for (int h1 : Arrays.asList(1, 2)) { | |
for (int h2 : Arrays.asList(3, 4)) { | |
for (int h3 : Arrays.asList(5, 6)) { | |
expectedResult.add(new Row(h1, h2, h3, (h1 * 10000 + h2 * 100 + h3))); | |
} | |
} | |
} | |
return expectedResult; | |
} | |
@Override | |
String getOptimizedSelectQuery() { | |
return String.format( | |
"SELECT * FROM %s WHERE" | |
+ " h2 IN (%d, %d) AND" | |
+ " h3 IN (%d, %d) AND" | |
+ " h1 IN (%d, %d)", | |
tableName, 3, 4, 5, 6, 1, 2); | |
} | |
@Override | |
String getOptimizedPreparedSelectQueryString() { | |
return String.format( | |
"SELECT * FROM %s WHERE" | |
+ " h2 IN (?, ?) AND" | |
+ " h3 IN (?, ?) AND" | |
+ " h1 IN (?, ?)", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareSelectOrDeleteQuery(String queryString) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
pquery.setInt(1, 3); | |
pquery.setInt(2, 4); | |
pquery.setInt(3, 5); | |
pquery.setInt(4, 6); | |
pquery.setInt(5, 1); | |
pquery.setInt(6, 2); | |
return pquery; | |
} | |
@Override | |
String getOptimizedUpdateQuery(int valueToSet) { | |
return String.format("UPDATE %s SET v = %d WHERE" | |
+ " h2 IN (%d, %d) AND" | |
+ " h3 IN (%d, %d) AND" | |
+ " h1 IN (%d, %d)", | |
tableName, valueToSet, 3, 4, 5, 6, 1, 2); | |
} | |
@Override | |
String getOptimizedPreparedUpdateQueryString() { | |
return String.format("UPDATE %s SET v = ? WHERE" | |
+ " h2 IN (?, ?) AND" | |
+ " h3 IN (?, ?) AND" | |
+ " h1 IN (?, ?)", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareUpdateQuery(String queryString, int valueToSet) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
pquery.setInt(1, valueToSet); | |
pquery.setInt(2, 3); | |
pquery.setInt(3, 4); | |
pquery.setInt(4, 5); | |
pquery.setInt(5, 6); | |
pquery.setInt(6, 1); | |
pquery.setInt(7, 2); | |
return pquery; | |
} | |
@Override | |
String getResetQuery() { | |
return String.format("UPDATE %s SET v = h1 * 10000 + h2 * 100 + h3", tableName); | |
} | |
@Override | |
String getOptimizedDeleteQuery() { | |
return String.format( | |
"DELETE FROM %s WHERE" | |
+ " h2 IN (%d, %d) AND" | |
+ " h3 IN (%d, %d) AND" | |
+ " h1 IN (%d, %d)", | |
tableName, 3, 4, 5, 6, 1, 2); | |
} | |
@Override | |
String getOptimizedPreparedDeleteQueryString() { | |
return String.format( | |
"DELETE FROM %s WHERE" | |
+ " h2 IN (?, ?) AND" | |
+ " h3 IN (?, ?) AND" | |
+ " h1 IN (?, ?)", | |
tableName); | |
} | |
}; | |
tester.test(); | |
} | |
@Test | |
public void in_secondaryIndex() throws Exception { | |
// Table "public.in_sidx_pushdown_simple" | |
// Column | Type | Collation | Nullable | Default | |
// --------+---------+-----------+----------+--------- | |
// h | integer | | not null | | |
// i | integer | | | | |
// v | integer | | | | |
// Indexes: | |
// "in_sidx_pushdown_simple_pkey" PRIMARY KEY, lsm (h HASH) | |
// "in_sidx_pushdown_simple_i_idx" lsm (i HASH) | |
String tableName = "in_sidx_pushdown_simple"; | |
String tableSpec = "h int PRIMARY KEY, i int, v int"; | |
int numRowsToInsert = 5000; | |
PerfTester tester = new PerfTester(tableName, tableSpec) { | |
@Override | |
void createTable(Statement stmt) throws Exception { | |
super.createTable(stmt); | |
stmt.executeUpdate(String.format("CREATE INDEX %s_i_idx ON %s (i)", tableName, tableName)); | |
} | |
@Override | |
void fillTable(Statement stmt) throws Exception { | |
StringBuilder sb = new StringBuilder("INSERT INTO " + tableName + " (h, i, v) VALUES "); | |
for (int i = 0; i < numRowsToInsert; ++i) { | |
sb.append(String.format("(%d, %d, %d),", i, i, i)); | |
} | |
stmt.executeUpdate(sb.substring(0, sb.length() - 1)); // Drop trailing comma | |
} | |
@Override | |
List<Row> getExpectedRows() { | |
return Arrays.asList( | |
new Row(10, 10, 10), | |
new Row(20, 20, 20), | |
new Row(30, 30, 30)); | |
} | |
@Override | |
String getOptimizedSelectQuery() { | |
return String.format( | |
"SELECT * FROM %s WHERE i IN (%d, %d, %d)", | |
tableName, 10, 20, 30); | |
} | |
@Override | |
String getOptimizedPreparedSelectQueryString() { | |
return String.format( | |
"SELECT * FROM %s WHERE i IN (?, ?, ?)", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareSelectOrDeleteQuery(String queryString) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
pquery.setInt(1, 10); | |
pquery.setInt(2, 20); | |
pquery.setInt(3, 30); | |
return pquery; | |
} | |
@Override | |
String getOptimizedUpdateQuery(int valueToSet) { | |
return String.format( | |
"UPDATE %s SET v = %d WHERE i IN (%d, %d, %d)", | |
tableName, valueToSet, 10, 20, 30); | |
} | |
@Override | |
String getOptimizedPreparedUpdateQueryString() { | |
return String.format( | |
"UPDATE %s SET v = ? WHERE i IN (?, ?, ?)", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareUpdateQuery(String queryString, int valueToSet) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
pquery.setInt(1, valueToSet); | |
pquery.setInt(2, 10); | |
pquery.setInt(3, 20); | |
pquery.setInt(4, 30); | |
return pquery; | |
} | |
@Override | |
String getResetQuery() { | |
return String.format("UPDATE %s SET v = h", tableName); | |
} | |
@Override | |
String getOptimizedDeleteQuery() { | |
return String.format( | |
"DELETE FROM %s WHERE i IN (%d, %d, %d)", | |
tableName, 10, 20, 30); | |
} | |
@Override | |
String getOptimizedPreparedDeleteQueryString() { | |
return String.format( | |
"DELETE FROM %s WHERE i IN (?, ?, ?)", | |
tableName); | |
} | |
}; | |
tester.test(); | |
} | |
/** Mixing IN and equality: h1 IN (1, 2, 3) AND h2 = 4 */ | |
@Test | |
public void inEq_primaryKeyTwoHash() throws Exception { | |
// Table "public.in_eq_pk_pushdown_hash1_hash2" | |
// Column | Type | Collation | Nullable | Default | |
// --------+---------+-----------+----------+--------- | |
// h1 | integer | | not null | | |
// h2 | integer | | not null | | |
// v | integer | | | | |
// Indexes: | |
// "in_eq_pk_pushdown_hash1_hash2_pkey" PRIMARY KEY, lsm ((h1, h2) HASH) | |
String tableName = "in_eq_pk_pushdown_hash1_hash2"; | |
String tableSpec = "h1 int, h2 int, v int, PRIMARY KEY ((h1, h2) HASH)"; | |
int maxIntToInsert = 80; // 6400 rows | |
PerfTester tester = new PerfTester(tableName, tableSpec) { | |
@Override | |
void fillTable(Statement stmt) throws Exception { | |
// Set the value to be the concatenation of row keys | |
StringBuilder sb = new StringBuilder( | |
"INSERT INTO " + tableName + " (h1, h2, v) VALUES "); | |
for (int h1 = 0; h1 < maxIntToInsert; ++h1) { | |
for (int h2 = 0; h2 < maxIntToInsert; ++h2) { | |
sb.append(String.format("(%d, %d, %d),", | |
h1, h2, | |
(h1 * 10000 + h2))); | |
} | |
} | |
stmt.executeUpdate(sb.substring(0, sb.length() - 1)); // Drop trailing comma | |
} | |
@Override | |
List<Row> getExpectedRows() { | |
List<Row> expectedResult = new ArrayList<>(); | |
int h2 = 4; | |
for (int h1 : Arrays.asList(1, 2, 3)) { | |
expectedResult.add(new Row(h1, h2, (h1 * 10000 + h2))); | |
} | |
return expectedResult; | |
} | |
@Override | |
String getOptimizedSelectQuery() { | |
return String.format( | |
"SELECT * FROM %s WHERE" | |
+ " h1 IN (%d, %d, %d) AND" | |
+ " h2 = %d", | |
tableName, 1, 2, 3, 4); | |
} | |
@Override | |
String getOptimizedPreparedSelectQueryString() { | |
return String.format( | |
"SELECT * FROM %s WHERE" | |
+ " h1 IN (?, ?, ?) AND" | |
+ " h2 = ?", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareSelectOrDeleteQuery(String queryString) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
for (int i = 1; i <= 4; ++i) { | |
pquery.setInt(i, i); | |
} | |
return pquery; | |
} | |
@Override | |
String getOptimizedUpdateQuery(int valueToSet) { | |
return String.format("UPDATE %s SET v = %d WHERE" | |
+ " h1 IN (%d, %d, %d) AND" | |
+ " h2 = %d", | |
tableName, valueToSet, 1, 2, 3, 4); | |
} | |
@Override | |
String getOptimizedPreparedUpdateQueryString() { | |
return String.format("UPDATE %s SET v = ? WHERE" | |
+ " h1 IN (?, ?, ?) AND" | |
+ " h2 = ?", | |
tableName); | |
} | |
@Override | |
PreparedStatement prepareUpdateQuery(String queryString, int valueToSet) throws Exception { | |
PreparedStatement pquery = connection.prepareStatement(queryString); | |
pquery.setInt(1, valueToSet); | |
for (int i = 1; i <= 4; ++i) { | |
pquery.setInt(i + 1, i); | |
} | |
return pquery; | |
} | |
@Override | |
String getResetQuery() { | |
return String.format("UPDATE %s SET v = h1 * 10000 + h2", tableName); | |
} | |
@Override | |
String getOptimizedDeleteQuery() { | |
return String.format( | |
"DELETE FROM %s WHERE" | |
+ " h1 IN (%d, %d, %d) AND" | |
+ " h2 = %d", | |
tableName, 1, 2, 3, 4); | |
} | |
@Override | |
String getOptimizedPreparedDeleteQueryString() { | |
return String.format( | |
"DELETE FROM %s WHERE" | |
+ " h1 IN (?, ?, ?) AND" | |
+ " h2 = ?", | |
tableName); | |
} | |
}; | |
tester.test(); | |
} | |
/** | |
* Tests execution time of SELECT/UPDATE ... WHERE ... IN (...) type of statements by comparing it | |
* with the execution time of similar non-optimized queries. | |
* <p> | |
* Expects last column to be "v" of type INT. | |
*/ | |
private abstract class PerfTester { | |
/** How many times would each query be iterated to get a total running time? */ | |
public final int queryRunCount = 10000; | |
public final int ignoredQueryRuns = 3; | |
public final String tableName; | |
/** Passed to CREATE TABLE in parentheses, list columns and primary key */ | |
public final String tableSpec; | |
public PerfTester(String tableName, String tableSpec) { | |
this.tableName = tableName; | |
this.tableSpec = tableSpec; | |
} | |
void createTable(Statement stmt) throws Exception { | |
stmt.executeUpdate(String.format("DROP TABLE IF EXISTS %s", tableName)); | |
stmt.executeUpdate(String.format("CREATE TABLE %s (%s)", tableName, tableSpec)); | |
} | |
void dropTable(Statement stmt) throws Exception { | |
stmt.executeUpdate(String.format("DROP TABLE %s", tableName)); | |
} | |
/** Fill the table with test values */ | |
abstract void fillTable(Statement stmt) throws Exception; | |
/** Expected rows subset operated by subsequent queries */ | |
abstract List<Row> getExpectedRows(); | |
// | |
// SELECT | |
// | |
/** Optimized query that should yield expected rows */ | |
abstract String getOptimizedSelectQuery(); | |
/** Optimized query string that, once prepared, should yield expected rows */ | |
abstract String getOptimizedPreparedSelectQueryString(); | |
/** | |
* Prepare a query returned by either {@link #getOptimizedPreparedSelectQueryString()} or | |
* {@link #getOptimizedPreparedDeleteQueryString()} (since their parameters match) | |
*/ | |
abstract PreparedStatement prepareSelectOrDeleteQuery(String queryString) throws Exception; | |
// | |
// UPDATE | |
// | |
/** Optimized query updating column "v" in expected rows to the given value */ | |
abstract String getOptimizedUpdateQuery(int valueToSet); | |
/** Optimized query string that, once prepared, should update column "v" in expected rows */ | |
abstract String getOptimizedPreparedUpdateQueryString(); | |
/** | |
* Prepare a query returned by {@link #getOptimizedPreparedUpdateQueryString(int)} to set the | |
* given value in expected rows | |
*/ | |
abstract PreparedStatement prepareUpdateQuery(String queryString, int valueToSet) | |
throws Exception; | |
/** Query that should reset column "v" to the initial state in the whole table */ | |
abstract String getResetQuery(); | |
// | |
// DELETE | |
// | |
/** Optimized query deleting expected rows */ | |
abstract String getOptimizedDeleteQuery(); | |
/** Optimized query string that, once prepared, should delete expected rows */ | |
abstract String getOptimizedPreparedDeleteQueryString(); | |
// | |
// Test methods | |
// | |
public void test() throws Exception { | |
try (Statement stmt = connection.createStatement()) { | |
createTable(stmt); | |
fillTable(stmt); | |
List<Row> expectedResult = getExpectedRows(); | |
testUpdate(stmt, expectedResult); | |
testDelete(stmt, expectedResult); | |
dropTable(stmt); | |
} | |
} | |
private void testUpdate(Statement stmt, List<Row> expectedResult) throws Exception { | |
int lastColIdx = expectedResult.get(0).elems.size() - 1; | |
int valueToSet = 123456789; | |
List<Row> updatedResult = new ArrayList<>(expectedResult.size()); | |
for (Row row : expectedResult) { | |
Row urow = row.clone(); | |
urow.elems.set(lastColIdx, valueToSet); | |
updatedResult.add(urow); | |
} | |
String selectQuery1 = getOptimizedSelectQuery(); | |
String selectQuery2 = String.format("SELECT * FROM %s WHERE v = %d", tableName, valueToSet); | |
RuntimeData rawRuntime, plainRuntime, preparedRuntime; | |
// { | |
// // Full scan update query used as an execution time reference | |
// String inClause = StringUtils | |
// .join(expectedResult.stream().map(r -> r.getInt(lastColIdx)).iterator(), ","); | |
// String query = String.format( | |
// "UPDATE %s SET v = %d WHERE v IN (%s)", tableName, valueToSet, inClause); | |
// rawRuntime = timeStatement("Non-optimized UPDATE", query, queryRunCount); | |
// assertEquals(updatedResult, getSortedRowList(stmt.executeQuery(selectQuery1))); | |
// assertEquals(updatedResult, getSortedRowList(stmt.executeQuery(selectQuery2))); | |
// } | |
// Plain optimized query | |
{ | |
stmt.executeUpdate(getResetQuery()); | |
String query = getOptimizedUpdateQuery(valueToSet); | |
plainRuntime = timeStatement("Plain optimized UPDATE", query, queryRunCount); | |
assertEquals(updatedResult, getSortedRowList(stmt.executeQuery(selectQuery1))); | |
assertEquals(updatedResult, getSortedRowList(stmt.executeQuery(selectQuery2))); | |
} | |
// Prepared optimized query | |
{ | |
stmt.executeUpdate(getResetQuery()); | |
String queryString = getOptimizedPreparedUpdateQueryString(); | |
try (PreparedStatement pquery = prepareUpdateQuery(queryString, valueToSet)) { | |
preparedRuntime = timeStatement("Prepared optimized UPDATE", pquery, queryRunCount); | |
assertEquals(updatedResult, getSortedRowList(stmt.executeQuery(selectQuery1))); | |
assertEquals(updatedResult, getSortedRowList(stmt.executeQuery(selectQuery2))); | |
} | |
} | |
// Cleanup | |
stmt.executeUpdate(getResetQuery()); | |
reportRunningTimes(/*rawRuntime, */plainRuntime, preparedRuntime); | |
} | |
private void testDelete(Statement stmt, List<Row> rowsToDelete) throws Exception { | |
int lastColIdx = rowsToDelete.get(0).elems.size() - 1; | |
String truncateQuery = String.format("TRUNCATE TABLE %s", tableName); | |
String selectQuery = getOptimizedSelectQuery(); | |
long tableSizeBefore = getTableSize(stmt); | |
long tableSizeAfter = tableSizeBefore - rowsToDelete.size(); | |
RuntimeData rawRuntime, plainRuntime, preparedRuntime; | |
// { | |
// // Full scan update query used as an execution time reference | |
// String inClause = StringUtils | |
// .join(rowsToDelete.stream().map(r -> r.getInt(lastColIdx)).iterator(), ","); | |
// String nonOptimizedQuery = String.format( | |
// "DELETE FROM %s WHERE v IN (%s)", tableName, inClause); | |
// rawRuntime = timeStatement("Non-optimized DELETE", nonOptimizedQuery, queryRunCount); | |
// assertEquals(Collections.EMPTY_LIST, getSortedRowList(stmt.executeQuery(selectQuery))); | |
// assertEquals(tableSizeAfter, getTableSize(stmt)); | |
// } | |
// Plain optimized query | |
{ | |
stmt.executeUpdate(truncateQuery); | |
fillTable(stmt); | |
String query = getOptimizedDeleteQuery(); | |
plainRuntime = timeStatement("Plain optimized DELETE", query, queryRunCount); | |
assertEquals(Collections.EMPTY_LIST, getSortedRowList(stmt.executeQuery(selectQuery))); | |
assertEquals(tableSizeAfter, getTableSize(stmt)); | |
} | |
// Prepared optimized query | |
{ | |
stmt.executeUpdate(truncateQuery); | |
fillTable(stmt); | |
String queryString = getOptimizedPreparedDeleteQueryString(); | |
try (PreparedStatement pquery = prepareSelectOrDeleteQuery(queryString)) { | |
preparedRuntime = timeStatement("Prepared optimized DELETE", pquery, queryRunCount); | |
assertEquals(Collections.EMPTY_LIST, getSortedRowList(stmt.executeQuery(selectQuery))); | |
assertEquals(tableSizeAfter, getTableSize(stmt)); | |
} | |
} | |
reportRunningTimes(/*rawRuntime, */plainRuntime, preparedRuntime); | |
} | |
// | |
// Helpers | |
// | |
private long getTableSize(Statement stmt) throws Exception { | |
String selectCountQuery = String.format("SELECT COUNT(*) FROM %s", tableName); | |
return getRowList(stmt.executeQuery(selectCountQuery)).get(0).getLong(0); | |
} | |
private RuntimeData timeStatement(String desc, String stmtSrc, int numberOfRuns) | |
throws Exception { | |
LOG.info("Starting timing query '" + desc + "'"); | |
List<Long> result = new ArrayList<>(numberOfRuns); | |
try (Statement stmt = connection.createStatement()) { | |
// Not timing the first few query runs as their result is not predictable. | |
for (int i = 0; i < ignoredQueryRuns; ++i) { | |
stmt.executeUpdate(stmtSrc); | |
} | |
for (int qrun = 0; qrun < numberOfRuns; qrun++) { | |
long startMs = System.currentTimeMillis(); | |
stmt.executeUpdate(stmtSrc); | |
long endMs = System.currentTimeMillis(); | |
result.add(endMs - startMs); | |
} | |
} | |
// Check the elapsed time. | |
LOG.info("Finished timing query '" + desc + "'"); | |
return new RuntimeData(desc, result); | |
} | |
private RuntimeData timeStatement(String desc, PreparedStatement stmt, int numberOfRuns) | |
throws Exception { | |
LOG.info("Starting timing query '" + desc + "'"); | |
List<Long> result = new ArrayList<>(numberOfRuns); | |
// Not timing the first few query runs as their result is not predictable. | |
for (int i = 0; i < ignoredQueryRuns; ++i) { | |
stmt.executeUpdate(); | |
} | |
for (int qrun = 0; qrun < numberOfRuns; qrun++) { | |
long startMs = System.currentTimeMillis(); | |
stmt.executeUpdate(); | |
long endMs = System.currentTimeMillis(); | |
result.add(endMs - startMs); | |
} | |
// Check the elapsed time. | |
LOG.info("Finished timing query '" + desc + "'"); | |
return new RuntimeData(desc, result); | |
} | |
private double prettyDouble(double d) { | |
return ((long) (d * 10)) / 10.0d; | |
} | |
private void reportRunningTimes(RuntimeData... data) { | |
for (RuntimeData datum : data) { | |
List<Long> rt = new ArrayList<>(datum.runningTimes); | |
int size = rt.size(); | |
Collections.sort(rt); | |
String header = "=== Runtime data for " + datum.desc + ", in ms (ran " + size | |
+ " times, test = " + testName.getMethodName() + ") ==="; | |
double avg = 0; | |
for (double x : rt) { | |
avg += x / size; | |
} | |
double stddev2 = 0; | |
for (double x : rt) { | |
stddev2 += Math.pow(x - avg, 2) / size; | |
} | |
double stddev = Math.sqrt(stddev2); | |
double total = 0; | |
for (double x : rt) { | |
total += x; | |
} | |
assertTrue(total < Long.MAX_VALUE); | |
LOG.info(header); | |
LOG.info("min = " + rt.get(0)); | |
LOG.info("max = " + rt.get(size - 1)); | |
LOG.info("avg = " + prettyDouble(avg)); | |
LOG.info("median = " + rt.get(size / 2)); | |
LOG.info("stddev = " + prettyDouble(stddev)); | |
LOG.info("total = " + (long) total); | |
} | |
} | |
private class RuntimeData { | |
public final String desc; | |
public final List<Long> runningTimes; | |
public RuntimeData(String desc, List<Long> runningTimes) { | |
this.desc = desc; | |
this.runningTimes = runningTimes; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment