diff --git a/benchmarks/ycsb/Dockerfile b/benchmarks/ycsb/Dockerfile index a8bb2d691..1c1ef7e9d 100644 --- a/benchmarks/ycsb/Dockerfile +++ b/benchmarks/ycsb/Dockerfile @@ -1,21 +1,22 @@ # BUILD FROM maven:3.8.4-eclipse-temurin-17-alpine AS build -# Copy over build files to docker image. -COPY LICENSE ./ -COPY CONTRIBUTING.md ./ -COPY README.md ./ -COPY NOTIFICATIONS.md ./ -COPY logging.properties ./ -COPY src src/ -COPY pom.xml ./ -COPY license-checks.xml ./ -COPY java.header ./ -# Download dependencies -RUN mvn dependency:go-offline - -# Build from source. -RUN mvn package -Passembly -DskipTests - +## Copy over build files to docker image. +#COPY LICENSE ./ +#COPY CONTRIBUTING.md ./ +#COPY README.md ./ +#COPY NOTIFICATIONS.md ./ +#COPY logging.properties ./ +#COPY src src/ +#COPY pom.xml ./ +#COPY license-checks.xml ./ +#COPY java.header ./ +## Download dependencies +#RUN mvn dependency:go-offline +# +## Build from source. +#RUN mvn package -Passembly -DskipTests + +COPY target/pgadapter target/pgadapter # Docker image for the YCSB runner. FROM gcr.io/google.com/cloudsdktool/google-cloud-cli:slim @@ -27,7 +28,7 @@ RUN apt -y install python COPY --from=build target/pgadapter / -ADD https://github.com/brianfrankcooper/YCSB/releases/download/0.17.0/ycsb-0.17.0.tar.gz /ycsb-0.17.0.tar.gz +COPY benchmarks/ycsb/ycsb-0.17.0.tar.gz ./ RUN tar xfvz ycsb-0.17.0.tar.gz RUN mv ycsb-0.17.0 ycsb diff --git a/benchmarks/ycsb/create-and-run-ycsb-job.sh b/benchmarks/ycsb/create-and-run-ycsb-job.sh index be7af8e44..c80cd1c2b 100755 --- a/benchmarks/ycsb/create-and-run-ycsb-job.sh +++ b/benchmarks/ycsb/create-and-run-ycsb-job.sh @@ -1,16 +1,19 @@ #!/bin/bash set -euo pipefail -PGADAPTER_YCSB_RUNNER=pgadapter-ycsb-runner -PGADAPTER_YCSB_JOB=pgadapter-ycsb-job +PGADAPTER_YCSB_RUNNER=pgadapter-channels-ycsb-runner +PGADAPTER_YCSB_JOB=pgadapter-channels-ycsb-job PGADAPTER_YCSB_REGION=europe-north1 +NUMBER_OF_TASKS=4 SPANNER_INSTANCE=pgadapter-ycsb-regional-test -SPANNER_DATABASE=pgadapter-ycsb-test +SPANNER_DATABASE=pgadapter-channels-ycsb-test gcloud config set run/region $PGADAPTER_YCSB_REGION gcloud config set builds/region $PGADAPTER_YCSB_REGION +mvn clean package -Passembly -DskipTests + gcloud auth configure-docker gcr.io --quiet docker build --platform=linux/amd64 . -f benchmarks/ycsb/Dockerfile \ -t gcr.io/$(gcloud config get project --quiet)/$PGADAPTER_YCSB_RUNNER @@ -20,7 +23,7 @@ gcloud beta run jobs delete $PGADAPTER_YCSB_JOB --quiet || true sleep 3 gcloud beta run jobs create $PGADAPTER_YCSB_JOB \ --image gcr.io/$(gcloud config get project --quiet)/$PGADAPTER_YCSB_RUNNER \ - --tasks 1 \ + --tasks $NUMBER_OF_TASKS \ --set-env-vars SPANNER_INSTANCE=$SPANNER_INSTANCE \ --set-env-vars SPANNER_DATABASE=$SPANNER_DATABASE \ --max-retries 0 \ diff --git a/benchmarks/ycsb/run.sh b/benchmarks/ycsb/run.sh index 947687499..3474f1b6f 100644 --- a/benchmarks/ycsb/run.sh +++ b/benchmarks/ycsb/run.sh @@ -3,13 +3,15 @@ set -euox pipefail echo "Starting Task #${CLOUD_RUN_TASK_INDEX}, Attempt #${CLOUD_RUN_TASK_ATTEMPT}..." EXECUTED_AT=`date +"%Y-%m-%dT%T"` +EXECUTED_AT="${EXECUTED_AT} ${CLOUD_RUN_TASK_INDEX}" DATABASES=$(gcloud spanner databases list --instance $SPANNER_INSTANCE --filter="name:${SPANNER_DATABASE}") if [[ "$DATABASES" != *"$SPANNER_DATABASE"* ]]; then gcloud spanner databases create $SPANNER_DATABASE --instance=$SPANNER_INSTANCE --database-dialect='POSTGRESQL' fi -java -jar pgadapter.jar -p $(gcloud --quiet config get project) -i $SPANNER_INSTANCE -r="minSessions=1000;maxSessions=1000;numChannels=20" & +PARAMETERS="minSessions=400;maxSessions=400;numChannels=100" +java -jar pgadapter.jar -p $(gcloud --quiet config get project) -i $SPANNER_INSTANCE -r="${PARAMETERS}" & sleep 6 export PGDATABASE=$SPANNER_DATABASE psql -h localhost -c "CREATE TABLE IF NOT EXISTS usertable ( @@ -65,22 +67,22 @@ db.user= db.passwd= EOT -psql -h localhost -c "set spanner.autocommit_dml_mode='partitioned_non_atomic'; delete from usertable;" +#psql -h localhost -c "set spanner.autocommit_dml_mode='partitioned_non_atomic'; delete from usertable;" # Load workloada -./bin/ycsb load jdbc -P workloads/workloada \ - -threads 20 \ - -p recordcount=100000 \ - -p db.batchsize=200 \ - -p jdbc.batchupdateapi=true \ - -P tcp.properties \ - -cp "jdbc-binding/lib/*" \ - > ycsb.log +#./bin/ycsb load jdbc -P workloads/workloada \ +# -threads 40 \ +# -p recordcount=5000000 \ +# -p db.batchsize=200 \ +# -p jdbc.batchupdateapi=true \ +# -P tcp.properties \ +# -cp "jdbc-binding/lib/*" \ +# > ycsb.log # Run workloads a, b, c, f, and d. -for WORKLOAD in a b c f d +for WORKLOAD in a do - for DEPLOYMENT in java_tcp java_uds + for DEPLOYMENT in java_uds do if [ $DEPLOYMENT == 'java_tcp' ] then @@ -88,10 +90,10 @@ do else CONN=uds.properties fi - for THREADS in 1 5 20 50 200 + for THREADS in 100 do - OPERATION_COUNT=`expr $THREADS \* 100` - for BATCH_SIZE in 1 10 50 200 + OPERATION_COUNT=`expr $THREADS \* 15000` + for BATCH_SIZE in 10 do if [ $BATCH_SIZE == 1 ] then @@ -135,11 +137,11 @@ do INSERT_P99=$(grep '\[INSERT\], 99thPercentileLatency(us), ' ycsb.log | sed 's/^.*, //' || echo null) psql -h localhost \ - -c "insert into run (executed_at, deployment, workload, threads, batch_size, operation_count, run_time, throughput, + -c "insert into run (description, executed_at, deployment, workload, threads, batch_size, operation_count, run_time, throughput, read_min, read_max, read_avg, read_p50, read_p95, read_p99, update_min, update_max, update_avg, update_p50, update_p95, update_p99, insert_min, insert_max, insert_avg, insert_p50, insert_p95, insert_p99) values - ('$EXECUTED_AT', '$DEPLOYMENT', '$WORKLOAD', $THREADS, $BATCH_SIZE, $OPERATION_COUNT, $OVERALL_RUNTIME, $OVERALL_THROUGHPUT, + ('$PARAMETERS', '$EXECUTED_AT', '$DEPLOYMENT', '$WORKLOAD', $THREADS, $BATCH_SIZE, $OPERATION_COUNT, $OVERALL_RUNTIME, $OVERALL_THROUGHPUT, $READ_MIN, $READ_MAX, $READ_AVG, $READ_P50, $READ_P95, $READ_P99, $UPDATE_MIN, $UPDATE_MAX, $UPDATE_AVG, $UPDATE_P50, $UPDATE_P95, $UPDATE_P99, $INSERT_MIN, $INSERT_MAX, $INSERT_AVG, $INSERT_P50, $INSERT_P95, $INSERT_P99)" diff --git a/pom.xml b/pom.xml index 4c9d8feb3..542fb2580 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ com.google.cloud.spanner.pgadapter.nodejs.NodeJSTest - 6.35.2 + 6.38.3-SNAPSHOT 2.6.2 diff --git a/samples/ycsb/run-ycsb.sh b/samples/ycsb/run-ycsb.sh new file mode 100755 index 000000000..d7c73601c --- /dev/null +++ b/samples/ycsb/run-ycsb.sh @@ -0,0 +1,100 @@ + +# Make sure you have psql, Java, wget and python installed on the system. + +#sudo apt update && apt -y install postgresql-client +#sudo apt -y install default-jre +#sudo apt -y install wget +#sudo apt -y install python + +# Change these variables to your database name and service account. +SPANNER_DATABASE="projects/my-project/instances/my-instance/databases/my-database" +CREDENTIALS=/path/to/service-account.json + +echo "Downloading YCSB" +wget https://github.com/brianfrankcooper/YCSB/releases/download/0.17.0/ycsb-0.17.0.tar.gz + +echo "Extracting YCSB" +tar xfvz ycsb-0.17.0.tar.gz +mv ycsb-0.17.0 ycsb + +echo "Download the PostgreSQL JDBC driver and the additional libraries that are needed to use Unix Domain Sockets" +wget -P ycsb/jdbc-binding/lib/ https://repo1.maven.org/maven2/org/postgresql/postgresql/42.5.0/postgresql-42.5.0.jar +wget -P ycsb/jdbc-binding/lib/ https://repo1.maven.org/maven2/com/kohlschutter/junixsocket/junixsocket-common/2.6.2/junixsocket-common-2.6.2.jar +wget -P ycsb/jdbc-binding/lib/ https://repo1.maven.org/maven2/com/kohlschutter/junixsocket/junixsocket-native-common/2.6.2/junixsocket-native-common-2.6.2.jar + +echo "Pull and start the PGAdapter Docker container" +docker pull gcr.io/cloud-spanner-pg-adapter/pgadapter +docker run \ + --name pgadapter \ + --rm \ + -d -p 5432:5432 \ + -v /tmp:/tmp \ + -v $CREDENTIALS:/credentials.json:ro \ + gcr.io/cloud-spanner-pg-adapter/pgadapter \ + -c /credentials.json -x +sleep 6 + + +echo "Create the 'usertable' table that is used by YCSB" +psql -h /tmp -d $SPANNER_DATABASE \ + -c "CREATE TABLE IF NOT EXISTS usertable ( + YCSB_KEY VARCHAR(255) PRIMARY KEY, + FIELD0 TEXT, FIELD1 TEXT, + FIELD2 TEXT, FIELD3 TEXT, + FIELD4 TEXT, FIELD5 TEXT, + FIELD6 TEXT, FIELD7 TEXT, + FIELD8 TEXT, FIELD9 TEXT + );" + +cd ycsb + +# This replaces '/' in the database name with '%2f' to make it URL safe. +# This is necessary when using a fully qualified database name in a JDBC connection URL. +URL_ENCODED_SPANNER_DATABASE=$(echo $SPANNER_DATABASE | sed -e 's/\//%2f/g') + +# Create a properties file that can be used with YCSB. +# This tells YCSB which database it should connect to. +cat <> uds.properties +db.driver=org.postgresql.Driver +db.url=jdbc:postgresql://localhost/$URL_ENCODED_SPANNER_DATABASE?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory\$FactoryArg&socketFactoryArg=/tmp/.s.PGSQL.5432 +db.user= +db.passwd= +EOT + +# Delete any data currently in the `usertable` table. +# Skip this if you want to reuse existing data. +psql -h /tmp -d $SPANNER_DATABASE \ + -c "set spanner.autocommit_dml_mode='partitioned_non_atomic'; delete from usertable;" + +# Load 'workload a' into `usertable`. +# Skip this if you want to reuse existing data. +# See also https://github.com/brianfrankcooper/YCSB/wiki/Running-a-Workload +echo "Load workload a" +echo "" +./bin/ycsb load jdbc -P workloads/workloada \ + -threads 20 \ + -p recordcount=10000 \ + -p db.batchsize=200 \ + -p jdbc.batchupdateapi=true \ + -P uds.properties \ + -cp "jdbc-binding/lib/*" + +# Run 'workload a'. +# Repeat this step without any of the other steps to just repeatedly run a workload. +echo "" +echo "---------------" +echo "Run workload a" +echo "" +./bin/ycsb run jdbc -P workloads/workloada \ + -threads 20 \ + -p hdrhistogram.percentiles=50,95,99 \ + -p operationcount=1000 \ + -p recordcount=10000 \ + -p db.batchsize=20 \ + -p jdbc.batchupdateapi=true \ + -P uds.properties \ + -cp "jdbc-binding/lib/*" + + +# Stop PGAdapter. +docker stop pgadapter diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java index 94a0bcbc5..bf41de7a9 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java @@ -250,6 +250,7 @@ private String appendPropertiesToUrl(String url, Properties info) { return url; } StringBuilder result = new StringBuilder(url); + result.append(";numChannels=4"); for (Entry entry : info.entrySet()) { if (entry.getValue() != null && !"".equals(entry.getValue())) { result.append(";").append(entry.getKey()).append("=").append(entry.getValue()); diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java index fe436f259..fe8181f9a 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java @@ -81,6 +81,9 @@ import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -337,16 +340,46 @@ private String getExpectedInitialApplicationName() { } @Test - public void testQuery() throws SQLException { + public void testQuery() throws SQLException, InterruptedException { String sql = "SELECT 1"; - try (Connection connection = DriverManager.getConnection(createUrl())) { - try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { - assertTrue(resultSet.next()); - assertEquals(1L, resultSet.getLong(1)); - assertFalse(resultSet.next()); - } - } + int numThreads = 128; + ExecutorService service = Executors.newFixedThreadPool(128); + for (int i = 0; i < numThreads; i++) { + service.submit( + (Callable) + () -> { + try (Connection connection = DriverManager.getConnection(createUrl())) { + try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { + assertTrue(resultSet.next()); + assertEquals(1L, resultSet.getLong(1)); + assertFalse(resultSet.next()); + } + } + return null; + }); + } + service.shutdown(); + service.awaitTermination(60L, TimeUnit.SECONDS); + System.out.println("Finished"); + // 4 channels: + // 16 threads for Transport-Channel + // 4 grpc default executor + // 8 grpc default worker + // 5 grpc transport + // + // 8 channels: + // 16 threads for Transport-Channel + // 8 grpc default executor + // 8 grpc default worker + // 8 grpc transport + // + // 12 channels: + // 16 threads for Transport-Channel + // 5 grpc default executor + // 8 grpc default worker + // 8 grpc transport + // // The statement is only sent once to the mock server. The DescribePortal message will trigger // the execution of the query, and the result from that execution will be used for the Execute