Skip to content

Instantly share code, notes, and snippets.

@frozenspider
Created May 17, 2020 03:26
Show Gist options
  • Save frozenspider/beb80b651b62b7f27467a43254c43be0 to your computer and use it in GitHub Desktop.
Save frozenspider/beb80b651b62b7f27467a43254c43be0 to your computer and use it in GitHub Desktop.
// 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