mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-25 18:55:28 -06:00
Merge pull request #342 from szimmer1/cockroachdb-errorhandler
Refactor error format for error handling in JDBCActivity
This commit is contained in:
commit
a4bad299f4
@ -4,12 +4,15 @@ description: An example of a basic cockroach insert
|
|||||||
|
|
||||||
scenarios:
|
scenarios:
|
||||||
default:
|
default:
|
||||||
- run driver=cockroachdb tags==phase:main threads==auto cycles===<<main-cycles:1000000>>
|
- run driver=cockroachdb tags==phase:main threads=auto cycles===<<main-cycles:1000000>>
|
||||||
|
serverName=localhost connectionpool=hikari
|
||||||
|
errors=SQLTransient.*:warn,count,retry;.*0800.*:warn,count,retry;.*40001:count,retry;stop
|
||||||
rampup:
|
rampup:
|
||||||
- run driver=cockroachdb tags==phase:rampup threads==auto cycles===<<rampup-cycles:1000000>>
|
- run driver=cockroachdb tags==phase:rampup threads=auto cycles===<<rampup-cycles:1000000>>
|
||||||
|
serverName=localhost connectionpool=hikari
|
||||||
|
errors=SQLTransient.*:warn,count,retry;.*0800.*:warn,count,retry;.*40001:count,retry;stop
|
||||||
schema:
|
schema:
|
||||||
- run driver=cockroachdb tags==phase:schema threads==1 cycles===2
|
- run driver=cockroachdb tags==phase:schema threads===1 serverName=localhost
|
||||||
#- run driver=stdout tags==phase:schema threads==1 cycles===UNDEF
|
|
||||||
|
|
||||||
bindings:
|
bindings:
|
||||||
seq_key: Mod(<<keyCount:1000000>>L); ToInt()
|
seq_key: Mod(<<keyCount:1000000>>L); ToInt()
|
||||||
@ -39,7 +42,10 @@ blocks:
|
|||||||
phase: rampup
|
phase: rampup
|
||||||
params:
|
params:
|
||||||
statements:
|
statements:
|
||||||
- rampup-insert: insert into <<database:bank>>.<<table:banktransaction>> ( code, amount ) values ( '{seq_key}', {seq_value} );
|
- rampup-insert: |
|
||||||
|
INSERT INTO "<<database:bank>>"."<<table:banktransaction>>"
|
||||||
|
(code, amount) VALUES ('{seq_key}', {seq_value})
|
||||||
|
ON CONFLICT (code) DO NOTHING;
|
||||||
params:
|
params:
|
||||||
tags:
|
tags:
|
||||||
name: rampup-insert
|
name: rampup-insert
|
||||||
@ -51,7 +57,8 @@ blocks:
|
|||||||
ratio: <<read_ratio:1>>
|
ratio: <<read_ratio:1>>
|
||||||
statements:
|
statements:
|
||||||
- main-find: |
|
- main-find: |
|
||||||
SELECT code, amount FROM <<database:bank>>.<<table:banktransaction>> WHERE code = '{rw_key}' AND amount = {rw_value};
|
SELECT code, amount FROM "<<database:bank>>"."<<table:banktransaction>>"
|
||||||
|
WHERE code = '{rw_key}' AND amount = {rw_value};
|
||||||
params:
|
params:
|
||||||
tags:
|
tags:
|
||||||
name: main-find
|
name: main-find
|
||||||
@ -63,7 +70,7 @@ blocks:
|
|||||||
ratio: <<write_ratio:1>>
|
ratio: <<write_ratio:1>>
|
||||||
statements:
|
statements:
|
||||||
- main-insert: |
|
- main-insert: |
|
||||||
UPDATE <<database:bank>>.<<table:banktransaction>> SET amount = {seq_value} WHERE code = '{seq_key}';
|
UPDATE "<<database:bank>>"."<<table:banktransaction>>" SET amount = {seq_value} WHERE code = '{seq_key}';
|
||||||
params:
|
params:
|
||||||
tags:
|
tags:
|
||||||
name: main-insert
|
name: main-insert
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
# java -jar nb.jar run driver=cockroachdb workload=postgres-basic tags=phase:rampup cycles=10 \
|
# java -jar nb.jar run driver=cockroachdb workload=postgres-basic tags=phase:rampup cycles=10 \
|
||||||
# serverName=localhost databaseName=bank
|
# serverName=localhost databaseName=bank
|
||||||
# java -jar nb.jar run driver=cockroachdb workload=postgres-basic tags=phase:main cycles=10 serverName=localhost
|
# java -jar nb.jar run driver=cockroachdb workload=postgres-basic tags=phase:main cycles=10 serverName=localhost
|
||||||
description: An example of a basic postgres bank transaction workload
|
description: An example of a basic postgres bank transaction workload
|
||||||
|
|
||||||
scenarios:
|
scenarios:
|
||||||
default:
|
default:
|
||||||
- run driver===cockroachdb tags===phase:main threads==auto cycles=10000000
|
- run driver===cockroachdb tags===phase:main threads=auto cycles=10000000
|
||||||
serverName=localhost portNumber=5432 databaseName=<<database:bank>> user=postgres
|
|
||||||
password=postgres
|
|
||||||
rampup:
|
|
||||||
- run driver===cockroachdb tags===phase:rampup threads==auto cycles=<<accounts:1000000>>
|
|
||||||
serverName=localhost portNumber=5432 databaseName=<<database:bank>> user=postgres
|
|
||||||
password=postgres connectionpool=hikari filler-binding="AlphaNumericString(10)"
|
|
||||||
rampup-large:
|
|
||||||
- run driver===cockroachdb tags===phase:rampup threads==auto cycles=<<accounts:1000000>>
|
|
||||||
serverName=localhost portNumber=5432 databaseName=<<database:bank>> user=postgres
|
serverName=localhost portNumber=5432 databaseName=<<database:bank>> user=postgres
|
||||||
password=postgres connectionpool=hikari
|
password=postgres connectionpool=hikari
|
||||||
|
errors=SQLTransient.*:warn,count,retry;.*0800.*:warn,count,retry;stop
|
||||||
|
rampup:
|
||||||
|
- run driver===cockroachdb tags===phase:rampup threads=auto cycles=<<accounts:1000000>>
|
||||||
|
serverName=localhost portNumber=5432 databaseName=<<database:bank>> user=postgres
|
||||||
|
password=postgres connectionpool=hikari filler-binding="AlphaNumericString(10)"
|
||||||
|
errors=SQLTransient.*:warn,count,retry;.*0800.*:warn,count,retry;stop
|
||||||
|
rampup-large:
|
||||||
|
- run driver===cockroachdb tags===phase:rampup threads=auto cycles=<<accounts:1000000>>
|
||||||
|
serverName=localhost portNumber=5432 databaseName=<<database:bank>> user=postgres
|
||||||
|
password=postgres connectionpool=hikari
|
||||||
|
errors=SQLTransient.*:warn,count,retry;.*0800.*:warn,count,retry;stop
|
||||||
schema:
|
schema:
|
||||||
- run driver===cockroachdb tags===phase:schema threads===1 serverName=localhost portNumber=5432
|
- run driver===cockroachdb tags===phase:schema threads===1 serverName=localhost portNumber=5432
|
||||||
databaseName=bank user=postgres password=postgres connectionpool=hikari
|
databaseName=bank user=postgres password=postgres
|
||||||
|
|
||||||
bindings:
|
bindings:
|
||||||
seq_uuid: Mod(<<accounts:1000000>>L); ToHashedUUID()
|
seq_uuid: Mod(<<accounts:1000000>>L); ToHashedUUID()
|
||||||
@ -55,7 +58,7 @@ blocks:
|
|||||||
params:
|
params:
|
||||||
statements:
|
statements:
|
||||||
- rampup-insert: |
|
- rampup-insert: |
|
||||||
INSERT INTO "<<table:account>>" (uuid, amount, amount_unit, updated_at, created_at, filler)
|
INSERT INTO "<<table:account>>" (uuid, amount, amount_unit, updated_at, created_at, filler)
|
||||||
VALUES ('{seq_uuid}', {rand_amount}, 'us_cents', '{timestamp}', '{timestamp}', '{filler}')
|
VALUES ('{seq_uuid}', {rand_amount}, 'us_cents', '{timestamp}', '{timestamp}', '{filler}')
|
||||||
ON CONFLICT DO NOTHING;
|
ON CONFLICT DO NOTHING;
|
||||||
params:
|
params:
|
||||||
|
@ -21,6 +21,30 @@ section for detailed parameter documentation.
|
|||||||
* *hikari* -
|
* *hikari* -
|
||||||
use [HikariCP](https://github.com/brettwooldridge/HikariCP)
|
use [HikariCP](https://github.com/brettwooldridge/HikariCP)
|
||||||
* **maxtries** (optional) - number of times to retry retry-able errors; Default *3*.
|
* **maxtries** (optional) - number of times to retry retry-able errors; Default *3*.
|
||||||
* **errors** (optional) - expression which specifies how to handle SQL state error codes.
|
* **minretrydelayms** (optional) - minimum time in ms to wait before retry with exponential backoff; Default *200*.
|
||||||
Expression syntax and behavior is explained in the `error-handlers` topic. Default
|
* **errors** (optional) - see `error-handlers` topic for details (`./nb help error-handlers`). Default *stop*.
|
||||||
*stop*, in other words exit on any error.
|
|
||||||
|
#### errors parameter
|
||||||
|
|
||||||
|
This parameter expects an expression which specifies how to handle exceptions by class name
|
||||||
|
and SQL state code. Error names are formatted as `<exception-name>_<sql-state>`.
|
||||||
|
|
||||||
|
For example, a *org.postgresql.util.PSQLException* with *SQLState=80001* will be formatted `PSQLException_80001`.
|
||||||
|
To continue on such an error, use `errors=PQLException_80001:warn,count;stop`. To retry any
|
||||||
|
*java.sql.SQLTransientException* or any *SQLState=80001* and otherwise stop, use
|
||||||
|
`errors=SQLTransientException.*:warn,count,retry;.*80001:warn,count,retry;stop`.
|
||||||
|
|
||||||
|
See scenario implementations in workloads `cockroachdb-basic` and `postgres-basic` for reasonable defaults
|
||||||
|
of the errors parameter. This is a reasonable default error handler chain:
|
||||||
|
|
||||||
|
1. `SQLTransient.*:warn,count,retry` - log, emit metric, and retry on transient errors
|
||||||
|
([java.sql doc](https://docs.oracle.com/javase/8/docs/api/java/sql/SQLTransientException.html))
|
||||||
|
2. `.*0800.*:warn,count,retry` - log, emit metric, and retry on "connection exception" class of postgresql driver
|
||||||
|
SQLState codes ([postgresql java doc](https://www.postgresql.org/docs/9.4/errcodes-appendix.html))
|
||||||
|
3. `.*40001:count,retry` - emit metric and retry on "serialization error" SQLState code of postgresql driver
|
||||||
|
([postgresql java doc](https://www.postgresql.org/docs/9.4/errcodes-appendix.html)).
|
||||||
|
These are common with CockroachDB
|
||||||
|
([doc](https://www.cockroachlabs.com/docs/stable/error-handling-and-troubleshooting.html#transaction-retry-errors)).
|
||||||
|
4. `stop` - stop the activity for any other error or if max retries are exceeded
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,9 +7,11 @@ import org.postgresql.util.PSQLException;
|
|||||||
import org.postgresql.util.PSQLState;
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class CockroachActivityTest {
|
public class CockroachActivityTest {
|
||||||
@Test
|
@Test
|
||||||
@ -19,12 +21,16 @@ public class CockroachActivityTest {
|
|||||||
|
|
||||||
// When the Throwable is a SQLException, the error name should be getSQLState()
|
// When the Throwable is a SQLException, the error name should be getSQLState()
|
||||||
Throwable sqlException = new SQLException("my test exception", "my-test-sql-state");
|
Throwable sqlException = new SQLException("my test exception", "my-test-sql-state");
|
||||||
assertEquals("my-test-sql-state", activity.errorNameMapper(sqlException));
|
assertEquals("SQLException_my-test-sql-state", activity.errorNameMapper(sqlException));
|
||||||
|
|
||||||
// See PSQLState to string code mapping at the Github source code website
|
// See PSQLState to string code mapping at the Github source code website
|
||||||
// https://github.com/pgjdbc/pgjdbc/blob/master/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
|
// https://github.com/pgjdbc/pgjdbc/blob/master/pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
|
||||||
Throwable psqlException = new PSQLException("retry transaction", PSQLState.CONNECTION_FAILURE);
|
Throwable psqlException = new PSQLException("retry transaction", PSQLState.CONNECTION_FAILURE);
|
||||||
assertEquals("08006", activity.errorNameMapper(psqlException));
|
assertEquals("PSQLException_08006", activity.errorNameMapper(psqlException));
|
||||||
|
|
||||||
|
// When SQLState is null or empty, suffix shouldn't be underscore
|
||||||
|
Throwable nullSQLState = new PSQLException("my test runtime exception", null);
|
||||||
|
assertEquals("PSQLException", activity.errorNameMapper(nullSQLState));
|
||||||
|
|
||||||
// When Throwable is not a SQLException, the error name should be the class name
|
// When Throwable is not a SQLException, the error name should be the class name
|
||||||
Throwable runtimeException = new SocketTimeoutException("my test runtime exception");
|
Throwable runtimeException = new SocketTimeoutException("my test runtime exception");
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.nosqlbench.activitytype.jdbc.api;
|
package io.nosqlbench.activitytype.jdbc.api;
|
||||||
|
|
||||||
import com.codahale.metrics.Counter;
|
|
||||||
import com.codahale.metrics.Histogram;
|
import com.codahale.metrics.Histogram;
|
||||||
import com.codahale.metrics.Timer;
|
import com.codahale.metrics.Timer;
|
||||||
import com.zaxxer.hikari.HikariConfig;
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
@ -11,7 +10,6 @@ import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
|||||||
import io.nosqlbench.engine.api.activityimpl.OpDispenser;
|
import io.nosqlbench.engine.api.activityimpl.OpDispenser;
|
||||||
import io.nosqlbench.engine.api.activityimpl.SimpleActivity;
|
import io.nosqlbench.engine.api.activityimpl.SimpleActivity;
|
||||||
import io.nosqlbench.engine.api.metrics.ActivityMetrics;
|
import io.nosqlbench.engine.api.metrics.ActivityMetrics;
|
||||||
import io.nosqlbench.engine.api.metrics.ExceptionCountMetrics;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@ -28,6 +26,7 @@ public abstract class JDBCActivity extends SimpleActivity {
|
|||||||
private Timer resultSuccessTimer;
|
private Timer resultSuccessTimer;
|
||||||
private Histogram triesHisto;
|
private Histogram triesHisto;
|
||||||
private int maxTries;
|
private int maxTries;
|
||||||
|
private int minRetryDelayMs;
|
||||||
|
|
||||||
protected DataSource dataSource;
|
protected DataSource dataSource;
|
||||||
protected OpSequence<OpDispenser<String>> opSequence;
|
protected OpSequence<OpDispenser<String>> opSequence;
|
||||||
@ -47,6 +46,7 @@ public abstract class JDBCActivity extends SimpleActivity {
|
|||||||
super.onActivityDefUpdate(activityDef);
|
super.onActivityDefUpdate(activityDef);
|
||||||
|
|
||||||
this.maxTries = getParams().getOptionalInteger("maxtries").orElse(3);
|
this.maxTries = getParams().getOptionalInteger("maxtries").orElse(3);
|
||||||
|
this.minRetryDelayMs = getParams().getOptionalInteger("minretrydelayms").orElse(200);
|
||||||
|
|
||||||
LOGGER.debug("initializing data source");
|
LOGGER.debug("initializing data source");
|
||||||
dataSource = newDataSource();
|
dataSource = newDataSource();
|
||||||
@ -79,10 +79,15 @@ public abstract class JDBCActivity extends SimpleActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String errorNameMapper(Throwable e) {
|
public String errorNameMapper(Throwable e) {
|
||||||
|
StringBuilder sb = new StringBuilder(e.getClass().getSimpleName());
|
||||||
if (e instanceof SQLException) {
|
if (e instanceof SQLException) {
|
||||||
return ((SQLException) e).getSQLState();
|
String sqlState = ((SQLException) e).getSQLState();
|
||||||
|
if (sqlState != null && !sqlState.isEmpty()) {
|
||||||
|
sb.append('_');
|
||||||
|
sb.append(sqlState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return e.getClass().getSimpleName();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -94,6 +99,10 @@ public abstract class JDBCActivity extends SimpleActivity {
|
|||||||
return this.maxTries;
|
return this.maxTries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinRetryDelayMs() {
|
||||||
|
return this.minRetryDelayMs;
|
||||||
|
}
|
||||||
|
|
||||||
public DataSource getDataSource() {
|
public DataSource getDataSource() {
|
||||||
return dataSource;
|
return dataSource;
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,9 @@ public class JDBCAction implements SyncAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int maxTries = activity.getMaxTries();
|
int maxTries = activity.getMaxTries();
|
||||||
|
Exception error = null;
|
||||||
|
|
||||||
for (int tries = 1; tries <= maxTries; tries++) {
|
for (int tries = 1; tries <= maxTries; tries++) {
|
||||||
Exception error = null;
|
|
||||||
long startTimeNanos = System.nanoTime();
|
long startTimeNanos = System.nanoTime();
|
||||||
|
|
||||||
try (Connection conn = activity.getDataSource().getConnection()) {
|
try (Connection conn = activity.getDataSource().getConnection()) {
|
||||||
@ -65,12 +65,31 @@ public class JDBCAction implements SyncAction {
|
|||||||
ErrorDetail detail = activity.getErrorHandler().handleError(error, cycle, executionTimeNanos);
|
ErrorDetail detail = activity.getErrorHandler().handleError(error, cycle, executionTimeNanos);
|
||||||
if (!detail.isRetryable()) {
|
if (!detail.isRetryable()) {
|
||||||
LOGGER.debug("Exit failure after non-retryable error");
|
LOGGER.debug("Exit failure after non-retryable error");
|
||||||
return 1;
|
throw new RuntimeException("non-retryable error", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int retryDelay = retryDelayMs(tries, activity.getMinRetryDelayMs());
|
||||||
|
LOGGER.debug("tries=" + tries + " sleeping for " + retryDelay + " ms");
|
||||||
|
Thread.sleep(retryDelay);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException("thread interrupted", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.debug("Exit failure after maxretries=" + maxTries);
|
LOGGER.debug("Exit failure after maxretries=" + maxTries);
|
||||||
return 1;
|
throw new RuntimeException("maxtries exceeded", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute retry delay based on exponential backoff with full jitter
|
||||||
|
* @param tries 1-indexed
|
||||||
|
* @param minDelayMs lower bound of retry delay
|
||||||
|
* @return retry delay
|
||||||
|
*/
|
||||||
|
private int retryDelayMs(int tries, int minDelayMs) {
|
||||||
|
int exponentialDelay = minDelayMs * (int) Math.pow(2.0, tries - 1);
|
||||||
|
return (int) (Math.random() * exponentialDelay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user