Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ For composite benchmarks like `chbenchmark`, which require multiple schemas to b
java -jar benchbase.jar -b tpcc,chbenchmark -c config/postgres/sample_chbenchmark_config.xml --create=true --load=true --execute=true
```

To execute the `tpcc` benchmark with every transaction issued as a single stored procedure call (PostgreSQL only):
```bash
java -jar benchbase.jar -b tpcc -c config/postgres/sample_tpcc_stored_procedures_config.xml --create=true --load=true --execute=true
```

Setting `<useStoredProcedures>true</useStoredProcedures>` makes `--create=true` install `procedures-postgres.sql` alongside the schema and makes each terminal issue one `CALL` per transaction instead of the usual sequence of statements. The database does the same work either way; what changes is that a transaction costs one round trip plus the commit rather than roughly 25, which matters when the client/server round trip rather than the database is the limit.

The following options are provided:

```text
Expand Down
54 changes: 54 additions & 0 deletions config/postgres/sample_tpcc_stored_procedures_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0"?>
<parameters>

<!-- Connection details -->
<type>POSTGRES</type>
<driver>org.postgresql.Driver</driver>
<url>jdbc:postgresql://localhost:5432/benchbase?sslmode=disable&amp;ApplicationName=tpcc&amp;reWriteBatchedInserts=true</url>
<username>admin</username>
<password>password</password>
<reconnectOnConnectionFailure>true</reconnectOnConnectionFailure>
<isolation>TRANSACTION_SERIALIZABLE</isolation>
<batchsize>128</batchsize>

<!--
Run every TPC-C transaction as a single CALL of a PL/pgSQL function instead
of the statement sequence the Java procedures issue. The database work is
the same; what changes is that a transaction costs one round trip plus the
commit rather than roughly 25. Requires --create=true so that
procedures-postgres.sql is installed alongside the schema.
-->
<useStoredProcedures>true</useStoredProcedures>

<!-- Scale factor is the number of warehouses in TPCC -->
<scalefactor>1</scalefactor>

<!-- The workload -->
<terminals>1</terminals>
<works>
<work>
<time>60</time>
<rate>10000</rate>
<weights>45,43,4,4,4</weights>
</work>
</works>

<!-- TPCC specific -->
<transactiontypes>
<transactiontype>
<name>NewOrder</name>
</transactiontype>
<transactiontype>
<name>Payment</name>
</transactiontype>
<transactiontype>
<name>OrderStatus</name>
</transactiontype>
<transactiontype>
<name>Delivery</name>
</transactiontype>
<transactiontype>
<name>StockLevel</name>
</transactiontype>
</transactiontypes>
</parameters>
25 changes: 25 additions & 0 deletions src/main/java/com/oltpbenchmark/api/BenchmarkModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,25 @@ public final void createDatabase() throws SQLException, IOException {
}
}

/**
* True if every procedure of this benchmark performs one complete transaction on its own, in
* which case workers run on an autocommit connection and do not issue their own commit or
* rollback.
*/
public boolean usesAutoCommit() {
return false;
}

/**
* Return the classpath resource holding statements that must run right after the DDL, such as
* stored procedure definitions. Benchmarks that do not need one return null.
*
* @param db_type
*/
public String getPostDDLScriptPath(DatabaseType db_type) {
return null;
}

/**
* Create the Benchmark Database This is the main method used to create all the database objects
* (e.g., table, indexes, etc) needed for this benchmark
Expand All @@ -251,6 +270,12 @@ public final void createDatabase(DatabaseType dbType, Connection conn)
LOG.debug("Executing script [{}] for database type [{}]", ddlPath, dbType);
runner.runScript(ddlPath);
}

String postDDLPath = this.getPostDDLScriptPath(dbType);
if (postDDLPath != null) {
LOG.debug("Executing post-DDL script [{}] for database type [{}]", postDDLPath, dbType);
runner.runScript(postDDLPath);
}
}

public final void runScript(String scriptPath) throws SQLException, IOException {
Expand Down
20 changes: 14 additions & 6 deletions src/main/java/com/oltpbenchmark/api/Worker.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public Worker(T benchmark, int id) {
if (!this.configuration.getNewConnectionPerTxn()) {
try {
this.conn = this.benchmark.makeConnection();
this.conn.setAutoCommit(false);
this.conn.setAutoCommit(this.benchmark.usesAutoCommit());
this.conn.setTransactionIsolation(this.configuration.getIsolationMode());
} catch (SQLException ex) {
throw new RuntimeException("Failed to connect to database", ex);
Expand Down Expand Up @@ -424,7 +424,7 @@ protected final void doWork(DatabaseType databaseType, TransactionType transacti
}
}
this.conn = this.benchmark.makeConnection();
this.conn.setAutoCommit(false);
this.conn.setAutoCommit(this.benchmark.usesAutoCommit());
this.conn.setTransactionIsolation(this.configuration.getIsolationMode());
} catch (SQLException ex) {
if (LOG.isDebugEnabled()) {
Expand Down Expand Up @@ -453,13 +453,17 @@ protected final void doWork(DatabaseType databaseType, TransactionType transacti
LOG.debug(String.format("%s %s committing...", this, transactionType));
}

conn.commit();
if (!conn.getAutoCommit()) {
conn.commit();
}

break;

} catch (UserAbortException ex) {
try {
conn.rollback();
if (!conn.getAutoCommit()) {
conn.rollback();
}
} catch (SQLException ex2) {
LOG.error("SQLException caught while rolling back transaction.", ex2);
// force a reconnection
Expand Down Expand Up @@ -517,7 +521,9 @@ protected final void doWork(DatabaseType databaseType, TransactionType transacti
ex.getErrorCode()),
ex);
try {
conn.rollback();
if (!conn.getAutoCommit()) {
conn.rollback();
}
} catch (SQLException ex2) {
LOG.error("SQLException caught while attempting to rollback transaction.", ex2);
// force a reconnection
Expand Down Expand Up @@ -550,7 +556,9 @@ protected final void doWork(DatabaseType databaseType, TransactionType transacti
ex.getErrorCode()),
ex);
try {
conn.rollback();
if (!conn.getAutoCommit()) {
conn.rollback();
}
} catch (SQLException ex2) {
LOG.error("SQLException caught while attempting to rollback transaction.", ex2);
// force a reconnection
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/com/oltpbenchmark/benchmarks/tpcc/TPCCBenchmark.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.oltpbenchmark.api.Loader;
import com.oltpbenchmark.api.Worker;
import com.oltpbenchmark.benchmarks.tpcc.procedures.NewOrder;
import com.oltpbenchmark.types.DatabaseType;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -32,8 +33,41 @@
public final class TPCCBenchmark extends BenchmarkModule {
private static final Logger LOG = LoggerFactory.getLogger(TPCCBenchmark.class);

private final boolean useStoredProcedures;

public TPCCBenchmark(WorkloadConfiguration workConf) {
super(workConf);
this.useStoredProcedures =
workConf.getXmlConfig() != null
&& workConf.getXmlConfig().getBoolean("useStoredProcedures", false);
}

/**
* When true each transaction is a single CALL of a server-side function instead of the statement
* sequence issued by the procedure classes. The database work is the same; only the number of
* client/server round trips changes.
*/
public boolean useStoredProcedures() {
return this.useStoredProcedures;
}

@Override
public boolean usesAutoCommit() {
// A stored procedure call is a complete transaction, so there is nothing for the worker to
// commit afterwards, and skipping the commit saves the round trip it costs.
return this.useStoredProcedures;
}

@Override
public String getPostDDLScriptPath(DatabaseType dbType) {
if (!this.useStoredProcedures) {
return null;
}
if (dbType != DatabaseType.POSTGRES) {
throw new UnsupportedOperationException(
"TPC-C stored procedures are currently implemented for PostgreSQL only, not " + dbType);
}
return "/benchmarks/" + this.getBenchmarkName() + "/procedures-postgres.sql";
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ SELECT SUM(OL_AMOUNT) AS OL_TOTAL
"""
.formatted(TPCCConstants.TABLENAME_CUSTOMER));

public SQLStmt stmtDeliveryProcSQL = new SQLStmt("SELECT tpcc_delivery(?,?,?)");

public void run(
Connection conn,
Random gen,
Expand All @@ -120,6 +122,11 @@ public void run(

int o_carrier_id = TPCCUtil.randomNumber(1, 10, gen);

if (w.getBenchmark().useStoredProcedures()) {
deliveryStoredProcedure(conn, w_id, o_carrier_id, terminalDistrictUpperID);
return;
}

int d_id;

int[] orderIDs = new int[10];
Expand Down Expand Up @@ -173,6 +180,23 @@ public void run(
}
}

private void deliveryStoredProcedure(
Connection conn, int w_id, int o_carrier_id, int terminalDistrictUpperID)
throws SQLException {

try (PreparedStatement stmt = this.getPreparedStatement(conn, stmtDeliveryProcSQL)) {
stmt.setInt(1, w_id);
stmt.setInt(2, o_carrier_id);
stmt.setInt(3, terminalDistrictUpperID);

try (ResultSet rs = stmt.executeQuery()) {
if (!rs.next()) {
throw new RuntimeException("tpcc_delivery returned no row");
}
}
}
}

private Integer getOrderId(Connection conn, int w_id, int d_id) throws SQLException {

try (PreparedStatement delivGetOrderId = this.getPreparedStatement(conn, delivGetOrderIdSQL)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ public class NewOrder extends TPCCProcedure {
"""
.formatted(TPCCConstants.TABLENAME_ORDERLINE));

public SQLStmt stmtNewOrderProcSQL = new SQLStmt("SELECT * FROM tpcc_new_order(?,?,?,?,?,?)");

/** SQLSTATE raised by tpcc_new_order for the 1% of orders that must roll back. */
private static final String SQLSTATE_USER_ABORT = "TPCC1";

public void run(
Connection conn,
Random gen,
Expand Down Expand Up @@ -168,16 +173,68 @@ public void run(
itemIDs[numItems - 1] = TPCCConfig.INVALID_ITEM_ID;
}

newOrderTransaction(
terminalWarehouseID,
districtID,
customerID,
numItems,
allLocal,
itemIDs,
supplierWarehouseIDs,
orderQuantities,
conn);
if (w.getBenchmark().useStoredProcedures()) {
newOrderStoredProcedure(
terminalWarehouseID,
districtID,
customerID,
numItems,
itemIDs,
supplierWarehouseIDs,
orderQuantities,
conn);
} else {
newOrderTransaction(
terminalWarehouseID,
districtID,
customerID,
numItems,
allLocal,
itemIDs,
supplierWarehouseIDs,
orderQuantities,
conn);
}
}

private void newOrderStoredProcedure(
int w_id,
int d_id,
int c_id,
int o_ol_cnt,
int[] itemIDs,
int[] supplierWarehouseIDs,
int[] orderQuantities,
Connection conn)
throws SQLException {

try (PreparedStatement stmt = this.getPreparedStatement(conn, stmtNewOrderProcSQL)) {
stmt.setInt(1, w_id);
stmt.setInt(2, d_id);
stmt.setInt(3, c_id);
stmt.setArray(4, conn.createArrayOf("integer", box(itemIDs, o_ol_cnt)));
stmt.setArray(5, conn.createArrayOf("integer", box(supplierWarehouseIDs, o_ol_cnt)));
stmt.setArray(6, conn.createArrayOf("integer", box(orderQuantities, o_ol_cnt)));

try (ResultSet rs = stmt.executeQuery()) {
if (!rs.next()) {
throw new RuntimeException("tpcc_new_order returned no row");
}
}
} catch (SQLException e) {
if (SQLSTATE_USER_ABORT.equals(e.getSQLState())) {
throw new UserAbortException(e.getMessage());
}
throw e;
}
}

private static Integer[] box(int[] values, int length) {
Integer[] boxed = new Integer[length];
for (int i = 0; i < length; i++) {
boxed[i] = values[i];
}
return boxed;
}

private void newOrderTransaction(
Expand Down
Loading