From 960807e359706e664b3897bc962d02f6705ae29f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Thu, 14 Dec 2023 21:30:08 +0100 Subject: [PATCH 01/46] perf: use virtual threads --- pom.xml | 7 ++++--- .../spanner/pgadapter/ConnectionHandler.java | 21 +++++++++++++++---- .../cloud/spanner/pgadapter/ProxyServer.java | 4 +++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index ceb96ed21..bdb5901a3 100644 --- a/pom.xml +++ b/pom.xml @@ -15,9 +15,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - 1.8 - 1.8 - 1.8 + 21 + 21 + 21 + 21 false ${skipTests} true 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 35ff8101c..94af92435 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java @@ -101,7 +101,7 @@ * necessarily need to have its own thread, this makes the implementation more straightforward. */ @InternalApi -public class ConnectionHandler extends Thread { +public class ConnectionHandler implements Runnable { private static final Logger logger = Logger.getLogger(ConnectionHandler.class.getName()); private static final AtomicLong CONNECTION_HANDLER_ID_GENERATOR = new AtomicLong(0L); private static final String CHANNEL_PROVIDER_PROPERTY = "CHANNEL_PROVIDER"; @@ -119,6 +119,8 @@ public class ConnectionHandler extends Thread { private static final Map CONNECTION_HANDLERS = new ConcurrentHashMap<>(); private volatile ConnectionStatus status = ConnectionStatus.UNAUTHENTICATED; + private final String name; + private Thread thread; private final int connectionId; private final int secret; // Separate the following from the threat ID generator, since PG connection IDs are maximum @@ -152,13 +154,12 @@ public class ConnectionHandler extends Thread { /** Constructor only for testing. */ @VisibleForTesting ConnectionHandler(ProxyServer server, Socket socket, Connection spannerConnection) { - super("ConnectionHandler-" + CONNECTION_HANDLER_ID_GENERATOR.incrementAndGet()); + this.name = "ConnectionHandler-" + CONNECTION_HANDLER_ID_GENERATOR.incrementAndGet(); this.server = server; this.socket = socket; this.secret = new SecureRandom().nextInt(); this.connectionId = incrementingConnectionId.incrementAndGet(); CONNECTION_HANDLERS.put(this.connectionId, this); - setDaemon(true); logger.log( Level.INFO, () -> @@ -168,6 +169,18 @@ public class ConnectionHandler extends Thread { this.spannerConnection = spannerConnection; } + String getName() { + return name; + } + + Thread getThread() { + return thread; + } + + void setThread(Thread thread) { + this.thread = thread; + } + void createSSLSocket() throws IOException { this.socket = ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(socket, null, true); @@ -630,7 +643,7 @@ public boolean cancelActiveStatement(int connectionId, int secret) { // otherwise) try { connectionToCancel.getSpannerConnection().cancel(); - connectionToCancel.interrupt(); + connectionToCancel.getThread().interrupt(); return true; } catch (Throwable ignore) { } diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java index 587995383..0498b0801 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java @@ -325,7 +325,9 @@ void createConnectionHandler(Socket socket) throws SocketException { socket.setTcpNoDelay(true); ConnectionHandler handler = new ConnectionHandler(this, socket); register(handler); - handler.start(); + Thread thread = Thread.startVirtualThread(handler); + handler.setThread(thread); + // handler.start(); } /** Returns an immutable copy of the current connection handlers at this server. */ From 1e242459fe5dbce129f7bcbe80bfdc51a60502c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sat, 16 Dec 2023 10:54:51 +0100 Subject: [PATCH 02/46] build: create jdk21 build profile --- .github/workflows/integration-emulator.yaml | 12 ++- .github/workflows/units.yaml | 29 +++++ pom.xml | 39 ++++++- .../spanner/pgadapter/ConnectionHandler.java | 4 + .../pgadapter/ConnectionThreadBuilder.java | 19 ++++ .../ConnectionThreadBuilderJdk8.java | 26 +++++ .../cloud/spanner/pgadapter/ProxyServer.java | 19 +++- .../ConnectionThreadBuilderJdk21.java | 28 +++++ .../pgadapter/ConnectionHandlerTest.java | 101 ++++++------------ .../metadata/OptionsMetadataTest.java | 45 ++++++-- 10 files changed, 235 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilder.java create mode 100644 src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java create mode 100644 src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java diff --git a/.github/workflows/integration-emulator.yaml b/.github/workflows/integration-emulator.yaml index 854857ec4..e2a365a74 100644 --- a/.github/workflows/integration-emulator.yaml +++ b/.github/workflows/integration-emulator.yaml @@ -40,7 +40,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: zulu - java-version: 17 + java-version: 21 - run: java -version - name: Setup Go uses: actions/setup-go@v4 @@ -65,7 +65,15 @@ jobs: run: | psql -h /pg -U postgres -c "CREATE DATABASE pgadapter" - name: Run integration tests - run: mvn verify -B -Dclirr.skip=true -DskipUnits=true -DskipITs=false + run: mvn clean verify -B -Dclirr.skip=true -DskipUnits=true -DskipITs=false + env: + POSTGRES_HOST: /pg + POSTGRES_PORT: 5432 + POSTGRES_USER: postgres + POSTGRES_DATABASE: pgadapter + SPANNER_EMULATOR_HOST: localhost:9010 + - name: Run integration tests using JDK21 build + run: mvn clean verify -Pjdk21 -B -Dclirr.skip=true -DskipUnits=true -DskipITs=false env: POSTGRES_HOST: /pg POSTGRES_PORT: 5432 diff --git a/.github/workflows/units.yaml b/.github/workflows/units.yaml index bd1bfc755..74d1fb28e 100644 --- a/.github/workflows/units.yaml +++ b/.github/workflows/units.yaml @@ -83,3 +83,32 @@ jobs: ruby-version: '3.0' bundler-cache: true - run: mvn -B test -Ptest-all -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn + ubuntu-jdk21: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: [21] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: ${{matrix.java}} + - run: java -version + - uses: actions/setup-go@v4 + with: + go-version: '1.20.7' + - run: go version + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + - run: python --version + - uses: actions/setup-node@v4 + with: + node-version: 16 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.0' + bundler-cache: true + - run: mvn -B test -Pjdk21 -Ptest-all -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn diff --git a/pom.xml b/pom.xml index bdb5901a3..e3caaca96 100644 --- a/pom.xml +++ b/pom.xml @@ -15,10 +15,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - 21 - 21 - 21 - 21 + 1.8 + 1.8 + 1.8 false ${skipTests} true @@ -233,6 +232,38 @@ + + jdk21 + + 21 + 21 + 21 + 21 + true + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + generate-sources + + add-source + + + + src/main/java21 + + + + + + + + shade 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 94af92435..824df469f 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java @@ -169,6 +169,10 @@ public class ConnectionHandler implements Runnable { this.spannerConnection = spannerConnection; } + void start() { + thread.start(); + } + String getName() { return name; } diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilder.java b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilder.java new file mode 100644 index 000000000..7a73815bc --- /dev/null +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilder.java @@ -0,0 +1,19 @@ +// Copyright 2020 Google LLC +// +// 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 com.google.cloud.spanner.pgadapter; + +interface ConnectionThreadBuilder { + Thread createConnectionHandlerThread(ConnectionHandler connectionHandler); +} diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java new file mode 100644 index 000000000..054bbc291 --- /dev/null +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java @@ -0,0 +1,26 @@ +// Copyright 2020 Google LLC +// +// 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 com.google.cloud.spanner.pgadapter; + +/** Jdk8-compatible thread builder for connections. Creates a standard platform thread. */ +class ConnectionThreadBuilderJdk8 implements ConnectionThreadBuilder { + + @Override + public Thread createConnectionHandlerThread(ConnectionHandler connectionHandler) { + Thread thread = new Thread(connectionHandler, connectionHandler.getName()); + thread.setDaemon(true); + return thread; + } +} diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java index 0498b0801..ca9ceeda5 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java @@ -80,6 +80,8 @@ public class ProxyServer extends AbstractApiService { private final ConcurrentLinkedQueue debugMessages = new ConcurrentLinkedQueue<>(); private final AtomicInteger debugMessageCount = new AtomicInteger(); + private final ConnectionThreadBuilder connectionThreadBuilder; + /** * Instantiates the ProxyServer from CLI-gathered metadata. * @@ -115,9 +117,22 @@ public ProxyServer( this.localPort = optionsMetadata.getProxyPort(); this.properties = properties; this.debugMode = optionsMetadata.isDebugMode(); + this.connectionThreadBuilder = createConnectionThreadBuilder(); addConnectionProperties(); } + static ConnectionThreadBuilder createConnectionThreadBuilder() { + try { + //noinspection unchecked + Class clazz = + (Class) + Class.forName("com.google.cloud.spanner.pgadapter.ConnectionThreadBuilderJdk21"); + return clazz.getConstructor().newInstance(); + } catch (Exception ignore) { + return new ConnectionThreadBuilderJdk8(); + } + } + private void addConnectionProperties() { for (Map.Entry entry : options.getPropertyMap().entrySet()) { properties.setProperty(entry.getKey(), entry.getValue()); @@ -325,9 +340,9 @@ void createConnectionHandler(Socket socket) throws SocketException { socket.setTcpNoDelay(true); ConnectionHandler handler = new ConnectionHandler(this, socket); register(handler); - Thread thread = Thread.startVirtualThread(handler); + Thread thread = connectionThreadBuilder.createConnectionHandlerThread(handler); handler.setThread(thread); - // handler.start(); + handler.start(); } /** Returns an immutable copy of the current connection handlers at this server. */ diff --git a/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java b/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java new file mode 100644 index 000000000..da20ef60e --- /dev/null +++ b/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java @@ -0,0 +1,28 @@ +// Copyright 2020 Google LLC +// +// 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 com.google.cloud.spanner.pgadapter; + +/** Jdk21-compatible thread builder for connections. Creates a virtual thread. */ +class ConnectionThreadBuilderJdk21 implements ConnectionThreadBuilder { + + public ConnectionThreadBuilderJdk21() {} + + @Override + public Thread createConnectionHandlerThread(ConnectionHandler connectionHandler) { + return Thread.ofVirtual() + .name(connectionHandler.getName()) + .unstarted(connectionHandler); + } +} diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ConnectionHandlerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ConnectionHandlerTest.java index 83d20e8f6..2b8b1103b 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/ConnectionHandlerTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/ConnectionHandlerTest.java @@ -70,8 +70,7 @@ public class ConnectionHandlerTest { public void testRegisterStatement() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); IntermediatePreparedStatement statement = mock(IntermediatePreparedStatement.class); ConnectionHandler connection = new ConnectionHandler(server, socket); @@ -88,8 +87,7 @@ public void testRegisterStatement() { public void testCloseUnknownStatement() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket); @@ -100,8 +98,7 @@ public void testCloseUnknownStatement() { public void testCloseAll() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); IntermediatePreparedStatement statement1 = mock(IntermediatePreparedStatement.class); IntermediatePreparedStatement statement2 = mock(IntermediatePreparedStatement.class); @@ -119,8 +116,7 @@ public void testCloseAll() { public void testTerminateClosesSocket() throws IOException { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket); @@ -133,8 +129,7 @@ public void testTerminateDoesNotCloseSocketTwice() throws IOException { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); when(socket.isClosed()).thenReturn(false, true); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket); @@ -150,8 +145,7 @@ public void testTerminateDoesNotCloseSocketTwice() throws IOException { public void testTerminateHandlesCloseError() throws IOException { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); // IOException should be handled internally in terminate(). doThrow(new IOException("test exception")).when(socket).close(); @@ -165,8 +159,7 @@ public void testTerminateHandlesCloseError() throws IOException { public void testTerminateClosesAllPortals() throws Exception { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); IntermediatePortalStatement portal1 = mock(IntermediatePortalStatement.class); IntermediatePortalStatement portal2 = mock(IntermediatePortalStatement.class); @@ -184,8 +177,7 @@ public void testTerminateClosesAllPortals() throws Exception { public void testTerminateIgnoresPortalCloseError() throws Exception { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); IntermediatePortalStatement portal1 = mock(IntermediatePortalStatement.class); IntermediatePortalStatement portal2 = mock(IntermediatePortalStatement.class); doThrow(new Exception("test")).when(portal2).close(); @@ -204,8 +196,7 @@ public void testTerminateIgnoresPortalCloseError() throws Exception { public void testGetPortal() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); IntermediatePortalStatement portal1 = mock(IntermediatePortalStatement.class); ConnectionHandler connection = new ConnectionHandler(server, socket); @@ -218,8 +209,7 @@ public void testGetPortal() { public void testGetUnknownPortal() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket); PGException exception = @@ -231,8 +221,7 @@ public void testGetUnknownPortal() { public void testClosePortal() throws Exception { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); IntermediatePortalStatement portal1 = mock(IntermediatePortalStatement.class); ConnectionHandler connection = new ConnectionHandler(server, socket); @@ -246,8 +235,7 @@ public void testClosePortal() throws Exception { public void testCloseUnknownPortal() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket); PGException exception = @@ -259,8 +247,7 @@ public void testCloseUnknownPortal() { public void testHandleMessages_NonFatalException() throws Exception { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); DataOutputStream dataOutputStream = new DataOutputStream(new ByteArrayOutputStream()); ConnectionMetadata connectionMetadata = mock(ConnectionMetadata.class); when(connectionMetadata.getOutputStream()).thenReturn(dataOutputStream); @@ -289,8 +276,7 @@ public ConnectionMetadata getConnectionMetadata() { public void testHandleMessages_FatalException() throws Exception { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); DataOutputStream dataOutputStream = new DataOutputStream(new ByteArrayOutputStream()); ConnectionMetadata connectionMetadata = mock(ConnectionMetadata.class); when(connectionMetadata.getOutputStream()).thenReturn(dataOutputStream); @@ -315,15 +301,14 @@ public ConnectionMetadata getConnectionMetadata() { public void testCancelActiveStatement() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); - when(address.getHostAddress()).thenReturn("address1"); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); Connection spannerConnection = mock(Connection.class); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket, mock(Connection.class)); ConnectionHandler connectionHandlerToCancel = new ConnectionHandler(server, socket, spannerConnection); + connectionHandlerToCancel.setThread(mock(Thread.class)); // Cancelling yourself is not allowed. assertFalse( @@ -354,8 +339,7 @@ public void testCancelActiveStatement() { public void testRestartConnectionWithSsl_CreatesSslSocket() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); AtomicBoolean calledCreateSSLSocket = new AtomicBoolean(); ConnectionHandler connection = new ConnectionHandler(server, socket) { @@ -373,8 +357,7 @@ void createSSLSocket() { public void testRestartConnectionWithSsl_SslSocketCreationFailureIsConvertedToPGException() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket) { @Override @@ -391,8 +374,7 @@ void createSSLSocket() throws IOException { testRestartConnectionWithSsl_SslSocketCreationFailureIsConvertedToPGExceptionWithMessage() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket) { @Override @@ -408,8 +390,7 @@ void createSSLSocket() throws IOException { public void testRestartConnectionWithSsl_SendsPGException() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket) { @@ -432,8 +413,7 @@ public ConnectionMetadata getConnectionMetadata() { public void testRestartConnectionWithSsl_IgnoresExceptionErrorHandling() { ProxyServer server = mock(ProxyServer.class); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connection = new ConnectionHandler(server, socket) { @@ -463,8 +443,7 @@ public void testCheckValidConnection_loopback() throws Exception { ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket) { @Override @@ -472,12 +451,7 @@ public ConnectionMetadata getConnectionMetadata() { return new ConnectionMetadata(mock(InputStream.class), mock(OutputStream.class)); } }; - - when(address.isLoopbackAddress()).thenReturn(true); assertTrue(connectionHandler.checkValidConnection(false)); - - when(address.isLoopbackAddress()).thenReturn(false); - assertFalse(connectionHandler.checkValidConnection(false)); } @Test @@ -486,8 +460,7 @@ public void testCheckValidConnection_anyLocalAddress() throws Exception { ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket) { @Override @@ -495,12 +468,7 @@ public ConnectionMetadata getConnectionMetadata() { return new ConnectionMetadata(mock(InputStream.class), mock(OutputStream.class)); } }; - - when(address.isAnyLocalAddress()).thenReturn(true); assertTrue(connectionHandler.checkValidConnection(false)); - - when(address.isAnyLocalAddress()).thenReturn(false); - assertFalse(connectionHandler.checkValidConnection(false)); } @Test @@ -509,8 +477,7 @@ public void testCheckValidConnection_ssl() throws Exception { ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket) { @Override @@ -520,11 +487,9 @@ public ConnectionMetadata getConnectionMetadata() { }; when(options.getSslMode()).thenReturn(SslMode.Enable); - when(address.isLoopbackAddress()).thenReturn(true); assertTrue(connectionHandler.checkValidConnection(false)); assertTrue(connectionHandler.checkValidConnection(true)); - when(address.isLoopbackAddress()).thenReturn(false); assertTrue(connectionHandler.checkValidConnection(true)); when(options.getSslMode()).thenReturn(SslMode.Require); @@ -539,8 +504,7 @@ public void testCheckValidConnection_localhostCheckDisabled() throws Exception { ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket); assertTrue(connectionHandler.checkValidConnection(false)); @@ -552,8 +516,7 @@ public void testMaybeDetermineWellKnownClient_remainsUnspecifiedForUnknownStatem ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket); when(options.shouldAutoDetectClient()).thenReturn(true); @@ -570,8 +533,7 @@ public void testMaybeDetermineWellKnownClient_changesFromUnspecifiedWithKnownSta ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket); when(options.shouldAutoDetectClient()).thenReturn(true); @@ -592,8 +554,7 @@ public void testMaybeDetermineWellKnownClient_respectsAutoDetectClientSetting() ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket); when(options.shouldAutoDetectClient()).thenReturn(false); @@ -614,8 +575,7 @@ public void testMaybeDetermineWellKnownClient_skipsClientSideStatements() { ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket); when(options.shouldAutoDetectClient()).thenReturn(true); @@ -640,8 +600,7 @@ public void testMaybeDetermineWellKnownClient_stopsSkippingParseMessagesAfter10M ProxyServer server = mock(ProxyServer.class); when(server.getOptions()).thenReturn(options); Socket socket = mock(Socket.class); - InetAddress address = mock(InetAddress.class); - when(socket.getInetAddress()).thenReturn(address); + when(socket.getInetAddress()).thenReturn(InetAddress.getLoopbackAddress()); ConnectionHandler connectionHandler = new ConnectionHandler(server, socket); when(options.shouldAutoDetectClient()).thenReturn(true); diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadataTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadataTest.java index 23fc5d99b..12bdaab4e 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadataTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadataTest.java @@ -122,12 +122,17 @@ public void testDatabaseName() { public void testBuildConnectionUrlWithFullPath() { assertEquals( "cloudspanner:/projects/test-project/instances/test-instance/databases/test-database;userAgent=pg-adapter;credentials=credentials.json", - new OptionsMetadata(new String[] {"-c", "credentials.json"}) + new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), + new String[] {"-c", "credentials.json"}) .buildConnectionURL( "projects/test-project/instances/test-instance/databases/test-database")); assertEquals( "cloudspanner:/projects/test-project/instances/test-instance/databases/test-database;userAgent=pg-adapter;credentials=credentials.json", new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), new String[] { "-p", "test-project", "-i", "test-instance", "-c", "credentials.json" }) @@ -139,7 +144,11 @@ public void testMissingProjectId() { SpannerException spannerException = assertThrows( SpannerException.class, - () -> new OptionsMetadata(new String[] {"-i", "my-instance", "-d", "my-db"})); + () -> + new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), + new String[] {"-i", "my-instance", "-d", "my-db"})); assertEquals(ErrorCode.INVALID_ARGUMENT, spannerException.getErrorCode()); } @@ -155,7 +164,10 @@ public void testMissingInstanceId() { @Test public void testBuildConnectionUrlWithDefaultProjectId() { OptionsMetadata useDefaultProjectIdOptions = - new OptionsMetadata(new String[] {"-i", "test-instance", "-c", "credentials.json"}) { + new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), + new String[] {"-i", "test-instance", "-c", "credentials.json"}) { @Override String getDefaultProjectId() { return "custom-test-project"; @@ -165,7 +177,10 @@ String getDefaultProjectId() { "cloudspanner:/projects/custom-test-project/instances/test-instance/databases/test-database;userAgent=pg-adapter;credentials=credentials.json", useDefaultProjectIdOptions.buildConnectionURL("test-database")); OptionsMetadata noProjectIdOptions = - new OptionsMetadata(new String[] {"-i", "test-instance", "-c", "credentials.json"}) { + new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), + new String[] {"-i", "test-instance", "-c", "credentials.json"}) { @Override String getDefaultProjectId() { return null; @@ -180,7 +195,10 @@ String getDefaultProjectId() { @Test public void testBuildConnectionUrlWithDefaultCredentials() { OptionsMetadata useDefaultCredentials = - new OptionsMetadata(new String[] {"-p", "test-project", "-i", "test-instance"}) { + new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), + new String[] {"-p", "test-project", "-i", "test-instance"}) { @Override void tryGetDefaultCredentials() {} }; @@ -188,7 +206,10 @@ void tryGetDefaultCredentials() {} "cloudspanner:/projects/test-project/instances/test-instance/databases/test-database;userAgent=pg-adapter", useDefaultCredentials.buildConnectionURL("test-database")); OptionsMetadata noDefaultCredentialsOptions = - new OptionsMetadata(new String[] {"-p", "test-project", "-i", "test-instance"}) { + new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), + new String[] {"-p", "test-project", "-i", "test-instance"}) { @Override void tryGetDefaultCredentials() throws IOException { throw new IOException("test exception"); @@ -206,7 +227,11 @@ public void testAuthenticationAndCredentialsNotAllowed() { SpannerException exception = assertThrows( SpannerException.class, - () -> new OptionsMetadata(new String[] {"-c", "credentials.json", "-a"})); + () -> + new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), + new String[] {"-c", "credentials.json", "-a"})); assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); } @@ -218,7 +243,11 @@ public void testShouldAuthenticate() { @Test public void testCredentials() { - OptionsMetadata options = new OptionsMetadata(new String[] {"-c", "credentials.json"}); + OptionsMetadata options = + new OptionsMetadata( + ImmutableMap.of(), + System.getProperty("os.name", ""), + new String[] {"-c", "credentials.json"}); assertFalse(options.shouldAuthenticate()); assertEquals("credentials.json", options.buildCredentialsFile()); } From ff86fc02eec26f897f85a7226c3ab1d1fdfe28b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sat, 16 Dec 2023 14:17:29 +0100 Subject: [PATCH 03/46] build: update Docker images to jdk21 --- benchmarks/ycsb/Dockerfile | 9 ++++++--- benchmarks/ycsb/run.sh | 3 +++ build/Dockerfile | 6 +++--- build/distroless/Dockerfile | 6 +++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/benchmarks/ycsb/Dockerfile b/benchmarks/ycsb/Dockerfile index 6833f3de8..23a465ffa 100644 --- a/benchmarks/ycsb/Dockerfile +++ b/benchmarks/ycsb/Dockerfile @@ -1,5 +1,5 @@ # BUILD -FROM maven:3.8.4-eclipse-temurin-17-alpine AS build +FROM maven:3-eclipse-temurin-21 AS build # Copy over build files to docker image. COPY LICENSE ./ COPY CONTRIBUTING.md ./ @@ -14,17 +14,20 @@ COPY java.header ./ RUN mvn dependency:go-offline # Build from source. -RUN mvn package -Passembly -DskipTests +RUN mvn package -Passembly -Pjdk21 -DskipTests # Docker image for the YCSB runner. FROM gcr.io/google.com/cloudsdktool/google-cloud-cli:slim RUN apt update && apt -y install postgresql-client -RUN apt -y install default-jre RUN apt -y install wget RUN apt -y install python +ADD https://download.java.net/java/GA/jdk21.0.1/415e3f918a1f4062a0074a2794853d0d/12/GPL/openjdk-21.0.1_linux-x64_bin.tar.gz /openjdk.tar.gz +RUN tar xvfz /openjdk.tar.gz +RUN sudo mv jdk-21.0.1 /usr/local/ + 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 diff --git a/benchmarks/ycsb/run.sh b/benchmarks/ycsb/run.sh index dc589fadf..709da9d59 100644 --- a/benchmarks/ycsb/run.sh +++ b/benchmarks/ycsb/run.sh @@ -1,6 +1,9 @@ #!/bin/bash set -euox pipefail +export JAVA_HOME=/usr/local/jdk-21.0.1 +export PATH=$PATH:$JAVA_HOME/bin + echo "Starting Task #${CLOUD_RUN_TASK_INDEX}, Attempt #${CLOUD_RUN_TASK_ATTEMPT}..." EXECUTED_AT=`date +"%Y-%m-%dT%T"` diff --git a/build/Dockerfile b/build/Dockerfile index 69ca38f85..66a69cc4e 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -2,7 +2,7 @@ # BUILD # ################################################################################ -FROM maven:3.8.6-eclipse-temurin-17 AS build +FROM maven:3-eclipse-temurin-21 AS build # Copy over build files to docker image. COPY LICENSE ./ @@ -18,13 +18,13 @@ COPY java.header ./ RUN mvn dependency:go-offline # Build from source. -RUN mvn package -Passembly -DskipTests +RUN mvn package -Passembly -Pjdk21 -DskipTests ################################################################################ # RELEASE # ################################################################################ -FROM eclipse-temurin:17-jre +FROM eclipse-temurin:21-jre COPY --from=build target/pgadapter /home/pgadapter COPY --from=build LICENSE /home/pgadapter/ diff --git a/build/distroless/Dockerfile b/build/distroless/Dockerfile index 885b94592..30bcf5132 100644 --- a/build/distroless/Dockerfile +++ b/build/distroless/Dockerfile @@ -2,7 +2,7 @@ # BUILD # ################################################################################ -FROM maven:3.8.6-eclipse-temurin-17 AS build +FROM maven:3-eclipse-temurin-21 AS build # Copy over build files to docker image. COPY LICENSE ./ @@ -18,13 +18,13 @@ COPY java.header ./ RUN mvn dependency:go-offline # Build from source. -RUN mvn package -Passembly -DskipTests +RUN mvn package -Passembly -Pjdk21 -DskipTests ################################################################################ # RELEASE # ################################################################################ -FROM gcr.io/distroless/java17-debian11:nonroot +FROM gcr.io/distroless/java21-debian12:nonroot COPY --from=build --chown=nonroot target/pgadapter /home/pgadapter COPY --from=build --chown=nonroot LICENSE /home/pgadapter/ From f9daa7fceaf6b25ae832f10ace74cf032cfaf7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sat, 16 Dec 2023 14:19:51 +0100 Subject: [PATCH 04/46] build: remove Python install --- benchmarks/ycsb/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/ycsb/Dockerfile b/benchmarks/ycsb/Dockerfile index 23a465ffa..a0d7f58da 100644 --- a/benchmarks/ycsb/Dockerfile +++ b/benchmarks/ycsb/Dockerfile @@ -22,7 +22,7 @@ FROM gcr.io/google.com/cloudsdktool/google-cloud-cli:slim RUN apt update && apt -y install postgresql-client RUN apt -y install wget -RUN apt -y install python +# RUN apt -y install python ADD https://download.java.net/java/GA/jdk21.0.1/415e3f918a1f4062a0074a2794853d0d/12/GPL/openjdk-21.0.1_linux-x64_bin.tar.gz /openjdk.tar.gz RUN tar xvfz /openjdk.tar.gz From bd02b1ff5ba30b1813afd8fda290ec664b3cd869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sat, 16 Dec 2023 14:28:13 +0100 Subject: [PATCH 05/46] build: keep JDK in download folder --- benchmarks/ycsb/Dockerfile | 1 - benchmarks/ycsb/run.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmarks/ycsb/Dockerfile b/benchmarks/ycsb/Dockerfile index a0d7f58da..d1f6dca0d 100644 --- a/benchmarks/ycsb/Dockerfile +++ b/benchmarks/ycsb/Dockerfile @@ -26,7 +26,6 @@ RUN apt -y install wget ADD https://download.java.net/java/GA/jdk21.0.1/415e3f918a1f4062a0074a2794853d0d/12/GPL/openjdk-21.0.1_linux-x64_bin.tar.gz /openjdk.tar.gz RUN tar xvfz /openjdk.tar.gz -RUN sudo mv jdk-21.0.1 /usr/local/ COPY --from=build target/pgadapter / diff --git a/benchmarks/ycsb/run.sh b/benchmarks/ycsb/run.sh index 709da9d59..ae8a87192 100644 --- a/benchmarks/ycsb/run.sh +++ b/benchmarks/ycsb/run.sh @@ -1,7 +1,7 @@ #!/bin/bash set -euox pipefail -export JAVA_HOME=/usr/local/jdk-21.0.1 +export JAVA_HOME=/jdk-21.0.1 export PATH=$PATH:$JAVA_HOME/bin echo "Starting Task #${CLOUD_RUN_TASK_INDEX}, Attempt #${CLOUD_RUN_TASK_ATTEMPT}..." From f1d4d2f4262c5fe50e3beb2e9de137e040260697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 17 Dec 2023 21:05:21 +0100 Subject: [PATCH 06/46] fix: install python 2 --- benchmarks/ycsb/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/ycsb/Dockerfile b/benchmarks/ycsb/Dockerfile index b9774f1ce..1c4e66195 100644 --- a/benchmarks/ycsb/Dockerfile +++ b/benchmarks/ycsb/Dockerfile @@ -24,7 +24,7 @@ FROM gcr.io/google.com/cloudsdktool/google-cloud-cli:455.0.0-slim RUN apt update && apt -y install postgresql-client RUN apt -y install wget -# RUN apt -y install python +RUN apt -y install python ADD https://download.java.net/java/GA/jdk21.0.1/415e3f918a1f4062a0074a2794853d0d/12/GPL/openjdk-21.0.1_linux-x64_bin.tar.gz /openjdk.tar.gz RUN tar xvfz /openjdk.tar.gz From 5bbc30753d935758ae9215379bf2d06a054628ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 25 Dec 2023 14:39:26 +0100 Subject: [PATCH 07/46] chore: update copyright year --- .../cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java | 2 +- .../cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java index 054bbc291..ba8b98c16 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java b/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java index da20ef60e..2ff9be9fb 100644 --- a/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java +++ b/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 056e94731fa73c4c38c63f1715b135851e2d1981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 29 Dec 2023 10:14:54 +0100 Subject: [PATCH 08/46] feat: use virtual threads for connection --- pom.xml | 1 + .../com/google/cloud/spanner/pgadapter/ConnectionHandler.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index d78d08ba0..478515ef7 100644 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,7 @@ com.google.cloud google-cloud-spanner + 6.55.1-SNAPSHOT io.opentelemetry 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 824df469f..d6ccb3616 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java @@ -63,6 +63,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.spanner.admin.database.v1.InstanceName; import com.google.spanner.v1.DatabaseName; import java.io.DataOutputStream; @@ -83,7 +84,9 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; @@ -195,6 +198,7 @@ public void connectToSpanner(String database, @Nullable Credentials credentials) OptionsMetadata options = getServer().getOptions(); String uri = buildConnectionURL(database, options, getServer().getProperties()); ConnectionOptions.Builder connectionOptionsBuilder = ConnectionOptions.newBuilder().setUri(uri); + connectionOptionsBuilder.setThreadFactory(Thread.ofVirtual().factory()); if (credentials != null) { connectionOptionsBuilder = ConnectionOptionsHelper.setCredentials(connectionOptionsBuilder, credentials); From a799cccec8253d50b480c7f4db73d64b2c1ba790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 29 Dec 2023 14:03:29 +0100 Subject: [PATCH 09/46] feat: automatically use virtual threads --- build/Dockerfile | 2 +- build/distroless/Dockerfile | 2 +- pom.xml | 33 --------------- .../spanner/pgadapter/ConnectionHandler.java | 20 ++++------ .../pgadapter/ConnectionThreadBuilder.java | 19 --------- .../ConnectionThreadBuilderJdk8.java | 26 ------------ .../cloud/spanner/pgadapter/ProxyServer.java | 40 ++++++++++++++----- .../pgadapter/utils/MutationWriter.java | 7 +++- .../ConnectionThreadBuilderJdk21.java | 28 ------------- 9 files changed, 44 insertions(+), 133 deletions(-) delete mode 100644 src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilder.java delete mode 100644 src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java delete mode 100644 src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java diff --git a/build/Dockerfile b/build/Dockerfile index 66a69cc4e..b71976504 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -18,7 +18,7 @@ COPY java.header ./ RUN mvn dependency:go-offline # Build from source. -RUN mvn package -Passembly -Pjdk21 -DskipTests +RUN mvn package -Passembly -DskipTests ################################################################################ # RELEASE # diff --git a/build/distroless/Dockerfile b/build/distroless/Dockerfile index 30bcf5132..d8be0894c 100644 --- a/build/distroless/Dockerfile +++ b/build/distroless/Dockerfile @@ -18,7 +18,7 @@ COPY java.header ./ RUN mvn dependency:go-offline # Build from source. -RUN mvn package -Passembly -Pjdk21 -DskipTests +RUN mvn package -Passembly -DskipTests ################################################################################ # RELEASE # diff --git a/pom.xml b/pom.xml index 478515ef7..1a8f97307 100644 --- a/pom.xml +++ b/pom.xml @@ -103,7 +103,6 @@ com.google.cloud google-cloud-spanner - 6.55.1-SNAPSHOT io.opentelemetry @@ -231,38 +230,6 @@ - - jdk21 - - 21 - 21 - 21 - 21 - true - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.0.0 - - - generate-sources - - add-source - - - - src/main/java21 - - - - - - - - shade 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 d6ccb3616..315cbd2b9 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java @@ -63,7 +63,6 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.spanner.admin.database.v1.InstanceName; import com.google.spanner.v1.DatabaseName; import java.io.DataOutputStream; @@ -84,9 +83,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; @@ -122,7 +119,6 @@ public class ConnectionHandler implements Runnable { private static final Map CONNECTION_HANDLERS = new ConcurrentHashMap<>(); private volatile ConnectionStatus status = ConnectionStatus.UNAUTHENTICATED; - private final String name; private Thread thread; private final int connectionId; private final int secret; @@ -157,18 +153,11 @@ public class ConnectionHandler implements Runnable { /** Constructor only for testing. */ @VisibleForTesting ConnectionHandler(ProxyServer server, Socket socket, Connection spannerConnection) { - this.name = "ConnectionHandler-" + CONNECTION_HANDLER_ID_GENERATOR.incrementAndGet(); this.server = server; this.socket = socket; this.secret = new SecureRandom().nextInt(); this.connectionId = incrementingConnectionId.incrementAndGet(); CONNECTION_HANDLERS.put(this.connectionId, this); - logger.log( - Level.INFO, - () -> - String.format( - "Connection handler with ID %s created for client %s", - getName(), socket.getInetAddress().getHostAddress())); this.spannerConnection = spannerConnection; } @@ -177,7 +166,7 @@ void start() { } String getName() { - return name; + return thread.getName(); } Thread getThread() { @@ -186,6 +175,12 @@ Thread getThread() { void setThread(Thread thread) { this.thread = thread; + logger.log( + Level.INFO, + () -> + String.format( + "Connection handler with ID %s created for client %s", + getName(), socket.getInetAddress().getHostAddress())); } void createSSLSocket() throws IOException { @@ -198,7 +193,6 @@ public void connectToSpanner(String database, @Nullable Credentials credentials) OptionsMetadata options = getServer().getOptions(); String uri = buildConnectionURL(database, options, getServer().getProperties()); ConnectionOptions.Builder connectionOptionsBuilder = ConnectionOptions.newBuilder().setUri(uri); - connectionOptionsBuilder.setThreadFactory(Thread.ofVirtual().factory()); if (credentials != null) { connectionOptionsBuilder = ConnectionOptionsHelper.setCredentials(connectionOptionsBuilder, credentials); diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilder.java b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilder.java deleted file mode 100644 index 7a73815bc..000000000 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilder.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Google LLC -// -// 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 com.google.cloud.spanner.pgadapter; - -interface ConnectionThreadBuilder { - Thread createConnectionHandlerThread(ConnectionHandler connectionHandler); -} diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java deleted file mode 100644 index ba8b98c16..000000000 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk8.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2023 Google LLC -// -// 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 com.google.cloud.spanner.pgadapter; - -/** Jdk8-compatible thread builder for connections. Creates a standard platform thread. */ -class ConnectionThreadBuilderJdk8 implements ConnectionThreadBuilder { - - @Override - public Thread createConnectionHandlerThread(ConnectionHandler connectionHandler) { - Thread thread = new Thread(connectionHandler, connectionHandler.getName()); - thread.setDaemon(true); - return thread; - } -} diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java index 647426a91..b81315384 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java @@ -26,10 +26,13 @@ import com.google.cloud.spanner.pgadapter.statements.IntermediateStatement; import com.google.cloud.spanner.pgadapter.wireprotocol.WireMessage; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.opentelemetry.api.OpenTelemetry; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; @@ -41,6 +44,7 @@ import java.util.Properties; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -81,7 +85,7 @@ public class ProxyServer extends AbstractApiService { private final ConcurrentLinkedQueue debugMessages = new ConcurrentLinkedQueue<>(); private final AtomicInteger debugMessageCount = new AtomicInteger(); - private final ConnectionThreadBuilder connectionThreadBuilder; + private final ThreadFactory threadFactory; /** * Instantiates the ProxyServer from CLI-gathered metadata. @@ -118,19 +122,32 @@ public ProxyServer( this.localPort = optionsMetadata.getProxyPort(); this.properties = properties; this.debugMode = optionsMetadata.isDebugMode(); - this.connectionThreadBuilder = createConnectionThreadBuilder(); + this.threadFactory = createThreadFactory("ConnectionHandler"); addConnectionProperties(); } - static ConnectionThreadBuilder createConnectionThreadBuilder() { + public static ThreadFactory createThreadFactory(String baseNameFormat) { + ThreadFactory virtualThreadFactory = tryCreateVirtualThreadFactory(baseNameFormat); + if (virtualThreadFactory != null) { + return virtualThreadFactory; + } + + return new ThreadFactoryBuilder().setDaemon(true).setNameFormat(baseNameFormat + "-%d").build(); + } + + static ThreadFactory tryCreateVirtualThreadFactory(String baseNameFormat) { try { - //noinspection unchecked - Class clazz = - (Class) - Class.forName("com.google.cloud.spanner.pgadapter.ConnectionThreadBuilderJdk21"); - return clazz.getConstructor().newInstance(); - } catch (Exception ignore) { - return new ConnectionThreadBuilderJdk8(); + Class threadBuilderClass = Class.forName("java.lang.Thread$Builder"); + Method ofVirtualMethod = Thread.class.getDeclaredMethod("ofVirtual"); + Object virtualBuilder = ofVirtualMethod.invoke(null); + Method nameMethod = threadBuilderClass.getDeclaredMethod("name", String.class, long.class); + virtualBuilder = nameMethod.invoke(virtualBuilder, baseNameFormat + "-", 0); + Method factoryMethod = threadBuilderClass.getDeclaredMethod("factory"); + return (ThreadFactory) factoryMethod.invoke(virtualBuilder); + } catch (ClassNotFoundException | NoSuchMethodException ignore) { + return null; + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); } } @@ -344,7 +361,8 @@ void createConnectionHandler(Socket socket) throws SocketException { socket.setTcpNoDelay(true); ConnectionHandler handler = new ConnectionHandler(this, socket); register(handler); - Thread thread = connectionThreadBuilder.createConnectionHandlerThread(handler); + // Thread thread = connectionThreadBuilder.createConnectionHandlerThread(handler); + Thread thread = threadFactory.newThread(handler); handler.setThread(thread); handler.start(); } diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java b/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java index 5ce27fdf6..cba8ae8a2 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java @@ -14,6 +14,8 @@ package com.google.cloud.spanner.pgadapter.utils; +import static com.google.cloud.spanner.pgadapter.ProxyServer.createThreadFactory; + import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; import com.google.api.core.InternalApi; @@ -64,6 +66,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -97,6 +100,8 @@ public enum CopyTransactionMode { private static final Logger logger = Logger.getLogger(MutationWriter.class.getName()); + private static final ThreadFactory THREAD_FACTORY = createThreadFactory("copy-worker"); + private final CopyTransactionMode transactionMode; private long rowCount; private final Connection connection; @@ -117,7 +122,7 @@ public enum CopyTransactionMode { private final AtomicBoolean rollback = new AtomicBoolean(false); private final CountDownLatch closedLatch = new CountDownLatch(1); private final ListeningExecutorService executorService = - MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); + MoreExecutors.listeningDecorator(Executors.newCachedThreadPool(THREAD_FACTORY)); private final Object lock = new Object(); diff --git a/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java b/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java deleted file mode 100644 index 2ff9be9fb..000000000 --- a/src/main/java21/com/google/cloud/spanner/pgadapter/ConnectionThreadBuilderJdk21.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2023 Google LLC -// -// 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 com.google.cloud.spanner.pgadapter; - -/** Jdk21-compatible thread builder for connections. Creates a virtual thread. */ -class ConnectionThreadBuilderJdk21 implements ConnectionThreadBuilder { - - public ConnectionThreadBuilderJdk21() {} - - @Override - public Thread createConnectionHandlerThread(ConnectionHandler connectionHandler) { - return Thread.ofVirtual() - .name(connectionHandler.getName()) - .unstarted(connectionHandler); - } -} From cab5a69536a2001b757b31b287558f246139a39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 29 Dec 2023 14:40:06 +0100 Subject: [PATCH 10/46] test: add Java 21 to test matrix --- .github/workflows/integration-emulator.yaml | 14 +++----- .github/workflows/integration.yaml | 4 ++- .github/workflows/units.yaml | 35 ++----------------- benchmarks/ycsb/Dockerfile | 2 +- .../cloud/spanner/pgadapter/ProxyServer.java | 4 +++ 5 files changed, 16 insertions(+), 43 deletions(-) diff --git a/.github/workflows/integration-emulator.yaml b/.github/workflows/integration-emulator.yaml index 09e4e9ba1..51e772f58 100644 --- a/.github/workflows/integration-emulator.yaml +++ b/.github/workflows/integration-emulator.yaml @@ -12,6 +12,10 @@ env: jobs: integration-test-emulator: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: [8, 11, 17, 21] # Add a PostgreSQL Docker container and the emulator. services: postgres: @@ -40,7 +44,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: zulu - java-version: 21 + java-version: ${{matrix.java}} - run: java -version - name: Setup Go uses: actions/setup-go@v5 @@ -72,11 +76,3 @@ jobs: POSTGRES_USER: postgres POSTGRES_DATABASE: pgadapter SPANNER_EMULATOR_HOST: localhost:9010 - - name: Run integration tests using JDK21 build - run: mvn clean verify -Pjdk21 -B -Dclirr.skip=true -DskipUnits=true -DskipITs=false - env: - POSTGRES_HOST: /pg - POSTGRES_PORT: 5432 - POSTGRES_USER: postgres - POSTGRES_DATABASE: pgadapter - SPANNER_EMULATOR_HOST: localhost:9010 diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 9c142a24c..0c5f931eb 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -80,7 +80,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: zulu - java-version: 17 + java-version: 21 - run: java -version - name: Setup Go uses: actions/setup-go@v5 @@ -106,6 +106,8 @@ jobs: psql -h /pg -U postgres -c "CREATE DATABASE pgadapter" - name: Run unit tests run: mvn test -B -Ptest-all + - name: Run unit tests without virtual threads + run: mvn test -B - name: Auth uses: google-github-actions/auth@v2 with: diff --git a/.github/workflows/units.yaml b/.github/workflows/units.yaml index 676da4103..f10c9349f 100644 --- a/.github/workflows/units.yaml +++ b/.github/workflows/units.yaml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - java: [8, 11, 17] + java: [8, 11, 17, 21] steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 @@ -40,7 +40,7 @@ jobs: strategy: fail-fast: false matrix: - java: [8, 11, 17] + java: [8, 11, 17, 21] steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 @@ -56,7 +56,7 @@ jobs: strategy: fail-fast: false matrix: - java: [8, 11, 17] + java: [8, 11, 17, 21] steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 @@ -83,32 +83,3 @@ jobs: ruby-version: '3.0' bundler-cache: true - run: mvn -B test -Ptest-all -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn - ubuntu-jdk21: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - java: [21] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - distribution: zulu - java-version: ${{matrix.java}} - - run: java -version - - uses: actions/setup-go@v4 - with: - go-version: '1.20.7' - - run: go version - - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - run: python --version - - uses: actions/setup-node@v4 - with: - node-version: 16 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.0' - bundler-cache: true - - run: mvn -B test -Pjdk21 -Ptest-all -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn diff --git a/benchmarks/ycsb/Dockerfile b/benchmarks/ycsb/Dockerfile index 1c4e66195..290aabe5e 100644 --- a/benchmarks/ycsb/Dockerfile +++ b/benchmarks/ycsb/Dockerfile @@ -14,7 +14,7 @@ COPY java.header ./ RUN mvn dependency:go-offline # Build from source. -RUN mvn package -Passembly -Pjdk21 -DskipTests +RUN mvn package -Passembly -DskipTests # Docker image for the YCSB runner. diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java index b81315384..e6cae0273 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java @@ -41,6 +41,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; @@ -136,6 +137,9 @@ public static ThreadFactory createThreadFactory(String baseNameFormat) { } static ThreadFactory tryCreateVirtualThreadFactory(String baseNameFormat) { + if (Objects.equals("true", System.getProperty("pgadapter.disable_virtual_threads"))) { + return null; + } try { Class threadBuilderClass = Class.forName("java.lang.Thread$Builder"); Method ofVirtualMethod = Thread.class.getDeclaredMethod("ofVirtual"); From 21675370d7d865205b21e515e42a58b6fea48fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 29 Dec 2023 15:00:45 +0100 Subject: [PATCH 11/46] test: run unit tests with virtual threads disabled --- .github/workflows/integration.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 0c5f931eb..feeda2746 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -107,7 +107,7 @@ jobs: - name: Run unit tests run: mvn test -B -Ptest-all - name: Run unit tests without virtual threads - run: mvn test -B + run: mvn test -B -Dpgadapter.disable_virtual_threads=true - name: Auth uses: google-github-actions/auth@v2 with: From c5afcd683bc2edd4f9a21461459b0f62218aa2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 29 Dec 2023 15:46:54 +0100 Subject: [PATCH 12/46] test: reduce matrix for emulator --- .github/workflows/integration-emulator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-emulator.yaml b/.github/workflows/integration-emulator.yaml index 51e772f58..355e78532 100644 --- a/.github/workflows/integration-emulator.yaml +++ b/.github/workflows/integration-emulator.yaml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - java: [8, 11, 17, 21] + java: [11, 21] # Add a PostgreSQL Docker container and the emulator. services: postgres: From e505c183c4e64d5d00538109028a083594a1bdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 29 Dec 2023 15:50:05 +0100 Subject: [PATCH 13/46] test: run units with Java 11 also --- .github/workflows/integration.yaml | 22 ++++++++++++------- .../cloud/spanner/pgadapter/ProxyServer.java | 4 ---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index feeda2746..c300557bd 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -76,12 +76,6 @@ jobs: run: | echo "EXCLUDED_INTEGRATION_TESTS=SlowTest" >> $GITHUB_ENV - uses: actions/checkout@v4 - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: zulu - java-version: 21 - - run: java -version - name: Setup Go uses: actions/setup-go@v5 with: @@ -104,10 +98,22 @@ jobs: - name: Create PostgreSQL test database run: | psql -h /pg -U postgres -c "CREATE DATABASE pgadapter" + - name: Setup Java 11 + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 11 + - run: java -version + - name: Run unit tests without virtual threads + run: mvn test -B + - name: Setup Java 21 + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 21 + - run: java -version - name: Run unit tests run: mvn test -B -Ptest-all - - name: Run unit tests without virtual threads - run: mvn test -B -Dpgadapter.disable_virtual_threads=true - name: Auth uses: google-github-actions/auth@v2 with: diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java index e6cae0273..b81315384 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java @@ -41,7 +41,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Properties; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; @@ -137,9 +136,6 @@ public static ThreadFactory createThreadFactory(String baseNameFormat) { } static ThreadFactory tryCreateVirtualThreadFactory(String baseNameFormat) { - if (Objects.equals("true", System.getProperty("pgadapter.disable_virtual_threads"))) { - return null; - } try { Class threadBuilderClass = Class.forName("java.lang.Thread$Builder"); Method ofVirtualMethod = Thread.class.getDeclaredMethod("ofVirtual"); From e64b7d11aa123dcc377bb9bc54758f476b9b579c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sat, 30 Dec 2023 19:43:43 +0100 Subject: [PATCH 14/46] test: test with virtual threads in connection api --- pom.xml | 1 + .../cloud/spanner/pgadapter/ProxyServer.java | 21 +- .../pgadapter/metadata/OptionsMetadata.java | 5 + .../pgadapter/AbstractMockServerTest.java | 2 + .../pgadapter/BenchmarkMockServerTest.java | 208 ++++++++++++++++++ .../metadata/TestOptionsMetadataBuilder.java | 6 + 6 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/google/cloud/spanner/pgadapter/BenchmarkMockServerTest.java diff --git a/pom.xml b/pom.xml index 1a8f97307..2d821eceb 100644 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,7 @@ com.google.cloud google-cloud-spanner + 6.55.1-SNAPSHOT io.opentelemetry diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java index b81315384..1be882884 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java @@ -44,6 +44,8 @@ import java.util.Properties; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -87,6 +89,8 @@ public class ProxyServer extends AbstractApiService { private final ThreadFactory threadFactory; + private final ExecutorService createConnectionHandlerExecutor = Executors.newSingleThreadExecutor(); + /** * Instantiates the ProxyServer from CLI-gathered metadata. * @@ -136,6 +140,10 @@ public static ThreadFactory createThreadFactory(String baseNameFormat) { } static ThreadFactory tryCreateVirtualThreadFactory(String baseNameFormat) { + if ("true".equals(System.getProperty("pgadapter.disable_virtual_threads"))) { + return null; + } + try { Class threadBuilderClass = Class.forName("java.lang.Thread$Builder"); Method ofVirtualMethod = Thread.class.getDeclaredMethod("ofVirtual"); @@ -252,6 +260,7 @@ protected void doStop() { SpannerPool.closeSpannerPool(); } catch (Throwable ignore) { } + createConnectionHandlerExecutor.shutdown(); notifyStopped(); } @@ -328,7 +337,16 @@ void runServer( awaitRunning(); try { while (isRunning()) { - createConnectionHandler(serverSocket.accept()); + Socket socket = serverSocket.accept(); + // Hand off the creation of the connection handler to a worker thread to ensure that we + // continue to listen for new incoming connection requests as quickly as possible. + createConnectionHandlerExecutor.submit(() -> { + try { + createConnectionHandler(socket); + } catch (SocketException socketException) { + logger.log(Level.WARNING, () -> String.format("Failed to create connection on socket %s: %s.", socket, socketException)); + } + }); } } catch (SocketException e) { // This is a normal exception, as this will occur when Server#stopServer() is called. @@ -361,7 +379,6 @@ void createConnectionHandler(Socket socket) throws SocketException { socket.setTcpNoDelay(true); ConnectionHandler handler = new ConnectionHandler(this, socket); register(handler); - // Thread thread = connectionThreadBuilder.createConnectionHandlerThread(handler); Thread thread = threadFactory.newThread(handler); handler.setThread(thread); handler.start(); diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java index e96faa5a4..8dcc56de9 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java @@ -313,6 +313,11 @@ Builder enableDebugMode() { return this; } + Builder disableDebugMode() { + this.debugMode = false; + return this; + } + Builder setEndpoint(String endpoint) { this.endpoint = endpoint; return this; diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java index 144dd939d..1436587d8 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java @@ -14,6 +14,7 @@ package com.google.cloud.spanner.pgadapter; +import static com.google.cloud.spanner.connection.ThreadFactoryUtil.createVirtualOrDaemonThreadFactory; import static com.google.cloud.spanner.pgadapter.statements.PgCatalog.PG_TYPE_CTE_EMULATED; import static com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgNamespace.PG_NAMESPACE_CTE; import static org.junit.Assert.assertNotNull; @@ -75,6 +76,7 @@ import java.util.ArrayList; import java.util.Base64; import java.util.List; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.logging.Logger; diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/BenchmarkMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/BenchmarkMockServerTest.java new file mode 100644 index 000000000..612ac19c4 --- /dev/null +++ b/src/test/java/com/google/cloud/spanner/pgadapter/BenchmarkMockServerTest.java @@ -0,0 +1,208 @@ +// Copyright 2022 Google LLC +// +// 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 com.google.cloud.spanner.pgadapter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.connection.RandomResultSetGenerator; +import com.google.cloud.spanner.pgadapter.metadata.TestOptionsMetadataBuilder; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import io.opentelemetry.api.OpenTelemetry; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.logging.LogManager; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class BenchmarkMockServerTest extends AbstractMockServerTest { + static { + try { + LogManager.getLogManager().readConfiguration(BenchmarkMockServerTest.class.getResourceAsStream("/logging.properties")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static class BenchmarkResult { + final String name; + final int parallelism; + final boolean disableVirtualThreads; + final Duration avg; + final Duration p50; + final Duration p90; + final Duration p95; + final Duration p99; + + BenchmarkResult(String name, int parallelism, boolean disableVirtualThreads, ConcurrentLinkedQueue durations) { + this.name = name; + this.parallelism = parallelism; + this.disableVirtualThreads = disableVirtualThreads; + List list = new ArrayList<>(durations); + list.sort(Duration::compareTo); + avg = list.stream().reduce(Duration::plus).orElse(Duration.ZERO).dividedBy(list.size()); + p50 = list.get(durations.size() / 2); + p90 = list.get(durations.size() * 90 / 100); + p95 = list.get(durations.size() * 95 / 100); + p99 = list.get(durations.size() * 99 / 100); + } + + @Override + public String toString() { + return String.format("name: %s\nparallelism: %d\nvirtual: %s\navg: %s\np50: %s\np90: %s\np95: %s\np99: %s\n\n", name, parallelism, !disableVirtualThreads, avg, p50, p90, p95, p99); + } + } + + @Parameter(0) + public boolean disableVirtualThreads; + + @Parameter(1) + public int parallelism; + + @Parameters(name = "disableVirtualThreads = {0}, parallelism = {1}") + public static List parameters() { + ImmutableList.Builder builder = ImmutableList.builder(); + for (int parallelism : new int[] {1, 2, 4, 8, 16, 32, 50, 100, 150, 200, 300, 400}) { + for (boolean disableVirtualThreads : new boolean[] {false, true}) { + builder.add(new Object[]{disableVirtualThreads, parallelism}); + } + } + return builder.build(); + } + + private static final int NUM_ITERATIONS = 10000; + + private static final int NUM_RESULTS = 1000; + + private static final List IDENTIFIERS = new ArrayList<>(NUM_RESULTS); + + private static final String SELECT_SINGLE_ROW_SQL = "select * from random where id=?"; + + + @BeforeClass + public static void setupBenchmarkServer() throws Exception { + assumeTrue(System.getProperty("pgadapter.benchmark") == null); + + doStartMockSpannerAndPgAdapterServers( + createMockSpannerThatReturnsOneQueryPartition(), + "d", + TestOptionsMetadataBuilder::disableDebugMode, + OpenTelemetry.noop()); + + mockSpanner.setExecuteStreamingSqlExecutionTime(SimulatedExecutionTime.ofMinimumAndRandomTime(3, 3)); + setupResults(); + } + + private static void setupResults() { + String spannerSql = SELECT_SINGLE_ROW_SQL.replace("?", "$1"); + RandomResultSetGenerator generator = new RandomResultSetGenerator(1, Dialect.POSTGRESQL); + + for (int i=0; i < NUM_RESULTS; i++) { + String id = UUID.randomUUID().toString(); + Statement spannerStatement = Statement.newBuilder(spannerSql).bind("p1").to(id).build(); + mockSpanner.putStatementResult(StatementResult.query(spannerStatement, generator.generate())); + IDENTIFIERS.add(id); + } + } + + @Before + public void restartServer() { + pgServer.stopServer(); + pgServer.awaitTerminated(); + + mockSpanner.clearRequests(); + + if (disableVirtualThreads) { + System.setProperty("pgadapter.disable_virtual_threads", "true"); + } else { + System.clearProperty("pgadapter.disable_virtual_threads"); + } + pgServer = new ProxyServer(pgServer.getOptions(), pgServer.getOpenTelemetry()); + pgServer.startServer(); + pgServer.awaitRunning(); + } + + private String createUrl() { + return String.format( + "jdbc:postgresql://localhost:%d/db", + pgServer.getLocalPort()); + } + + @Test + public void testSelectOneRowAutoCommit() throws Exception { + ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); + + ExecutorService executor = Executors.newFixedThreadPool(parallelism); + for (int task = 0; task < parallelism; task++) { + executor.submit(() -> runQuery(NUM_ITERATIONS, durations)); + } + executor.shutdown(); + assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); + assertEquals(parallelism * NUM_ITERATIONS, durations.size()); + BenchmarkResult result = new BenchmarkResult("SelectOneRowAutoCommit", parallelism, disableVirtualThreads, durations); + System.out.print(result); + } + + private void runQuery(int iterations, ConcurrentLinkedQueue durations) { + try (Connection connection = DriverManager.getConnection(createUrl())) { + for (int n = 0; n < iterations; n++) { + String id = IDENTIFIERS.get(ThreadLocalRandom.current().nextInt(IDENTIFIERS.size())); + Stopwatch watch = Stopwatch.createStarted(); + try (PreparedStatement statement = connection.prepareStatement(SELECT_SINGLE_ROW_SQL)) { + statement.setString(1, id); + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + for (int col = 1; col <= resultSet.getMetaData().getColumnCount(); col++) { + assertEquals(resultSet.getString(col), + resultSet.getString(resultSet.getMetaData().getColumnLabel(col))); + } + } + } + } + durations.add(watch.elapsed()); + } + } catch (SQLException exception) { + exception.printStackTrace(); + throw new RuntimeException(exception); + } + } + + +} diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/metadata/TestOptionsMetadataBuilder.java b/src/test/java/com/google/cloud/spanner/pgadapter/metadata/TestOptionsMetadataBuilder.java index 843c890f5..a3a4138ef 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/metadata/TestOptionsMetadataBuilder.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/metadata/TestOptionsMetadataBuilder.java @@ -22,6 +22,12 @@ public TestOptionsMetadataBuilder enableDebugMode() { return this; } + @Override + public TestOptionsMetadataBuilder disableDebugMode() { + super.disableDebugMode(); + return this; + } + @Override public TestOptionsMetadataBuilder setEndpoint(String endpoint) { super.setEndpoint(endpoint); From 6296602c78a5ecbdcf065fee6f693e4348d07574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 31 Dec 2023 13:53:00 +0100 Subject: [PATCH 15/46] test: add benchmarks --- benchmarks/client-comparisons/.gitignore | 1 + benchmarks/client-comparisons/README.md | 65 ++++++ benchmarks/client-comparisons/pom.xml | 64 ++++++ .../benchmark/BenchmarkApplication.java | 134 ++++++++++++ .../pgadapter/benchmark/BenchmarkResult.java | 36 ++++ .../pgadapter/benchmark/BenchmarkRunner.java | 116 +++++++++++ .../benchmark/LastNameGenerator.java | 22 ++ .../pgadapter/benchmark/SchemaService.java | 53 +++++ .../cloud/pgadapter/benchmark/Statistics.java | 85 ++++++++ .../config/BenchmarkConfiguration.java | 100 +++++++++ .../config/PGAdapterConfiguration.java | 88 ++++++++ .../config/SpannerConfiguration.java | 36 ++++ .../dataloader/AbstractRowProducer.java | 196 ++++++++++++++++++ .../dataloader/AllTypesRowProducer.java | 57 +++++ .../benchmark/dataloader/DataLoadStatus.java | 55 +++++ .../benchmark/dataloader/DataLoader.java | 113 ++++++++++ .../src/main/resources/application.properties | 71 +++++++ .../src/main/resources/drop_schema.sql | 6 + .../src/main/resources/schema.sql | 38 ++++ .../cloud/spanner/pgadapter/ProxyServer.java | 23 +- .../pgadapter/AbstractMockServerTest.java | 2 - .../pgadapter/BenchmarkMockServerTest.java | 38 ++-- 22 files changed, 1374 insertions(+), 25 deletions(-) create mode 100644 benchmarks/client-comparisons/.gitignore create mode 100644 benchmarks/client-comparisons/README.md create mode 100644 benchmarks/client-comparisons/pom.xml create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkResult.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/LastNameGenerator.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SchemaService.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/PGAdapterConfiguration.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/AbstractRowProducer.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/AllTypesRowProducer.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/DataLoadStatus.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/DataLoader.java create mode 100644 benchmarks/client-comparisons/src/main/resources/application.properties create mode 100644 benchmarks/client-comparisons/src/main/resources/drop_schema.sql create mode 100644 benchmarks/client-comparisons/src/main/resources/schema.sql diff --git a/benchmarks/client-comparisons/.gitignore b/benchmarks/client-comparisons/.gitignore new file mode 100644 index 000000000..cd26be4b7 --- /dev/null +++ b/benchmarks/client-comparisons/.gitignore @@ -0,0 +1 @@ +tpcc-log.log diff --git a/benchmarks/client-comparisons/README.md b/benchmarks/client-comparisons/README.md new file mode 100644 index 000000000..44382c193 --- /dev/null +++ b/benchmarks/client-comparisons/README.md @@ -0,0 +1,65 @@ +# TPC-C Benchmark Application for PGAdapter and Cloud Spanner + +This application implements the standard TPC-C benchmark and runs it against PGAdapter and a Cloud +Spanner database. It is a Java Spring Boot application. See [src/main/resources/application.properties](src/main/resources/application.properties) +for a full list of configuration options. + +## Running + +The application automatically starts PGAdapter together with the benchmark application. Supply the +Cloud Spanner database and your credentials using the below command. The `tpcc.benchmark-threads` +argument determines the number of threads that will execute test transactions in parallel. + +```shell +mvn spring-boot:run -Dspring-boot.run.arguments=" + --tpcc.benchmark-threads=8 + --spanner.project=my-project + --spanner.instance=my-instance + --spanner.database=my-database + --pgadapter.credentials=/path/to/credentials.json + " +``` + + +Load data: + +```shell +mvn spring-boot:run -Dspring-boot.run.arguments=" + --tpcc.benchmark-duration=PT600s + --tpcc.warehouses=10 + --tpcc.benchmark-threads=1 + --tpcc.load-data=true + --tpcc.truncate-before-load=false + --tpcc.run-benchmark=false + --tpcc.use-read-only-transactions=false + --tpcc.lock-scanned-ranges=false + --spanner.project=appdev-soda-spanner-staging + --spanner.instance=knut-test-ycsb + --spanner.database=tpcc2 + --pgadapter.credentials=/home/loite/appdev-soda-spanner-staging.json + --pgadapter.disable-internal-retries=false + " +``` + + +Run benchmark: + +```shell +mvn spring-boot:run -Dspring-boot.run.arguments=" + --tpcc.benchmark-duration=PT600s + --tpcc.warehouses=10 + --tpcc.benchmark-threads=1 + --tpcc.load-data=false + --tpcc.truncate-before-load=false + --tpcc.run-benchmark=true + --tpcc.use-read-only-transactions=true + --tpcc.lock-scanned-ranges=false + --spanner.project=appdev-soda-spanner-staging + --spanner.instance=knut-test-ycsb + --spanner.database=tpcc2 + --pgadapter.enable-open-telemetry=true + --pgadatper.open-telemetry-sample-ratio=1.0 + --pgadapter.credentials=/home/loite/appdev-soda-spanner-staging.json + --pgadapter.disable-internal-retries=true + " +``` diff --git a/benchmarks/client-comparisons/pom.xml b/benchmarks/client-comparisons/pom.xml new file mode 100644 index 000000000..cbd826b78 --- /dev/null +++ b/benchmarks/client-comparisons/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.5 + + + com.google.cloud.spanner + pgadapter-client-benchmarks + 0.0.1-SNAPSHOT + PGAdapter Client Benchmarks + Various Benchmarks for PGAdapter and other Cloud Spanner clients + + 17 + + + + org.springframework.boot + spring-boot-starter + + + + org.postgresql + postgresql + 42.7.1 + + + + com.google.cloud + google-cloud-spanner-pgadapter + 0.27.2-SNAPSHOT + + + org.apache.commons + commons-lang3 + 3.14.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + com.spotify.fmt + fmt-maven-plugin + 2.21.1 + + + + format + + + + + + + + diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java new file mode 100644 index 000000000..8a6be98ca --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -0,0 +1,134 @@ +package com.google.cloud.pgadapter.benchmark; + +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import com.google.cloud.pgadapter.benchmark.config.PGAdapterConfiguration; +import com.google.cloud.pgadapter.benchmark.config.SpannerConfiguration; +import com.google.cloud.pgadapter.benchmark.dataloader.DataLoadStatus; +import com.google.cloud.pgadapter.benchmark.dataloader.DataLoader; +import com.google.cloud.spanner.pgadapter.ProxyServer; +import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata; +import com.google.common.base.Stopwatch; +import com.google.common.base.Strings; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BenchmarkApplication implements CommandLineRunner { + private static final Logger LOG = LoggerFactory.getLogger(BenchmarkApplication.class); + + public static void main(String[] args) { + try { + SpringApplication.run(BenchmarkApplication.class, args); + } catch (Throwable exception) { + LOG.error("Failed to start application", exception); + //noinspection CallToPrintStackTrace + exception.printStackTrace(); + } + } + + private final SpannerConfiguration spannerConfiguration; + + private final PGAdapterConfiguration pgAdapterConfiguration; + + private final BenchmarkConfiguration benchmarkConfiguration; + + public BenchmarkApplication( + SpannerConfiguration spannerConfiguration, + PGAdapterConfiguration pgAdapterConfiguration, + BenchmarkConfiguration benchmarkConfiguration) { + this.spannerConfiguration = spannerConfiguration; + this.pgAdapterConfiguration = pgAdapterConfiguration; + this.benchmarkConfiguration = benchmarkConfiguration; + } + + @Override + public void run(String... args) throws Exception { + ProxyServer server = pgAdapterConfiguration.isInProcess() ? startPGAdapter() : null; + String connectionUrl = + server == null + ? pgAdapterConfiguration.getConnectionUrl() + : String.format( + "jdbc:postgresql://localhost:%d/%s", + server.getLocalPort(), spannerConfiguration.getDatabase()); + try { + SchemaService schemaService = new SchemaService(connectionUrl); + schemaService.createSchema(); + + if (benchmarkConfiguration.isLoadData()) { + LOG.info("Starting data load"); + ExecutorService executor = Executors.newSingleThreadExecutor(); + DataLoadStatus status = new DataLoadStatus(benchmarkConfiguration); + Future loadDataFuture = executor.submit(() -> loadData(status, connectionUrl)); + executor.shutdown(); + Stopwatch watch = Stopwatch.createStarted(); + while (!loadDataFuture.isDone()) { + //noinspection BusyWait + Thread.sleep(1_000L); + status.print(watch.elapsed()); + } + System.out.printf("Finished loading %d rows\n", loadDataFuture.get()); + } + + if (benchmarkConfiguration.isRunBenchmark()) { + LOG.info("Starting benchmark"); + Statistics statistics = new Statistics(benchmarkConfiguration); + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(new BenchmarkRunner(statistics, connectionUrl, benchmarkConfiguration)); + executor.shutdown(); + + Stopwatch watch = Stopwatch.createStarted(); + while (!executor.isTerminated() + && watch.elapsed().compareTo(benchmarkConfiguration.getBenchmarkDuration()) <= 0) { + //noinspection BusyWait + Thread.sleep(1_000L); + statistics.print(watch.elapsed()); + } + executor.shutdownNow(); + if (!executor.awaitTermination(60L, TimeUnit.SECONDS)) { + throw new TimeoutException("Timed out while waiting for benchmark runner to shut down"); + } + } + } finally { + if (server != null) { + server.stopServer(); + server.awaitTerminated(); + } + } + } + + private long loadData(DataLoadStatus status, String connectionUrl) throws Exception { + try (DataLoader loader = new DataLoader(status, connectionUrl, benchmarkConfiguration)) { + return loader.loadData(); + } + } + + private ProxyServer startPGAdapter() { + OptionsMetadata.Builder builder = + OptionsMetadata.newBuilder() + .setProject(spannerConfiguration.getProject()) + .setInstance(spannerConfiguration.getInstance()) + .setDatabase(spannerConfiguration.getDatabase()) + .disableUnixDomainSockets(); + if (pgAdapterConfiguration.isEnableOpenTelemetry()) { + builder + .setEnableOpenTelemetry() + .setOpenTelemetryTraceRatio(pgAdapterConfiguration.getOpenTelemetrySampleRate()); + } + if (!Strings.isNullOrEmpty(pgAdapterConfiguration.getCredentials())) { + builder.setCredentialsFile(pgAdapterConfiguration.getCredentials()); + } + ProxyServer server = new ProxyServer(builder.build()); + server.startServer(); + server.awaitRunning(); + + return server; + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkResult.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkResult.java new file mode 100644 index 000000000..de3a67e7b --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkResult.java @@ -0,0 +1,36 @@ +package com.google.cloud.pgadapter.benchmark; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +class BenchmarkResult { + + final String name; + final int parallelism; + final Duration avg; + final Duration p50; + final Duration p90; + final Duration p95; + final Duration p99; + + BenchmarkResult(String name, int parallelism, ConcurrentLinkedQueue durations) { + this.name = name; + this.parallelism = parallelism; + List list = new ArrayList<>(durations); + list.sort(Duration::compareTo); + avg = list.stream().reduce(Duration::plus).orElse(Duration.ZERO).dividedBy(list.size()); + p50 = list.get(durations.size() / 2); + p90 = list.get(durations.size() * 90 / 100); + p95 = list.get(durations.size() * 95 / 100); + p99 = list.get(durations.size() * 99 / 100); + } + + @Override + public String toString() { + return String.format( + "name: %s\nparallelism: %d\navg: %s\np50: %s\np90: %s\np95: %s\np99: %s\n\n", + name, parallelism, avg, p50, p90, p95, p99); + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java new file mode 100644 index 000000000..5b6ffeae0 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java @@ -0,0 +1,116 @@ +package com.google.cloud.pgadapter.benchmark; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import com.google.common.base.Stopwatch; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class BenchmarkRunner implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(BenchmarkRunner.class); + + private final Statistics statistics; + + private final String connectionUrl; + + private final BenchmarkConfiguration benchmarkConfiguration; + + private List identifiers; + + private boolean failed; + + BenchmarkRunner( + Statistics statistics, String connectionUrl, BenchmarkConfiguration benchmarkConfiguration) { + this.statistics = statistics; + this.connectionUrl = connectionUrl; + this.benchmarkConfiguration = benchmarkConfiguration; + } + + @Override + public void run() { + try (Connection connection = DriverManager.getConnection(connectionUrl)) { + this.identifiers = loadIdentifiers(connection); + + benchmarkSelectOneRowAutoCommit(1); + benchmarkSelectOneRowAutoCommit(2); + + } catch (Throwable throwable) { + LOG.error("Benchmark runner failed", throwable); + failed = true; + } + } + + private List loadIdentifiers(Connection connection) throws SQLException { + List result = new ArrayList<>(benchmarkConfiguration.getRecordCount()); + try (ResultSet resultSet = + connection.createStatement().executeQuery("select id from benchmark_all_types")) { + while (resultSet.next()) { + result.add(resultSet.getString(1)); + } + } + return result; + } + + private void benchmarkSelectOneRowAutoCommit(int parallelism) throws Exception { + int totalOperations = parallelism * benchmarkConfiguration.getIterations(); + statistics.reset("SelectOneRowAutoCommit", parallelism, totalOperations); + + ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); + + ExecutorService executor = Executors.newFixedThreadPool(parallelism); + for (int task = 0; task < parallelism; task++) { + executor.submit(() -> runQuery(benchmarkConfiguration.getIterations(), durations)); + } + executor.shutdown(); + assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); + assertEquals(totalOperations, durations.size()); + BenchmarkResult result = new BenchmarkResult("SelectOneRowAutoCommit", parallelism, durations); + System.out.print(result); + } + + private void runQuery(int iterations, ConcurrentLinkedQueue durations) { + try (Connection connection = DriverManager.getConnection(connectionUrl)) { + for (int n = 0; n < iterations; n++) { + String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); + Stopwatch watch = Stopwatch.createStarted(); + try (PreparedStatement statement = + connection.prepareStatement("select * from benchmark_all_types where id=?")) { + statement.setString(1, id); + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + for (int col = 1; col <= resultSet.getMetaData().getColumnCount(); col++) { + assertEquals( + resultSet.getString(col), + resultSet.getString(resultSet.getMetaData().getColumnLabel(col))); + } + } + } + } + statistics.incOperations(); + durations.add(watch.elapsed()); + } + } catch (SQLException exception) { + exception.printStackTrace(); + throw new RuntimeException(exception); + } + } + + public boolean isFailed() { + return failed; + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/LastNameGenerator.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/LastNameGenerator.java new file mode 100644 index 000000000..e3fc11395 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/LastNameGenerator.java @@ -0,0 +1,22 @@ +package com.google.cloud.pgadapter.benchmark; + +import com.google.common.collect.ImmutableList; +import java.util.Random; + +public class LastNameGenerator { + private static final ImmutableList LAST_NAME_PARTS = + ImmutableList.of( + "BAR", "OUGHT", "ABLE", "PRI", "PRES", "ESE", "ANTI", "CALLY", "ATION", "EING"); + + public static String generateLastName(Random random, long rowIndex) { + int row; + if (rowIndex < 1000L) { + row = (int) rowIndex; + } else { + row = random.nextInt(1000); + } + return LAST_NAME_PARTS.get(row / 100) + + LAST_NAME_PARTS.get((row / 10) % 10) + + LAST_NAME_PARTS.get(row % 10); + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SchemaService.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SchemaService.java new file mode 100644 index 000000000..4051b8837 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SchemaService.java @@ -0,0 +1,53 @@ +package com.google.cloud.pgadapter.benchmark; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class SchemaService { + private static final Logger LOG = LoggerFactory.getLogger(SchemaService.class); + + private final String connectionUrl; + + SchemaService(String connectionUrl) { + this.connectionUrl = connectionUrl; + } + + void createSchema() throws IOException, SQLException { + try (Connection connection = DriverManager.getConnection(connectionUrl)) { + // Check if the tables already exist. + try (ResultSet resultSet = + connection + .createStatement() + .executeQuery( + "select count(1) " + + "from information_schema.tables " + + "where table_schema='public' " + + "and table_name in ('benchmark_all_types', 'benchmark_results')")) { + if (resultSet.next() && resultSet.getInt(1) == 2) { + LOG.info("Skipping schema creation as tables already exist"); + return; + } + } + + URL url = BenchmarkRunner.class.getResource("/schema.sql"); + Path path = Paths.get(Objects.requireNonNull(url).getPath()); + String ddl = Files.readString(path); + LOG.info("Executing schema statements"); + String[] statements = ddl.split(";"); + for (String statement : statements) { + LOG.info(statement); + connection.createStatement().execute(statement); + } + } + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java new file mode 100644 index 000000000..37a1c4cd4 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java @@ -0,0 +1,85 @@ +package com.google.cloud.pgadapter.benchmark; + +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +class Statistics { + private final BenchmarkConfiguration tpccConfiguration; + + private final AtomicReference name = new AtomicReference<>("(unknown)"); + + private final AtomicInteger totalOperations = new AtomicInteger(); + + private final AtomicInteger parallelism = new AtomicInteger(); + + private final AtomicLong operations = new AtomicLong(); + + Statistics(BenchmarkConfiguration tpccConfiguration) { + this.tpccConfiguration = tpccConfiguration; + } + + void print(Duration runtime) { + System.out.print("\033[2J\033[1;1H"); + System.out.printf( + """ + \rBenchmark: %s\t + \rNum iterations: %d\t + \rDuration: %s\t + \rParallelism: %d\t + \r + \rOperations: %d/%d (%.2f/s)\t + """, + getName(), + tpccConfiguration.getIterations(), + runtime, + getParallelism(), + getOperations(), + getTotalOperations(), + getOperationsPerSecond(runtime)); + } + + void reset(String name, int parallelism, int totalOperations) { + setName(name); + setParallelism(parallelism); + setTotalOperations(totalOperations); + } + + String getName() { + return name.get(); + } + + private void setName(String name) { + this.name.set(name); + } + + int getTotalOperations() { + return totalOperations.get(); + } + + private void setTotalOperations(int totalOperations) { + this.totalOperations.set(totalOperations); + } + + int getParallelism() { + return parallelism.get(); + } + + private void setParallelism(int parallelism) { + this.parallelism.set(parallelism); + } + + long getOperations() { + return operations.get(); + } + + double getOperationsPerSecond(Duration runtime) { + return ((double) operations.get()) / runtime.getSeconds(); + } + + void incOperations() { + operations.incrementAndGet(); + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java new file mode 100644 index 000000000..f8ccb2423 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java @@ -0,0 +1,100 @@ +package com.google.cloud.pgadapter.benchmark.config; + +import java.time.Duration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "benchmark") +public class BenchmarkConfiguration { + private boolean loadData; + + private int loadDataThreads; + + private boolean truncateBeforeLoad; + + private boolean runBenchmark; + + private Duration benchmarkDuration; + + private int recordCount; + + private int iterations; + + /** --- Optimizations --- */ + private boolean useReadOnlyTransactions; + + private boolean lockScannedRanges; + + public boolean isLoadData() { + return loadData; + } + + public void setLoadData(boolean loadData) { + this.loadData = loadData; + } + + public int getLoadDataThreads() { + return loadDataThreads; + } + + public void setLoadDataThreads(int loadDataThreads) { + this.loadDataThreads = loadDataThreads; + } + + public boolean isTruncateBeforeLoad() { + return truncateBeforeLoad; + } + + public void setTruncateBeforeLoad(boolean truncateBeforeLoad) { + this.truncateBeforeLoad = truncateBeforeLoad; + } + + public boolean isRunBenchmark() { + return runBenchmark; + } + + public void setRunBenchmark(boolean runBenchmark) { + this.runBenchmark = runBenchmark; + } + + public Duration getBenchmarkDuration() { + return benchmarkDuration; + } + + public void setBenchmarkDuration(Duration benchmarkDuration) { + this.benchmarkDuration = benchmarkDuration; + } + + public int getRecordCount() { + return recordCount; + } + + public void setRecordCount(int recordCount) { + this.recordCount = recordCount; + } + + public int getIterations() { + return iterations; + } + + public void setIterations(int iterations) { + this.iterations = iterations; + } + + public boolean isUseReadOnlyTransactions() { + return useReadOnlyTransactions; + } + + public void setUseReadOnlyTransactions(boolean useReadOnlyTransactions) { + this.useReadOnlyTransactions = useReadOnlyTransactions; + } + + public boolean isLockScannedRanges() { + return lockScannedRanges; + } + + public void setLockScannedRanges(boolean lockScannedRanges) { + this.lockScannedRanges = lockScannedRanges; + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/PGAdapterConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/PGAdapterConfiguration.java new file mode 100644 index 000000000..572615d07 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/PGAdapterConfiguration.java @@ -0,0 +1,88 @@ +package com.google.cloud.pgadapter.benchmark.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "pgadapter") +public class PGAdapterConfiguration { + private boolean inProcess; + + private String credentials; + + private boolean enableOpenTelemetry; + + private double openTelemetrySampleRate; + + private boolean disableInternalRetries; + + private String host; + + private int port; + + private String connectionUrl; + + public boolean isInProcess() { + return inProcess; + } + + public void setInProcess(boolean inProcess) { + this.inProcess = inProcess; + } + + public boolean isEnableOpenTelemetry() { + return enableOpenTelemetry; + } + + public void setEnableOpenTelemetry(boolean enableOpenTelemetry) { + this.enableOpenTelemetry = enableOpenTelemetry; + } + + public double getOpenTelemetrySampleRate() { + return openTelemetrySampleRate; + } + + public void setOpenTelemetrySampleRate(double openTelemetrySampleRate) { + this.openTelemetrySampleRate = openTelemetrySampleRate; + } + + public boolean isDisableInternalRetries() { + return disableInternalRetries; + } + + public void setDisableInternalRetries(boolean disableInternalRetries) { + this.disableInternalRetries = disableInternalRetries; + } + + public String getCredentials() { + return credentials; + } + + public void setCredentials(String credentials) { + this.credentials = credentials; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getConnectionUrl() { + return connectionUrl; + } + + public void setConnectionUrl(String connectionUrl) { + this.connectionUrl = connectionUrl; + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java new file mode 100644 index 000000000..467190757 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java @@ -0,0 +1,36 @@ +package com.google.cloud.pgadapter.benchmark.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "spanner") +public class SpannerConfiguration { + private String project; + private String instance; + private String database; + + public String getProject() { + return project; + } + + public void setProject(String project) { + this.project = project; + } + + public String getInstance() { + return instance; + } + + public void setInstance(String instance) { + this.instance = instance; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/AbstractRowProducer.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/AbstractRowProducer.java new file mode 100644 index 000000000..b2393bdbb --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/AbstractRowProducer.java @@ -0,0 +1,196 @@ +package com.google.cloud.pgadapter.benchmark.dataloader; + +import java.io.Writer; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.apache.commons.codec.binary.Hex; + +abstract class AbstractRowProducer { + + private final String table; + private final String columns; + private final long rowCount; + private final Runnable rowCounterIncrementer; + final Random random = new Random(); + + AbstractRowProducer(String table, String columns, long rowCount, Runnable rowCounterIncrementer) { + this.table = table; + this.columns = columns; + this.rowCount = rowCount; + this.rowCounterIncrementer = rowCounterIncrementer; + } + + String getTable() { + return table; + } + + String getColumns() { + return columns; + } + + Future asyncWriteRows(ExecutorService executor, Writer writer) { + return executor.submit( + () -> { + try (writer) { + for (long rowIndex = 0L; rowIndex < rowCount; rowIndex++) { + String row = createRow(rowIndex); + if (row != null) { + writer.write(row + "\n"); + rowCounterIncrementer.run(); + } + } + return rowCount; + } + }); + } + + abstract String createRow(long rowIndex); + + String quote(String input) { + return "'" + input + "'"; + } + + String getRandomString(int length) { + int a = 97, z = 122; + return random + .ints(a, z + 1) + .limit(length) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + + String getRandomLong() { + return String.valueOf(random.nextLong()); + } + + String getRandomBoolean() { + return String.valueOf(random.nextBoolean()); + } + + String getRandomDouble() { + return String.valueOf(random.nextDouble()); + } + + int getRandomInt(int min, int max) { + return random.nextInt(max - min + 1) + min; + } + + String getRandomBytes(int min, int max) { + return quote(getUnquotedRandomBytes(min, max)); + } + + private String getUnquotedRandomBytes(int min, int max) { + byte[] result = new byte[getRandomInt(min, max)]; + random.nextBytes(result); + return "\\x" + new String(Hex.encodeHex(result)); + } + + String getRandomDecimal(int precision) { + return getRandomDecimal(1, precision); + } + + String getRandomDecimal(int factor, int precision) { + return BigDecimal.valueOf(random.nextDouble() * factor) + .round(new MathContext(precision, RoundingMode.HALF_UP)) + .toPlainString(); + } + + String now() { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME + .format(ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())) + .replace('T', ' '); + } + + String getRandomTimestamp() { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME + .format( + ZonedDateTime.ofInstant( + Instant.ofEpochSecond(random.nextLong(0L, System.currentTimeMillis() / 1000L)), + ZoneId.systemDefault())) + .replace('T', ' '); + } + + String getRandomDate() { + return LocalDate.of(getRandomInt(1900, 2024), getRandomInt(1, 12), getRandomInt(1, 28)) + .toString(); + } + + String getRandomJsonb() { + return quote(getUnquotedRandomJsonb()); + } + + private String getUnquotedRandomJsonb() { + return String.format( + "{\"key1\": \"%s\", \"key2\": \"%s\"}", + getRandomString(getRandomInt(10, 30)), getRandomString(getRandomInt(10, 30))); + } + + String getRandomLongArray() { + return random + .longs() + .limit(getRandomInt(2, 10)) + .mapToObj(String::valueOf) + .collect(Collectors.joining(",", "'{", "}'")); + } + + String getRandomBooleanArray() { + return random + .ints() + .limit(getRandomInt(2, 10)) + .mapToObj(i -> i % 2 == 0 ? "t" : "f") + .collect(Collectors.joining(",", "'{", "}'")); + } + + String getRandomBytesArray() { + return IntStream.range(0, getRandomInt(2, 10)) + .mapToObj(i -> getUnquotedRandomBytes(10, 20)) + .collect(Collectors.joining(",", "'{", "}'")); + } + + String getRandomDoubleArray() { + return IntStream.range(0, getRandomInt(2, 10)) + .mapToObj(i -> getRandomDouble()) + .collect(Collectors.joining(",", "'{", "}'")); + } + + String getRandomDecimalArray() { + return IntStream.range(0, getRandomInt(2, 10)) + .mapToObj(i -> getRandomDecimal(1)) + .collect(Collectors.joining(",", "'{", "}'")); + } + + String getRandomTimestampArray() { + return IntStream.range(0, getRandomInt(2, 10)) + .mapToObj(i -> getRandomTimestamp()) + .collect(Collectors.joining(",", "'{", "}'")); + } + + String getRandomDateArray() { + return IntStream.range(0, getRandomInt(2, 10)) + .mapToObj(i -> getRandomDate()) + .collect(Collectors.joining(",", "'{", "}'")); + } + + String getRandomStringArray() { + return IntStream.range(0, getRandomInt(2, 10)) + .mapToObj(i -> getRandomString(getRandomInt(5, 15))) + .collect(Collectors.joining(",", "'{", "}'")); + } + + String getRandomJsonbArray() { + return IntStream.range(0, getRandomInt(2, 10)) + .mapToObj(i -> "''" + getUnquotedRandomJsonb() + "''") + .collect(Collectors.joining(",", "'{", "}'")); + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/AllTypesRowProducer.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/AllTypesRowProducer.java new file mode 100644 index 000000000..4aeba4f05 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/AllTypesRowProducer.java @@ -0,0 +1,57 @@ +package com.google.cloud.pgadapter.benchmark.dataloader; + +import com.google.common.collect.ImmutableList; + +class AllTypesRowProducer extends AbstractRowProducer { + private static final String TABLE = "benchmark_all_types"; + private static final String COLUMNS = + """ + col_bigint, + col_bool, + col_bytea, + col_float8, + col_numeric, + col_timestamptz, + col_date, + col_varchar, + col_jsonb, + col_array_bigint, + col_array_bool, + col_array_bytea, + col_array_float8, + col_array_numeric, + col_array_timestamptz, + col_array_date, + col_array_varchar, + col_array_jsonb + """; + + AllTypesRowProducer(DataLoadStatus status, long rowCount) { + super(TABLE, COLUMNS, rowCount, status::incAllTypes); + } + + @Override + String createRow(long rowIndex) { + return String.join( + ",", + ImmutableList.of( + getRandomLong(), + getRandomBoolean(), + getRandomBytes(10, 1000), + getRandomDouble(), + getRandomDecimal(2), + now(), + getRandomDate(), + quote(getRandomString(getRandomInt(20, 50))), + getRandomJsonb(), + getRandomLongArray(), + getRandomBooleanArray(), + getRandomBytesArray(), + getRandomDoubleArray(), + getRandomDecimalArray(), + getRandomTimestampArray(), + getRandomDateArray(), + getRandomStringArray(), + "null")); + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/DataLoadStatus.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/DataLoadStatus.java new file mode 100644 index 000000000..55d02e310 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/DataLoadStatus.java @@ -0,0 +1,55 @@ +package com.google.cloud.pgadapter.benchmark.dataloader; + +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +public class DataLoadStatus { + private final BenchmarkConfiguration benchmarkConfiguration; + private final AtomicBoolean truncatedAllTypes = new AtomicBoolean(); + + private final AtomicLong allTypes = new AtomicLong(); + + public DataLoadStatus(BenchmarkConfiguration benchmarkConfiguration) { + this.benchmarkConfiguration = benchmarkConfiguration; + } + + public void print(Duration runtime) { + System.out.printf( + """ + \033[2J\033[1;1H + \rNum threads: %d\t + \rDuration: %s\t + \r + \rAll types: %d/%d (%.2f%%) \t + + \r + \rTotal: %d/%d (%.2f%%) \t + """, + benchmarkConfiguration.getLoadDataThreads(), + runtime, + allTypes.get(), + benchmarkConfiguration.getRecordCount(), + ((double) allTypes.get() / benchmarkConfiguration.getRecordCount()) * 100, + getCurrentTotal(), + getTotal(), + ((double) getCurrentTotal() / getTotal()) * 100); + } + + void setTruncatedAllTypes() { + truncatedAllTypes.set(true); + } + + void incAllTypes() { + allTypes.incrementAndGet(); + } + + long getCurrentTotal() { + return allTypes.get(); + } + + long getTotal() { + return benchmarkConfiguration.getRecordCount(); + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/DataLoader.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/DataLoader.java new file mode 100644 index 000000000..0ed43b68a --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/dataloader/DataLoader.java @@ -0,0 +1,113 @@ +package com.google.cloud.pgadapter.benchmark.dataloader; + +import com.google.cloud.pgadapter.benchmark.BenchmarkApplication; +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import java.io.IOException; +import java.io.PipedReader; +import java.io.PipedWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.postgresql.PGConnection; +import org.postgresql.copy.CopyManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DataLoader implements AutoCloseable { + private static final Logger LOG = LoggerFactory.getLogger(BenchmarkApplication.class); + + private final String connectionUrl; + + private final BenchmarkConfiguration benchmarkConfiguration; + + private final ListeningExecutorService loadDataExecutor; + + private final ListeningExecutorService rowProducerExecutor; + + private final DataLoadStatus status; + + public DataLoader( + DataLoadStatus status, String connectionUrl, BenchmarkConfiguration benchmarkConfiguration) { + this.connectionUrl = connectionUrl; + this.benchmarkConfiguration = benchmarkConfiguration; + this.loadDataExecutor = + MoreExecutors.listeningDecorator( + Executors.newFixedThreadPool(benchmarkConfiguration.getLoadDataThreads())); + this.rowProducerExecutor = + MoreExecutors.listeningDecorator( + Executors.newFixedThreadPool(benchmarkConfiguration.getLoadDataThreads())); + this.status = status; + } + + @Override + public void close() { + this.loadDataExecutor.shutdown(); + this.rowProducerExecutor.shutdown(); + } + + public long loadData() throws Exception { + if (benchmarkConfiguration.isTruncateBeforeLoad()) { + truncate(); + } + + long totalRowCount = 0L; + int recordCount = benchmarkConfiguration.getRecordCount(); + totalRowCount += recordCount; + Future allTypesFuture = + loadDataExecutor.submit( + () -> { + long rowCount = loadTable(new AllTypesRowProducer(status, recordCount)); + LOG.info("Loaded {} records", rowCount); + return rowCount; + }); + + allTypesFuture.get(); + loadDataExecutor.shutdown(); + if (!loadDataExecutor.awaitTermination(60L, TimeUnit.SECONDS)) { + throw new TimeoutException("Loading data timed out while waiting for executor to shut down."); + } + return totalRowCount; + } + + long loadTable(AbstractRowProducer rowProducer) throws SQLException, IOException { + PipedWriter writer = new PipedWriter(); + try (PipedReader reader = new PipedReader(writer, 30_000); + Connection connection = createConnection()) { + CopyManager copyManager = connection.unwrap(PGConnection.class).getCopyAPI(); + rowProducer.asyncWriteRows(this.rowProducerExecutor, writer); + return copyManager.copyIn( + String.format( + "copy \"%s\" (%s) from stdin (format csv, delimiter ',', quote '''', null 'null')", + rowProducer.getTable(), rowProducer.getColumns()), + reader); + } + } + + void truncate() throws SQLException { + try (Connection connection = createConnection(); + Statement statement = connection.createStatement()) { + LOG.info("truncating benchmark_all_types"); + statement.execute("truncate table benchmark_all_types"); + status.setTruncatedAllTypes(); + } + } + + private Connection createConnection() throws SQLException { + Connection connection = DriverManager.getConnection(connectionUrl); + // Use upsert instead of insert for COPY to prevent data loading errors if the tables are + // already half-filled. + connection.createStatement().execute("set spanner.copy_upsert=true"); + // Allow copy operations to be non-atomic. + connection + .createStatement() + .execute("set spanner.autocommit_dml_mode='partitioned_non_atomic'"); + return connection; + } +} diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties new file mode 100644 index 000000000..dfb0f5bbf --- /dev/null +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -0,0 +1,71 @@ + +# --- LOGGING PROPERTIES --- # +logging.file.name=benchmark-log.log +# Turn off console logging, so you can actually read the output. +logging.threshold.console=OFF + +# --- BENCHMARK PROPERTIES --- # + +# Load the initial data set. +benchmark.load-data=false +benchmark.load-data-threads=32 +# Truncates all existing data before loading new data. This setting only has any effect when +# tpcc.load-data also has been enabled. +benchmark.truncate-before-load=true + +# Run the benchmark. +benchmark.run-benchmark=true +# The max duration that the benchmark should run in ISO-8601 notation. +# E.g. PT60S (60 seconds), PT10M (10 minutes), PT2H (2 hours), P1D (1 day) +benchmark.benchmark-duration=PT300S + +# The number of rows to load into the benchmark table. +benchmark.record-count=1000 +# The number of iterations that each benchmark should run. +# A higher number means a longer execution time, but gives a better indication of how the +# performance will be over time. +benchmark.iterations=100 + +# --- Possible optimizations for TPC-C --- # +benchmark.use-read-only-transactions=false +benchmark.lock-scanned-ranges=false + +# Change these to match your Cloud Spanner PostgreSQL-dialect database. +spanner.project=appdev-soda-spanner-staging +spanner.instance=knut-test-ycsb +spanner.database=knut-test-db + + +# --- IN-PROCESS PGADAPTER --- # + +# These configuration properties are only relevant if you run an in-process PGAdapter instance. + +# Set this to true to instruct the benchmark runner to start a PGAdapter instance in-process with +# the benchmark application. +pgadapter.in-process=true + +# Set this if you want the in-process PGAdapter instance to use a specific service account +# credentials file. +# Leave unset if the application should use the APPLICATION_DEFAULT_CREDENTIALS. +pgadapter.credentials=/Users/loite/Downloads/appdev-soda-spanner-staging.json + +# Set this to true to disable automatic retries of aborted transactions by PGAdapter. Disabling this +# will propagate all aborted transaction errors to the application, and the transaction will be +# marked as failed instead of aborted. +pgadapter.disable-internal-retries=false + +# PGAdapter OpenTelemetry settings. +pgadapter.enable-open-telemetry=false +pgadapter.open-telemetry-sample-rate=1 + +# --- EXTERNAL PGADAPTER --- # + +# Set these properties to use an out-of-process PGAdapter instance. You must start this instance +# before running the benchmark. +pgadapter.host=localhost +pgadapter.port=5432 + +# This connection URL is built automatically from the properties above and should normally not be +# changed. Note that the application uses the PostgreSQL simple query protocol, so it +# can explicitly create prepared statements on PGAdapter. +pgadapter.connection-url=jdbc:postgresql://${pgadapter.host}:${pgadapter.port}/projects%2F${spanner.project}%2Finstances%2F${spanner.instance}%2Fdatabases%2F${spanner.database}?preferQueryMode=simple diff --git a/benchmarks/client-comparisons/src/main/resources/drop_schema.sql b/benchmarks/client-comparisons/src/main/resources/drop_schema.sql new file mode 100644 index 000000000..c27eecefa --- /dev/null +++ b/benchmarks/client-comparisons/src/main/resources/drop_schema.sql @@ -0,0 +1,6 @@ +start batch ddl; + +drop table if exists benchmark_all_types; +drop table if exists benchmark_results; + +run batch; diff --git a/benchmarks/client-comparisons/src/main/resources/schema.sql b/benchmarks/client-comparisons/src/main/resources/schema.sql new file mode 100644 index 000000000..364662031 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/resources/schema.sql @@ -0,0 +1,38 @@ +START BATCH DDL; + +create table benchmark_all_types ( + id varchar(36) primary key default spanner.generate_uuid(), + col_bigint bigint, + col_bool bool, + col_bytea bytea, + col_float8 float8, + col_numeric numeric, + col_timestamptz timestamptz, + col_date date, + col_varchar varchar, + col_jsonb jsonb, + col_array_bigint bigint[], + col_array_bool bool[], + col_array_bytea bytea[], + col_array_float8 float8[], + col_array_numeric numeric[], + col_array_timestamptz timestamptz[], + col_array_date date[], + col_array_varchar varchar[], + col_array_jsonb jsonb[] +); + +create table benchmark_results ( + name varchar, + executed_at timestamptz, + parallelism bigint, + avg numeric, + p50 numeric, + p90 numeric, + p95 numeric, + p99 numeric, + total numeric, + primary key (name, executed_at, parallelism) +); + +RUN BATCH; diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java index 1be882884..6d1a4f7c8 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java @@ -89,7 +89,8 @@ public class ProxyServer extends AbstractApiService { private final ThreadFactory threadFactory; - private final ExecutorService createConnectionHandlerExecutor = Executors.newSingleThreadExecutor(); + private final ExecutorService createConnectionHandlerExecutor = + Executors.newSingleThreadExecutor(); /** * Instantiates the ProxyServer from CLI-gathered metadata. @@ -340,13 +341,19 @@ void runServer( Socket socket = serverSocket.accept(); // Hand off the creation of the connection handler to a worker thread to ensure that we // continue to listen for new incoming connection requests as quickly as possible. - createConnectionHandlerExecutor.submit(() -> { - try { - createConnectionHandler(socket); - } catch (SocketException socketException) { - logger.log(Level.WARNING, () -> String.format("Failed to create connection on socket %s: %s.", socket, socketException)); - } - }); + createConnectionHandlerExecutor.submit( + () -> { + try { + createConnectionHandler(socket); + } catch (SocketException socketException) { + logger.log( + Level.WARNING, + () -> + String.format( + "Failed to create connection on socket %s: %s.", + socket, socketException)); + } + }); } } catch (SocketException e) { // This is a normal exception, as this will occur when Server#stopServer() is called. diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java index 1436587d8..144dd939d 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java @@ -14,7 +14,6 @@ package com.google.cloud.spanner.pgadapter; -import static com.google.cloud.spanner.connection.ThreadFactoryUtil.createVirtualOrDaemonThreadFactory; import static com.google.cloud.spanner.pgadapter.statements.PgCatalog.PG_TYPE_CTE_EMULATED; import static com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgNamespace.PG_NAMESPACE_CTE; import static org.junit.Assert.assertNotNull; @@ -76,7 +75,6 @@ import java.util.ArrayList; import java.util.Base64; import java.util.List; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.logging.Logger; diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/BenchmarkMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/BenchmarkMockServerTest.java index 612ac19c4..b2e792628 100644 --- a/src/test/java/com/google/cloud/spanner/pgadapter/BenchmarkMockServerTest.java +++ b/src/test/java/com/google/cloud/spanner/pgadapter/BenchmarkMockServerTest.java @@ -36,6 +36,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; @@ -55,7 +56,9 @@ public class BenchmarkMockServerTest extends AbstractMockServerTest { static { try { - LogManager.getLogManager().readConfiguration(BenchmarkMockServerTest.class.getResourceAsStream("/logging.properties")); + LogManager.getLogManager() + .readConfiguration( + BenchmarkMockServerTest.class.getResourceAsStream("/logging.properties")); } catch (IOException e) { throw new RuntimeException(e); } @@ -71,7 +74,11 @@ static class BenchmarkResult { final Duration p95; final Duration p99; - BenchmarkResult(String name, int parallelism, boolean disableVirtualThreads, ConcurrentLinkedQueue durations) { + BenchmarkResult( + String name, + int parallelism, + boolean disableVirtualThreads, + ConcurrentLinkedQueue durations) { this.name = name; this.parallelism = parallelism; this.disableVirtualThreads = disableVirtualThreads; @@ -86,7 +93,9 @@ static class BenchmarkResult { @Override public String toString() { - return String.format("name: %s\nparallelism: %d\nvirtual: %s\navg: %s\np50: %s\np90: %s\np95: %s\np99: %s\n\n", name, parallelism, !disableVirtualThreads, avg, p50, p90, p95, p99); + return String.format( + "name: %s\nparallelism: %d\nvirtual: %s\navg: %s\np50: %s\np90: %s\np95: %s\np99: %s\n\n", + name, parallelism, !disableVirtualThreads, avg, p50, p90, p95, p99); } } @@ -101,7 +110,7 @@ public static List parameters() { ImmutableList.Builder builder = ImmutableList.builder(); for (int parallelism : new int[] {1, 2, 4, 8, 16, 32, 50, 100, 150, 200, 300, 400}) { for (boolean disableVirtualThreads : new boolean[] {false, true}) { - builder.add(new Object[]{disableVirtualThreads, parallelism}); + builder.add(new Object[] {disableVirtualThreads, parallelism}); } } return builder.build(); @@ -115,10 +124,9 @@ public static List parameters() { private static final String SELECT_SINGLE_ROW_SQL = "select * from random where id=?"; - @BeforeClass public static void setupBenchmarkServer() throws Exception { - assumeTrue(System.getProperty("pgadapter.benchmark") == null); + assumeTrue(Objects.equals("true", System.getProperty("pgadapter.benchmark"))); doStartMockSpannerAndPgAdapterServers( createMockSpannerThatReturnsOneQueryPartition(), @@ -126,7 +134,8 @@ public static void setupBenchmarkServer() throws Exception { TestOptionsMetadataBuilder::disableDebugMode, OpenTelemetry.noop()); - mockSpanner.setExecuteStreamingSqlExecutionTime(SimulatedExecutionTime.ofMinimumAndRandomTime(3, 3)); + mockSpanner.setExecuteStreamingSqlExecutionTime( + SimulatedExecutionTime.ofMinimumAndRandomTime(3, 3)); setupResults(); } @@ -134,7 +143,7 @@ private static void setupResults() { String spannerSql = SELECT_SINGLE_ROW_SQL.replace("?", "$1"); RandomResultSetGenerator generator = new RandomResultSetGenerator(1, Dialect.POSTGRESQL); - for (int i=0; i < NUM_RESULTS; i++) { + for (int i = 0; i < NUM_RESULTS; i++) { String id = UUID.randomUUID().toString(); Statement spannerStatement = Statement.newBuilder(spannerSql).bind("p1").to(id).build(); mockSpanner.putStatementResult(StatementResult.query(spannerStatement, generator.generate())); @@ -160,9 +169,7 @@ public void restartServer() { } private String createUrl() { - return String.format( - "jdbc:postgresql://localhost:%d/db", - pgServer.getLocalPort()); + return String.format("jdbc:postgresql://localhost:%d/db", pgServer.getLocalPort()); } @Test @@ -176,7 +183,9 @@ public void testSelectOneRowAutoCommit() throws Exception { executor.shutdown(); assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); assertEquals(parallelism * NUM_ITERATIONS, durations.size()); - BenchmarkResult result = new BenchmarkResult("SelectOneRowAutoCommit", parallelism, disableVirtualThreads, durations); + BenchmarkResult result = + new BenchmarkResult( + "SelectOneRowAutoCommit", parallelism, disableVirtualThreads, durations); System.out.print(result); } @@ -190,7 +199,8 @@ private void runQuery(int iterations, ConcurrentLinkedQueue durations) try (ResultSet resultSet = statement.executeQuery()) { while (resultSet.next()) { for (int col = 1; col <= resultSet.getMetaData().getColumnCount(); col++) { - assertEquals(resultSet.getString(col), + assertEquals( + resultSet.getString(col), resultSet.getString(resultSet.getMetaData().getColumnLabel(col))); } } @@ -203,6 +213,4 @@ private void runQuery(int iterations, ConcurrentLinkedQueue durations) throw new RuntimeException(exception); } } - - } From 6040d9d455108b19600b1a9a3e7839f7c6ef5ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 31 Dec 2023 15:56:01 +0100 Subject: [PATCH 16/46] feat: make parallelism configurable --- .../cloud/pgadapter/benchmark/BenchmarkRunner.java | 5 +++-- .../google/cloud/pgadapter/benchmark/Statistics.java | 1 + .../benchmark/config/BenchmarkConfiguration.java | 11 +++++++++++ .../src/main/resources/application.properties | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java index 5b6ffeae0..a5f85492b 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java @@ -46,8 +46,9 @@ public void run() { try (Connection connection = DriverManager.getConnection(connectionUrl)) { this.identifiers = loadIdentifiers(connection); - benchmarkSelectOneRowAutoCommit(1); - benchmarkSelectOneRowAutoCommit(2); + for (int parallelism : benchmarkConfiguration.getParallelism()) { + benchmarkSelectOneRowAutoCommit(parallelism); + } } catch (Throwable throwable) { LOG.error("Benchmark runner failed", throwable); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java index 37a1c4cd4..e2dccc0ff 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java @@ -45,6 +45,7 @@ void reset(String name, int parallelism, int totalOperations) { setName(name); setParallelism(parallelism); setTotalOperations(totalOperations); + operations.set(0L); } String getName() { diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java index f8ccb2423..60d6e3e52 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java @@ -1,6 +1,7 @@ package com.google.cloud.pgadapter.benchmark.config; import java.time.Duration; +import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -21,6 +22,8 @@ public class BenchmarkConfiguration { private int iterations; + private List parallelism; + /** --- Optimizations --- */ private boolean useReadOnlyTransactions; @@ -82,6 +85,14 @@ public void setIterations(int iterations) { this.iterations = iterations; } + public List getParallelism() { + return parallelism; + } + + public void setParallelism(List parallelism) { + this.parallelism = parallelism; + } + public boolean isUseReadOnlyTransactions() { return useReadOnlyTransactions; } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index dfb0f5bbf..b48ed51cf 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -25,6 +25,8 @@ benchmark.record-count=1000 # A higher number means a longer execution time, but gives a better indication of how the # performance will be over time. benchmark.iterations=100 +# The number of parallel clients to use for benchmarks. +benchmark.parallelism=1,2,4 # --- Possible optimizations for TPC-C --- # benchmark.use-read-only-transactions=false From cafc412e2d4700bd3046138d9afaf4d90a29da40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 31 Dec 2023 16:08:43 +0100 Subject: [PATCH 17/46] feat: print results at end --- .../cloud/pgadapter/benchmark/BenchmarkApplication.java | 7 ++++++- .../google/cloud/pgadapter/benchmark/BenchmarkRunner.java | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index 8a6be98ca..208430cea 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -81,7 +81,8 @@ public void run(String... args) throws Exception { LOG.info("Starting benchmark"); Statistics statistics = new Statistics(benchmarkConfiguration); ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.submit(new BenchmarkRunner(statistics, connectionUrl, benchmarkConfiguration)); + BenchmarkRunner runner = new BenchmarkRunner(statistics, connectionUrl, benchmarkConfiguration); + executor.submit(runner); executor.shutdown(); Stopwatch watch = Stopwatch.createStarted(); @@ -95,6 +96,10 @@ public void run(String... args) throws Exception { if (!executor.awaitTermination(60L, TimeUnit.SECONDS)) { throw new TimeoutException("Timed out while waiting for benchmark runner to shut down"); } + + for (BenchmarkResult result : runner.getResults()) { + System.out.println(result); + } } } finally { if (server != null) { diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java index a5f85492b..cd69d5e40 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java @@ -34,6 +34,8 @@ class BenchmarkRunner implements Runnable { private boolean failed; + private final List results = new ArrayList<>(); + BenchmarkRunner( Statistics statistics, String connectionUrl, BenchmarkConfiguration benchmarkConfiguration) { this.statistics = statistics; @@ -41,6 +43,10 @@ class BenchmarkRunner implements Runnable { this.benchmarkConfiguration = benchmarkConfiguration; } + List getResults() { + return this.results; + } + @Override public void run() { try (Connection connection = DriverManager.getConnection(connectionUrl)) { @@ -81,7 +87,7 @@ private void benchmarkSelectOneRowAutoCommit(int parallelism) throws Exception { assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); assertEquals(totalOperations, durations.size()); BenchmarkResult result = new BenchmarkResult("SelectOneRowAutoCommit", parallelism, durations); - System.out.print(result); + results.add(result); } private void runQuery(int iterations, ConcurrentLinkedQueue durations) { From 5adde6f3fd587986b456a77b6655ba12b6c148fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 31 Dec 2023 16:48:10 +0100 Subject: [PATCH 18/46] fix: calculate ops/s based on current runtime --- .../cloud/pgadapter/benchmark/Statistics.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java index e2dccc0ff..2fd67189c 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java @@ -2,6 +2,7 @@ import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; import java.time.Duration; +import java.time.Instant; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -17,25 +18,30 @@ class Statistics { private final AtomicLong operations = new AtomicLong(); + private final AtomicReference startTime = new AtomicReference<>(Instant.now()); + Statistics(BenchmarkConfiguration tpccConfiguration) { this.tpccConfiguration = tpccConfiguration; } - void print(Duration runtime) { + void print(Duration totalRuntime) { + Duration runtime = getRuntime(); System.out.print("\033[2J\033[1;1H"); System.out.printf( """ \rBenchmark: %s\t \rNum iterations: %d\t - \rDuration: %s\t + \rTotal runtime: %s\t \rParallelism: %d\t \r + \rRuntime: %s\t \rOperations: %d/%d (%.2f/s)\t """, getName(), tpccConfiguration.getIterations(), - runtime, + totalRuntime, getParallelism(), + runtime, getOperations(), getTotalOperations(), getOperationsPerSecond(runtime)); @@ -46,6 +52,11 @@ void reset(String name, int parallelism, int totalOperations) { setParallelism(parallelism); setTotalOperations(totalOperations); operations.set(0L); + startTime.set(Instant.now()); + } + + Duration getRuntime() { + return Duration.between(startTime.get(), Instant.now()); } String getName() { From dbf5b14434a33ddcdeed71b9d1f9629fba658357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 31 Dec 2023 17:45:32 +0100 Subject: [PATCH 19/46] feat: add more benchmarks --- .../benchmark/BenchmarkApplication.java | 3 +- .../pgadapter/benchmark/BenchmarkRunner.java | 54 ++++++++++++++++--- .../src/main/resources/application.properties | 2 +- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index 208430cea..cda523a31 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -81,7 +81,8 @@ public void run(String... args) throws Exception { LOG.info("Starting benchmark"); Statistics statistics = new Statistics(benchmarkConfiguration); ExecutorService executor = Executors.newSingleThreadExecutor(); - BenchmarkRunner runner = new BenchmarkRunner(statistics, connectionUrl, benchmarkConfiguration); + BenchmarkRunner runner = + new BenchmarkRunner(statistics, connectionUrl, benchmarkConfiguration); executor.submit(runner); executor.shutdown(); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java index cd69d5e40..a2653e54a 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java @@ -54,6 +54,9 @@ public void run() { for (int parallelism : benchmarkConfiguration.getParallelism()) { benchmarkSelectOneRowAutoCommit(parallelism); + benchmarkSelect100RowsRowAutoCommit(parallelism); + benchmarkSelectOneRowTransaction(parallelism); + benchmarkSelect100RowsRowTransaction(parallelism); } } catch (Throwable throwable) { @@ -73,30 +76,65 @@ private List loadIdentifiers(Connection connection) throws SQLException return result; } + private void benchmarkSelect100RowsRowAutoCommit(int parallelism) throws Exception { + benchmarkSelect( + "Select100RowsAutoCommit", + parallelism, + "select * from benchmark_all_types where id>=? limit 100", + true); + } + private void benchmarkSelectOneRowAutoCommit(int parallelism) throws Exception { + benchmarkSelect( + "SelectOneRowAutoCommit", + parallelism, + "select * from benchmark_all_types where id=?", + true); + } + + private void benchmarkSelect100RowsRowTransaction(int parallelism) throws Exception { + benchmarkSelect( + "Select100RowsTransaction", + parallelism, + "select * from benchmark_all_types where id>=? limit 100", + false); + } + + private void benchmarkSelectOneRowTransaction(int parallelism) throws Exception { + benchmarkSelect( + "SelectOneRowTransaction", + parallelism, + "select * from benchmark_all_types where id=?", + false); + } + + private void benchmarkSelect(String name, int parallelism, String sql, boolean autoCommit) + throws Exception { int totalOperations = parallelism * benchmarkConfiguration.getIterations(); - statistics.reset("SelectOneRowAutoCommit", parallelism, totalOperations); + statistics.reset(name, parallelism, totalOperations); ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); ExecutorService executor = Executors.newFixedThreadPool(parallelism); for (int task = 0; task < parallelism; task++) { - executor.submit(() -> runQuery(benchmarkConfiguration.getIterations(), durations)); + executor.submit( + () -> runQuery(sql, autoCommit, benchmarkConfiguration.getIterations(), durations)); } executor.shutdown(); assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); assertEquals(totalOperations, durations.size()); - BenchmarkResult result = new BenchmarkResult("SelectOneRowAutoCommit", parallelism, durations); + BenchmarkResult result = new BenchmarkResult(name, parallelism, durations); results.add(result); } - private void runQuery(int iterations, ConcurrentLinkedQueue durations) { + private void runQuery( + String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations) { try (Connection connection = DriverManager.getConnection(connectionUrl)) { + connection.setAutoCommit(autoCommit); for (int n = 0; n < iterations; n++) { String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); Stopwatch watch = Stopwatch.createStarted(); - try (PreparedStatement statement = - connection.prepareStatement("select * from benchmark_all_types where id=?")) { + try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setString(1, id); try (ResultSet resultSet = statement.executeQuery()) { while (resultSet.next()) { @@ -108,11 +146,13 @@ private void runQuery(int iterations, ConcurrentLinkedQueue durations) } } } + if (!autoCommit) { + connection.commit(); + } statistics.incOperations(); durations.add(watch.elapsed()); } } catch (SQLException exception) { - exception.printStackTrace(); throw new RuntimeException(exception); } } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index b48ed51cf..1623f051d 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -26,7 +26,7 @@ benchmark.record-count=1000 # performance will be over time. benchmark.iterations=100 # The number of parallel clients to use for benchmarks. -benchmark.parallelism=1,2,4 +benchmark.parallelism=1,2 # --- Possible optimizations for TPC-C --- # benchmark.use-read-only-transactions=false From 96f000a61db79260be8e2c8623e157c030ca340b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 31 Dec 2023 18:06:22 +0100 Subject: [PATCH 20/46] feat: make numChannels configurable --- .../pgadapter/benchmark/BenchmarkApplication.java | 1 + .../benchmark/config/PGAdapterConfiguration.java | 10 ++++++++++ .../src/main/resources/application.properties | 1 + 3 files changed, 12 insertions(+) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index cda523a31..66711a04c 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -122,6 +122,7 @@ private ProxyServer startPGAdapter() { .setProject(spannerConfiguration.getProject()) .setInstance(spannerConfiguration.getInstance()) .setDatabase(spannerConfiguration.getDatabase()) + .setNumChannels(pgAdapterConfiguration.getNumChannels()) .disableUnixDomainSockets(); if (pgAdapterConfiguration.isEnableOpenTelemetry()) { builder diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/PGAdapterConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/PGAdapterConfiguration.java index 572615d07..58ab3f2ae 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/PGAdapterConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/PGAdapterConfiguration.java @@ -8,6 +8,8 @@ public class PGAdapterConfiguration { private boolean inProcess; + private int numChannels; + private String credentials; private boolean enableOpenTelemetry; @@ -30,6 +32,14 @@ public void setInProcess(boolean inProcess) { this.inProcess = inProcess; } + public int getNumChannels() { + return numChannels; + } + + public void setNumChannels(int numChannels) { + this.numChannels = numChannels; + } + public boolean isEnableOpenTelemetry() { return enableOpenTelemetry; } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index 1623f051d..910222fb6 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -45,6 +45,7 @@ spanner.database=knut-test-db # Set this to true to instruct the benchmark runner to start a PGAdapter instance in-process with # the benchmark application. pgadapter.in-process=true +pgadapter.num-channels=4 # Set this if you want the in-process PGAdapter instance to use a specific service account # credentials file. From 10d0721027893b2a87a98e85253010ae6529ecf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 1 Jan 2024 10:51:51 +0100 Subject: [PATCH 21/46] feat: make benchmark methods configurable --- .../pgadapter/benchmark/BenchmarkRunner.java | 59 +++++++++++-------- .../config/BenchmarkConfiguration.java | 10 ++++ .../src/main/resources/application.properties | 2 + 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java index a2653e54a..3642b3679 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java @@ -12,7 +12,9 @@ import java.sql.SQLException; import java.time.Duration; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -22,8 +24,15 @@ import org.slf4j.LoggerFactory; class BenchmarkRunner implements Runnable { + @FunctionalInterface + interface BenchmarkMethod { + void run(String name, int parallelism) throws Exception; + } + private static final Logger LOG = LoggerFactory.getLogger(BenchmarkRunner.class); + private final Map benchmarks = new LinkedHashMap<>(); + private final Statistics statistics; private final String connectionUrl; @@ -41,6 +50,19 @@ class BenchmarkRunner implements Runnable { this.statistics = statistics; this.connectionUrl = connectionUrl; this.benchmarkConfiguration = benchmarkConfiguration; + this.benchmarks.put("SelectOneRowAutoCommit", this::benchmarkSelectOneRowAutoCommit); + this.benchmarks.put("Select100RowsAutoCommit", this::benchmarkSelect100RowsRowAutoCommit); + this.benchmarks.put("SelectOneRowTransaction", this::benchmarkSelectOneRowTransaction); + this.benchmarks.put("Select100RowsTransaction", this::benchmarkSelect100RowsRowTransaction); + for (String benchmark : benchmarkConfiguration.getBenchmarks()) { + if (!this.benchmarks.containsKey(benchmark)) { + throw new IllegalArgumentException( + "Unknown benchmark: " + + benchmark + + "\nPossible values:\n" + + String.join("\n", this.benchmarks.keySet())); + } + } } List getResults() { @@ -53,10 +75,9 @@ public void run() { this.identifiers = loadIdentifiers(connection); for (int parallelism : benchmarkConfiguration.getParallelism()) { - benchmarkSelectOneRowAutoCommit(parallelism); - benchmarkSelect100RowsRowAutoCommit(parallelism); - benchmarkSelectOneRowTransaction(parallelism); - benchmarkSelect100RowsRowTransaction(parallelism); + for (String benchmarkName : benchmarkConfiguration.getBenchmarks()) { + benchmarks.get(benchmarkName).run(benchmarkName, parallelism); + } } } catch (Throwable throwable) { @@ -76,36 +97,22 @@ private List loadIdentifiers(Connection connection) throws SQLException return result; } - private void benchmarkSelect100RowsRowAutoCommit(int parallelism) throws Exception { - benchmarkSelect( - "Select100RowsAutoCommit", - parallelism, - "select * from benchmark_all_types where id>=? limit 100", - true); + private void benchmarkSelectOneRowAutoCommit(String name, int parallelism) throws Exception { + benchmarkSelect(name, parallelism, "select * from benchmark_all_types where id=?", true); } - private void benchmarkSelectOneRowAutoCommit(int parallelism) throws Exception { + private void benchmarkSelect100RowsRowAutoCommit(String name, int parallelism) throws Exception { benchmarkSelect( - "SelectOneRowAutoCommit", - parallelism, - "select * from benchmark_all_types where id=?", - true); + name, parallelism, "select * from benchmark_all_types where id>=? limit 100", true); } - private void benchmarkSelect100RowsRowTransaction(int parallelism) throws Exception { - benchmarkSelect( - "Select100RowsTransaction", - parallelism, - "select * from benchmark_all_types where id>=? limit 100", - false); + private void benchmarkSelectOneRowTransaction(String name, int parallelism) throws Exception { + benchmarkSelect(name, parallelism, "select * from benchmark_all_types where id=?", false); } - private void benchmarkSelectOneRowTransaction(int parallelism) throws Exception { + private void benchmarkSelect100RowsRowTransaction(String name, int parallelism) throws Exception { benchmarkSelect( - "SelectOneRowTransaction", - parallelism, - "select * from benchmark_all_types where id=?", - false); + name, parallelism, "select * from benchmark_all_types where id>=? limit 100", false); } private void benchmarkSelect(String name, int parallelism, String sql, boolean autoCommit) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java index 60d6e3e52..ce45a502a 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java @@ -24,6 +24,8 @@ public class BenchmarkConfiguration { private List parallelism; + private List benchmarks; + /** --- Optimizations --- */ private boolean useReadOnlyTransactions; @@ -89,6 +91,14 @@ public List getParallelism() { return parallelism; } + public List getBenchmarks() { + return benchmarks; + } + + public void setBenchmarks(List benchmarks) { + this.benchmarks = benchmarks; + } + public void setParallelism(List parallelism) { this.parallelism = parallelism; } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index 910222fb6..e6ed245eb 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -27,6 +27,8 @@ benchmark.record-count=1000 benchmark.iterations=100 # The number of parallel clients to use for benchmarks. benchmark.parallelism=1,2 +# The benchmarks to run +benchmark.benchmarks=SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction # --- Possible optimizations for TPC-C --- # benchmark.use-read-only-transactions=false From 684ab08c845a88cd6bca32272e676306be306b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 1 Jan 2024 13:55:21 +0100 Subject: [PATCH 22/46] feat: add runners for JDBC + client lib --- benchmarks/client-comparisons/pom.xml | 6 + .../benchmark/AbstractBenchmarkRunner.java | 142 +++++++++++++++ .../benchmark/BenchmarkApplication.java | 69 +++++-- .../pgadapter/benchmark/BenchmarkRunner.java | 170 ------------------ .../benchmark/JdbcBenchmarkRunner.java | 113 ++++++++++++ .../pgadapter/benchmark/SchemaService.java | 2 +- .../benchmark/SpannerBenchmarkRunner.java | 139 ++++++++++++++ .../cloud/pgadapter/benchmark/Statistics.java | 29 ++- .../config/BenchmarkConfiguration.java | 40 ++++- .../src/main/resources/application.properties | 11 +- 10 files changed, 524 insertions(+), 197 deletions(-) create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java delete mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java diff --git a/benchmarks/client-comparisons/pom.xml b/benchmarks/client-comparisons/pom.xml index cbd826b78..c6cb1650e 100644 --- a/benchmarks/client-comparisons/pom.xml +++ b/benchmarks/client-comparisons/pom.xml @@ -27,6 +27,12 @@ postgresql 42.7.1 + + + com.google.cloud + google-cloud-spanner-jdbc + 2.15.0 + com.google.cloud diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java new file mode 100644 index 000000000..55cd988b8 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -0,0 +1,142 @@ +package com.google.cloud.pgadapter.benchmark; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class AbstractBenchmarkRunner implements Runnable { + + @FunctionalInterface + interface BenchmarkMethod { + void run(String name, int parallelism) throws Exception; + } + + private static final Logger LOG = LoggerFactory.getLogger(JdbcBenchmarkRunner.class); + + private final String name; + + private final Map benchmarks = new LinkedHashMap<>(); + + final Statistics statistics; + + final BenchmarkConfiguration benchmarkConfiguration; + + List identifiers; + + private final List results = new ArrayList<>(); + + AbstractBenchmarkRunner( + String name, Statistics statistics, BenchmarkConfiguration benchmarkConfiguration) { + this.name = name; + this.statistics = statistics; + this.benchmarkConfiguration = benchmarkConfiguration; + this.benchmarks.put("SelectOneRowAutoCommit", this::benchmarkSelectOneRowAutoCommit); + this.benchmarks.put("Select100RowsAutoCommit", this::benchmarkSelect100RowsRowAutoCommit); + this.benchmarks.put("SelectOneRowTransaction", this::benchmarkSelectOneRowTransaction); + this.benchmarks.put("Select100RowsTransaction", this::benchmarkSelect100RowsRowTransaction); + for (String benchmark : benchmarkConfiguration.getBenchmarks()) { + if (!this.benchmarks.containsKey(benchmark)) { + throw new IllegalArgumentException( + "Unknown benchmark: " + + benchmark + + "\nPossible values:\n" + + String.join("\n", this.benchmarks.keySet())); + } + } + } + + String getName() { + return this.name; + } + + List getResults() { + return this.results; + } + + @Override + public void run() { + try { + this.identifiers = loadIdentifiers(); + + for (int parallelism : benchmarkConfiguration.getParallelism()) { + for (String benchmarkName : benchmarkConfiguration.getBenchmarks()) { + benchmarks.get(benchmarkName).run(benchmarkName, parallelism); + } + } + + } catch (Throwable throwable) { + throwable.printStackTrace(); + LOG.error("Benchmark runner failed", throwable); + } + } + + abstract List loadIdentifiers(); + + abstract String getParameterName(int index); + + void benchmarkSelectOneRowAutoCommit(String name, int parallelism) throws Exception { + benchmarkSelect( + name, + parallelism, + "select * from benchmark_all_types where id=" + getParameterName(1), + true); + } + + void benchmarkSelect100RowsRowAutoCommit(String name, int parallelism) throws Exception { + benchmarkSelect( + name, + parallelism, + "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit 100", + true); + } + + void benchmarkSelectOneRowTransaction(String name, int parallelism) throws Exception { + benchmarkSelect( + name, + parallelism, + "select * from benchmark_all_types where id=" + getParameterName(1), + false); + } + + void benchmarkSelect100RowsRowTransaction(String name, int parallelism) throws Exception { + benchmarkSelect( + name, + parallelism, + "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit 100", + false); + } + + void benchmarkSelect(String benchmarkName, int parallelism, String sql, boolean autoCommit) + throws Exception { + int totalOperations = parallelism * benchmarkConfiguration.getIterations(); + statistics.reset(this.name, benchmarkName, parallelism, totalOperations); + + ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); + + ExecutorService executor = Executors.newFixedThreadPool(parallelism); + for (int task = 0; task < parallelism; task++) { + executor.submit( + () -> runQuery(sql, autoCommit, benchmarkConfiguration.getIterations(), durations)); + } + executor.shutdown(); + assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); + assertEquals(totalOperations, durations.size()); + BenchmarkResult result = new BenchmarkResult(benchmarkName, parallelism, durations); + results.add(result); + } + + abstract void runQuery( + String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations); +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index 66711a04c..be2bf69c8 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -9,6 +9,8 @@ import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata; import com.google.common.base.Stopwatch; import com.google.common.base.Strings; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -52,21 +54,32 @@ public BenchmarkApplication( @Override public void run(String... args) throws Exception { ProxyServer server = pgAdapterConfiguration.isInProcess() ? startPGAdapter() : null; - String connectionUrl = + String pgAdapterConnectionUrl = server == null ? pgAdapterConfiguration.getConnectionUrl() : String.format( "jdbc:postgresql://localhost:%d/%s", server.getLocalPort(), spannerConfiguration.getDatabase()); + String spannerConnectionUrl = + String.format( + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?numChannels=%d" + + (pgAdapterConfiguration.getCredentials() == null + ? "" + : ";credentials=" + pgAdapterConfiguration.getCredentials()), + spannerConfiguration.getProject(), + spannerConfiguration.getInstance(), + spannerConfiguration.getDatabase(), + pgAdapterConfiguration.getNumChannels()); try { - SchemaService schemaService = new SchemaService(connectionUrl); + SchemaService schemaService = new SchemaService(pgAdapterConnectionUrl); schemaService.createSchema(); if (benchmarkConfiguration.isLoadData()) { LOG.info("Starting data load"); ExecutorService executor = Executors.newSingleThreadExecutor(); DataLoadStatus status = new DataLoadStatus(benchmarkConfiguration); - Future loadDataFuture = executor.submit(() -> loadData(status, connectionUrl)); + Future loadDataFuture = + executor.submit(() -> loadData(status, pgAdapterConnectionUrl)); executor.shutdown(); Stopwatch watch = Stopwatch.createStarted(); while (!loadDataFuture.isDone()) { @@ -77,13 +90,46 @@ public void run(String... args) throws Exception { System.out.printf("Finished loading %d rows\n", loadDataFuture.get()); } - if (benchmarkConfiguration.isRunBenchmark()) { - LOG.info("Starting benchmark"); + if (benchmarkConfiguration.isRunPgadapterBenchmark() + || benchmarkConfiguration.isRunJdbcBenchmark() + || benchmarkConfiguration.isRunSpannerBenchmark()) { + List runners = new ArrayList<>(); + LOG.info("Starting benchmarks"); Statistics statistics = new Statistics(benchmarkConfiguration); ExecutorService executor = Executors.newSingleThreadExecutor(); - BenchmarkRunner runner = - new BenchmarkRunner(statistics, connectionUrl, benchmarkConfiguration); - executor.submit(runner); + + if (benchmarkConfiguration.isRunPgadapterBenchmark()) { + JdbcBenchmarkRunner runner = + new JdbcBenchmarkRunner( + "PGAdapter Benchmarks", + statistics, + pgAdapterConnectionUrl, + benchmarkConfiguration); + executor.submit(runner); + runners.add(runner); + } + if (benchmarkConfiguration.isRunJdbcBenchmark()) { + JdbcBenchmarkRunner runner = + new JdbcBenchmarkRunner( + "Spanner JDBC Driver Benchmarks", + statistics, + spannerConnectionUrl, + benchmarkConfiguration); + executor.submit(runner); + runners.add(runner); + } + if (benchmarkConfiguration.isRunSpannerBenchmark()) { + SpannerBenchmarkRunner runner = + new SpannerBenchmarkRunner( + "Spanner Java Client Library Benchmarks", + statistics, + spannerConfiguration, + pgAdapterConfiguration, + benchmarkConfiguration); + executor.submit(runner); + runners.add(runner); + } + executor.shutdown(); Stopwatch watch = Stopwatch.createStarted(); @@ -98,8 +144,11 @@ public void run(String... args) throws Exception { throw new TimeoutException("Timed out while waiting for benchmark runner to shut down"); } - for (BenchmarkResult result : runner.getResults()) { - System.out.println(result); + for (AbstractBenchmarkRunner runner : runners) { + System.out.println(runner.getName()); + for (BenchmarkResult result : runner.getResults()) { + System.out.println(result); + } } } } finally { diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java deleted file mode 100644 index 3642b3679..000000000 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkRunner.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.google.cloud.pgadapter.benchmark; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; -import com.google.common.base.Stopwatch; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class BenchmarkRunner implements Runnable { - @FunctionalInterface - interface BenchmarkMethod { - void run(String name, int parallelism) throws Exception; - } - - private static final Logger LOG = LoggerFactory.getLogger(BenchmarkRunner.class); - - private final Map benchmarks = new LinkedHashMap<>(); - - private final Statistics statistics; - - private final String connectionUrl; - - private final BenchmarkConfiguration benchmarkConfiguration; - - private List identifiers; - - private boolean failed; - - private final List results = new ArrayList<>(); - - BenchmarkRunner( - Statistics statistics, String connectionUrl, BenchmarkConfiguration benchmarkConfiguration) { - this.statistics = statistics; - this.connectionUrl = connectionUrl; - this.benchmarkConfiguration = benchmarkConfiguration; - this.benchmarks.put("SelectOneRowAutoCommit", this::benchmarkSelectOneRowAutoCommit); - this.benchmarks.put("Select100RowsAutoCommit", this::benchmarkSelect100RowsRowAutoCommit); - this.benchmarks.put("SelectOneRowTransaction", this::benchmarkSelectOneRowTransaction); - this.benchmarks.put("Select100RowsTransaction", this::benchmarkSelect100RowsRowTransaction); - for (String benchmark : benchmarkConfiguration.getBenchmarks()) { - if (!this.benchmarks.containsKey(benchmark)) { - throw new IllegalArgumentException( - "Unknown benchmark: " - + benchmark - + "\nPossible values:\n" - + String.join("\n", this.benchmarks.keySet())); - } - } - } - - List getResults() { - return this.results; - } - - @Override - public void run() { - try (Connection connection = DriverManager.getConnection(connectionUrl)) { - this.identifiers = loadIdentifiers(connection); - - for (int parallelism : benchmarkConfiguration.getParallelism()) { - for (String benchmarkName : benchmarkConfiguration.getBenchmarks()) { - benchmarks.get(benchmarkName).run(benchmarkName, parallelism); - } - } - - } catch (Throwable throwable) { - LOG.error("Benchmark runner failed", throwable); - failed = true; - } - } - - private List loadIdentifiers(Connection connection) throws SQLException { - List result = new ArrayList<>(benchmarkConfiguration.getRecordCount()); - try (ResultSet resultSet = - connection.createStatement().executeQuery("select id from benchmark_all_types")) { - while (resultSet.next()) { - result.add(resultSet.getString(1)); - } - } - return result; - } - - private void benchmarkSelectOneRowAutoCommit(String name, int parallelism) throws Exception { - benchmarkSelect(name, parallelism, "select * from benchmark_all_types where id=?", true); - } - - private void benchmarkSelect100RowsRowAutoCommit(String name, int parallelism) throws Exception { - benchmarkSelect( - name, parallelism, "select * from benchmark_all_types where id>=? limit 100", true); - } - - private void benchmarkSelectOneRowTransaction(String name, int parallelism) throws Exception { - benchmarkSelect(name, parallelism, "select * from benchmark_all_types where id=?", false); - } - - private void benchmarkSelect100RowsRowTransaction(String name, int parallelism) throws Exception { - benchmarkSelect( - name, parallelism, "select * from benchmark_all_types where id>=? limit 100", false); - } - - private void benchmarkSelect(String name, int parallelism, String sql, boolean autoCommit) - throws Exception { - int totalOperations = parallelism * benchmarkConfiguration.getIterations(); - statistics.reset(name, parallelism, totalOperations); - - ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); - - ExecutorService executor = Executors.newFixedThreadPool(parallelism); - for (int task = 0; task < parallelism; task++) { - executor.submit( - () -> runQuery(sql, autoCommit, benchmarkConfiguration.getIterations(), durations)); - } - executor.shutdown(); - assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); - assertEquals(totalOperations, durations.size()); - BenchmarkResult result = new BenchmarkResult(name, parallelism, durations); - results.add(result); - } - - private void runQuery( - String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations) { - try (Connection connection = DriverManager.getConnection(connectionUrl)) { - connection.setAutoCommit(autoCommit); - for (int n = 0; n < iterations; n++) { - String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); - Stopwatch watch = Stopwatch.createStarted(); - try (PreparedStatement statement = connection.prepareStatement(sql)) { - statement.setString(1, id); - try (ResultSet resultSet = statement.executeQuery()) { - while (resultSet.next()) { - for (int col = 1; col <= resultSet.getMetaData().getColumnCount(); col++) { - assertEquals( - resultSet.getString(col), - resultSet.getString(resultSet.getMetaData().getColumnLabel(col))); - } - } - } - } - if (!autoCommit) { - connection.commit(); - } - statistics.incOperations(); - durations.add(watch.elapsed()); - } - } catch (SQLException exception) { - throw new RuntimeException(exception); - } - } - - public boolean isFailed() { - return failed; - } -} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java new file mode 100644 index 000000000..e7c8d76f0 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java @@ -0,0 +1,113 @@ +package com.google.cloud.pgadapter.benchmark; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import com.google.cloud.spanner.jdbc.CloudSpannerJdbcConnection; +import com.google.common.base.Stopwatch; +import java.lang.reflect.Array; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ThreadLocalRandom; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class JdbcBenchmarkRunner extends AbstractBenchmarkRunner { + private static final Logger LOG = LoggerFactory.getLogger(JdbcBenchmarkRunner.class); + + private final String connectionUrl; + + JdbcBenchmarkRunner( + String name, + Statistics statistics, + String connectionUrl, + BenchmarkConfiguration benchmarkConfiguration) { + super(name, statistics, benchmarkConfiguration); + this.connectionUrl = connectionUrl; + } + + List loadIdentifiers() { + try (Connection connection = DriverManager.getConnection(connectionUrl)) { + List result = new ArrayList<>(benchmarkConfiguration.getRecordCount()); + try (ResultSet resultSet = + connection.createStatement().executeQuery("select id from benchmark_all_types")) { + while (resultSet.next()) { + result.add(resultSet.getString(1)); + } + } + return result; + } catch (SQLException sqlException) { + throw new RuntimeException(sqlException); + } + } + + @Override + String getParameterName(int index) { + return "?"; + } + + @Override + void runQuery( + String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations) { + try (Connection connection = DriverManager.getConnection(connectionUrl)) { + connection.setAutoCommit(autoCommit); + for (int n = 0; n < iterations; n++) { + if (!benchmarkConfiguration.getMaxRandomWait().isZero()) { + long sleepDuration = + ThreadLocalRandom.current() + .nextLong(benchmarkConfiguration.getMaxRandomWait().toMillis()); + Thread.sleep(sleepDuration); + } + String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); + Stopwatch watch = Stopwatch.createStarted(); + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, id); + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + for (int col = 1; col <= resultSet.getMetaData().getColumnCount(); col++) { + if (connection.isWrapperFor(CloudSpannerJdbcConnection.class) + && !Objects.equals( + String.class.getName(), resultSet.getMetaData().getColumnClassName(col))) { + Object value1 = resultSet.getObject(col); + Object value2 = resultSet.getObject(resultSet.getMetaData().getColumnLabel(col)); + if (value1 != null + && value2 != null + && value1.getClass().isArray() + && value2.getClass().isArray()) { + assertEquals(Array.getLength(value1), Array.getLength(value2)); + for (int i = 0; i < Array.getLength(value1); i++) { + assertEquals(Array.get(value1, i), Array.get(value2, i)); + } + } else { + assertEquals(value1, value2); + } + } else { + assertEquals( + resultSet.getString(col), + resultSet.getString(resultSet.getMetaData().getColumnLabel(col))); + } + } + } + } + } + if (!autoCommit) { + connection.commit(); + } + statistics.incOperations(); + durations.add(watch.elapsed()); + } + } catch (SQLException exception) { + throw new RuntimeException(exception); + } catch (InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SchemaService.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SchemaService.java index 4051b8837..bb7d49065 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SchemaService.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SchemaService.java @@ -39,7 +39,7 @@ void createSchema() throws IOException, SQLException { } } - URL url = BenchmarkRunner.class.getResource("/schema.sql"); + URL url = JdbcBenchmarkRunner.class.getResource("/schema.sql"); Path path = Paths.get(Objects.requireNonNull(url).getPath()); String ddl = Files.readString(path); LOG.info("Executing schema statements"); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java new file mode 100644 index 000000000..552babd57 --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -0,0 +1,139 @@ +package com.google.cloud.pgadapter.benchmark; + +import static org.junit.Assert.assertEquals; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import com.google.cloud.pgadapter.benchmark.config.PGAdapterConfiguration; +import com.google.cloud.pgadapter.benchmark.config.SpannerConfiguration; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.Statement; +import com.google.common.base.Stopwatch; +import com.google.common.base.Strings; +import java.io.FileInputStream; +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ThreadLocalRandom; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class SpannerBenchmarkRunner extends AbstractBenchmarkRunner { + private static final Logger LOG = LoggerFactory.getLogger(SpannerBenchmarkRunner.class); + + private final SpannerConfiguration spannerConfiguration; + + private final PGAdapterConfiguration pgAdapterConfiguration; + + private DatabaseClient databaseClient; + + SpannerBenchmarkRunner( + String name, + Statistics statistics, + SpannerConfiguration spannerConfiguration, + PGAdapterConfiguration pgAdapterConfiguration, + BenchmarkConfiguration benchmarkConfiguration) { + super(name, statistics, benchmarkConfiguration); + this.spannerConfiguration = spannerConfiguration; + this.pgAdapterConfiguration = pgAdapterConfiguration; + } + + @Override + public void run() { + try (Spanner spanner = createSpanner()) { + databaseClient = + spanner.getDatabaseClient( + DatabaseId.of( + spannerConfiguration.getProject(), + spannerConfiguration.getInstance(), + spannerConfiguration.getDatabase())); + super.run(); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } + + List loadIdentifiers() { + List result = new ArrayList<>(benchmarkConfiguration.getRecordCount()); + try (ResultSet resultSet = + databaseClient + .singleUse() + .executeQuery(Statement.of("select id from benchmark_all_types"))) { + while (resultSet.next()) { + result.add(resultSet.getString(0)); + } + } + return result; + } + + @Override + String getParameterName(int index) { + return "$" + index; + } + + @Override + void runQuery( + String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations) { + try { + for (int n = 0; n < iterations; n++) { + if (!benchmarkConfiguration.getMaxRandomWait().isZero()) { + long sleepDuration = + ThreadLocalRandom.current() + .nextLong(benchmarkConfiguration.getMaxRandomWait().toMillis()); + Thread.sleep(sleepDuration); + } + String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); + Stopwatch watch = Stopwatch.createStarted(); + Statement statement = Statement.newBuilder(sql).bind("p1").to(id).build(); + if (autoCommit) { + try (ResultSet resultSet = databaseClient.singleUse().executeQuery(statement)) { + consumeResultSet(resultSet); + } + } else { + databaseClient + .readWriteTransaction() + .run( + transaction -> { + try (ResultSet resultSet = transaction.executeQuery(statement)) { + consumeResultSet(resultSet); + } + return 0L; + }); + } + statistics.incOperations(); + durations.add(watch.elapsed()); + } + } catch (InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + } + } + + private void consumeResultSet(ResultSet resultSet) { + while (resultSet.next()) { + for (int col = 0; col < resultSet.getColumnCount(); col++) { + assertEquals( + resultSet.getValue(col), + resultSet.getValue(resultSet.getMetadata().getRowType().getFields(col).getName())); + } + } + } + + private Spanner createSpanner() throws IOException { + SpannerOptions.Builder builder = + SpannerOptions.newBuilder() + .setProjectId(spannerConfiguration.getProject()) + .setNumChannels(pgAdapterConfiguration.getNumChannels()); + if (!Strings.isNullOrEmpty(pgAdapterConfiguration.getCredentials())) { + builder.setCredentials( + GoogleCredentials.fromStream( + new FileInputStream(pgAdapterConfiguration.getCredentials()))); + } + return builder.build().getService(); + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java index 2fd67189c..d496c2e52 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java @@ -10,7 +10,9 @@ class Statistics { private final BenchmarkConfiguration tpccConfiguration; - private final AtomicReference name = new AtomicReference<>("(unknown)"); + private final AtomicReference runnerName = new AtomicReference<>("(unknown)"); + + private final AtomicReference benchmarkName = new AtomicReference<>("(unknown)"); private final AtomicInteger totalOperations = new AtomicInteger(); @@ -29,6 +31,7 @@ void print(Duration totalRuntime) { System.out.print("\033[2J\033[1;1H"); System.out.printf( """ + \rRunner: %s\t \rBenchmark: %s\t \rNum iterations: %d\t \rTotal runtime: %s\t @@ -37,7 +40,8 @@ void print(Duration totalRuntime) { \rRuntime: %s\t \rOperations: %d/%d (%.2f/s)\t """, - getName(), + getRunnerName(), + getBenchmarkName(), tpccConfiguration.getIterations(), totalRuntime, getParallelism(), @@ -47,8 +51,9 @@ void print(Duration totalRuntime) { getOperationsPerSecond(runtime)); } - void reset(String name, int parallelism, int totalOperations) { - setName(name); + void reset(String runnerName, String benchmarkName, int parallelism, int totalOperations) { + setRunnerName(runnerName); + setBenchmarkName(benchmarkName); setParallelism(parallelism); setTotalOperations(totalOperations); operations.set(0L); @@ -59,12 +64,20 @@ Duration getRuntime() { return Duration.between(startTime.get(), Instant.now()); } - String getName() { - return name.get(); + String getRunnerName() { + return runnerName.get(); + } + + private void setRunnerName(String name) { + this.runnerName.set(name); + } + + String getBenchmarkName() { + return benchmarkName.get(); } - private void setName(String name) { - this.name.set(name); + private void setBenchmarkName(String name) { + this.benchmarkName.set(name); } int getTotalOperations() { diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java index ce45a502a..27894a461 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java @@ -14,7 +14,11 @@ public class BenchmarkConfiguration { private boolean truncateBeforeLoad; - private boolean runBenchmark; + private boolean runPgadapterBenchmark; + + private boolean runJdbcBenchmark; + + private boolean runSpannerBenchmark; private Duration benchmarkDuration; @@ -22,6 +26,8 @@ public class BenchmarkConfiguration { private int iterations; + private Duration maxRandomWait; + private List parallelism; private List benchmarks; @@ -55,12 +61,28 @@ public void setTruncateBeforeLoad(boolean truncateBeforeLoad) { this.truncateBeforeLoad = truncateBeforeLoad; } - public boolean isRunBenchmark() { - return runBenchmark; + public boolean isRunPgadapterBenchmark() { + return runPgadapterBenchmark; + } + + public void setRunPgadapterBenchmark(boolean runPgadapterBenchmark) { + this.runPgadapterBenchmark = runPgadapterBenchmark; + } + + public boolean isRunJdbcBenchmark() { + return runJdbcBenchmark; } - public void setRunBenchmark(boolean runBenchmark) { - this.runBenchmark = runBenchmark; + public void setRunJdbcBenchmark(boolean runJdbcBenchmark) { + this.runJdbcBenchmark = runJdbcBenchmark; + } + + public boolean isRunSpannerBenchmark() { + return runSpannerBenchmark; + } + + public void setRunSpannerBenchmark(boolean runSpannerBenchmark) { + this.runSpannerBenchmark = runSpannerBenchmark; } public Duration getBenchmarkDuration() { @@ -87,6 +109,14 @@ public void setIterations(int iterations) { this.iterations = iterations; } + public Duration getMaxRandomWait() { + return maxRandomWait; + } + + public void setMaxRandomWait(Duration maxRandomWait) { + this.maxRandomWait = maxRandomWait; + } + public List getParallelism() { return parallelism; } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index e6ed245eb..7abd9fad6 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -13,8 +13,10 @@ benchmark.load-data-threads=32 # tpcc.load-data also has been enabled. benchmark.truncate-before-load=true -# Run the benchmark. -benchmark.run-benchmark=true +# Run the various benchmarks. +benchmark.run-pgadapter-benchmark=true +benchmark.run-jdbc-benchmark=true +benchmark.run-spanner-benchmark=true # The max duration that the benchmark should run in ISO-8601 notation. # E.g. PT60S (60 seconds), PT10M (10 minutes), PT2H (2 hours), P1D (1 day) benchmark.benchmark-duration=PT300S @@ -25,8 +27,11 @@ benchmark.record-count=1000 # A higher number means a longer execution time, but gives a better indication of how the # performance will be over time. benchmark.iterations=100 +# The maximum random wait time between each iteration. The average wait time will be half this +# value. Set this value to PT0S to disable any wait times between iterations. +benchmark.max-random-wait=PT0S # The number of parallel clients to use for benchmarks. -benchmark.parallelism=1,2 +benchmark.parallelism=4 # The benchmarks to run benchmark.benchmarks=SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction From 1a77e0ea99b9a0d032e401e061c104101d19a6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 1 Jan 2024 15:38:16 +0100 Subject: [PATCH 23/46] feat: add gc stats --- .../benchmark/AbstractBenchmarkRunner.java | 9 ++++++++ .../cloud/pgadapter/benchmark/Statistics.java | 23 ++++++++++++++++++- .../src/main/resources/application.properties | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index 55cd988b8..9b6312a91 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -42,6 +42,7 @@ interface BenchmarkMethod { this.name = name; this.statistics = statistics; this.benchmarkConfiguration = benchmarkConfiguration; + this.benchmarks.put("SelectOneValueAutoCommit", this::benchmarkSelectOneValueAutoCommit); this.benchmarks.put("SelectOneRowAutoCommit", this::benchmarkSelectOneRowAutoCommit); this.benchmarks.put("Select100RowsAutoCommit", this::benchmarkSelect100RowsRowAutoCommit); this.benchmarks.put("SelectOneRowTransaction", this::benchmarkSelectOneRowTransaction); @@ -86,6 +87,14 @@ public void run() { abstract String getParameterName(int index); + void benchmarkSelectOneValueAutoCommit(String name, int parallelism) throws Exception { + benchmarkSelect( + name, + parallelism, + "select col_varchar from benchmark_all_types where id=" + getParameterName(1), + true); + } + void benchmarkSelectOneRowAutoCommit(String name, int parallelism) throws Exception { benchmarkSelect( name, diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java index d496c2e52..0567cf223 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/Statistics.java @@ -1,6 +1,8 @@ package com.google.cloud.pgadapter.benchmark; import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; import java.time.Duration; import java.time.Instant; import java.util.concurrent.atomic.AtomicInteger; @@ -36,7 +38,9 @@ void print(Duration totalRuntime) { \rNum iterations: %d\t \rTotal runtime: %s\t \rParallelism: %d\t - \r + \rNum GCs: %d\t + \rGC time: %s\t + \rRuntime: %s\t \rOperations: %d/%d (%.2f/s)\t """, @@ -45,6 +49,8 @@ void print(Duration totalRuntime) { tpccConfiguration.getIterations(), totalRuntime, getParallelism(), + getGarbageCollections(), + getGarbageCollectionTime(), runtime, getOperations(), getTotalOperations(), @@ -107,4 +113,19 @@ long getOperations() { void incOperations() { operations.incrementAndGet(); } + + long getGarbageCollections() { + return ManagementFactory.getGarbageCollectorMXBeans().stream() + .map(GarbageCollectorMXBean::getCollectionCount) + .reduce(Long::sum) + .orElse(0L); + } + + Duration getGarbageCollectionTime() { + return ManagementFactory.getGarbageCollectorMXBeans().stream() + .map(GarbageCollectorMXBean::getCollectionTime) + .reduce(Long::sum) + .map(Duration::ofMillis) + .orElse(Duration.ZERO); + } } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index 7abd9fad6..a36f9792d 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -33,7 +33,7 @@ benchmark.max-random-wait=PT0S # The number of parallel clients to use for benchmarks. benchmark.parallelism=4 # The benchmarks to run -benchmark.benchmarks=SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction +benchmark.benchmarks=SelectOneValueAutoCommit,SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction # --- Possible optimizations for TPC-C --- # benchmark.use-read-only-transactions=false From 02d109701aec24d1aa775c91c9f1c615228d33e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Tue, 2 Jan 2024 07:41:01 +0100 Subject: [PATCH 24/46] feat: add benchmark runner for generated client --- benchmarks/client-comparisons/pom.xml | 5 + .../benchmark/AbstractBenchmarkRunner.java | 5 +- .../benchmark/BenchmarkApplication.java | 14 +- .../benchmark/GapicBenchmarkRunner.java | 206 ++++++++++++++++++ .../benchmark/SpannerBenchmarkRunner.java | 15 +- .../config/BenchmarkConfiguration.java | 10 + .../config/SpannerConfiguration.java | 20 ++ .../src/main/resources/application.properties | 22 +- 8 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java diff --git a/benchmarks/client-comparisons/pom.xml b/benchmarks/client-comparisons/pom.xml index c6cb1650e..abc2c590e 100644 --- a/benchmarks/client-comparisons/pom.xml +++ b/benchmarks/client-comparisons/pom.xml @@ -34,6 +34,11 @@ 2.15.0 + + com.google.cloud + google-cloud-spanner + 6.55.1-SNAPSHOT + com.google.cloud google-cloud-spanner-pgadapter diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index 9b6312a91..079fa8364 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -1,5 +1,6 @@ package com.google.cloud.pgadapter.benchmark; +import static com.google.cloud.spanner.connection.ThreadFactoryUtil.createVirtualOrDaemonThreadFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -134,7 +135,9 @@ void benchmarkSelect(String benchmarkName, int parallelism, String sql, boolean ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); - ExecutorService executor = Executors.newFixedThreadPool(parallelism); + ExecutorService executor = + Executors.newFixedThreadPool( + parallelism, createVirtualOrDaemonThreadFactory("benchmark-worker")); for (int task = 0; task < parallelism; task++) { executor.submit( () -> runQuery(sql, autoCommit, benchmarkConfiguration.getIterations(), durations)); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index be2bf69c8..b0612e900 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -92,7 +92,8 @@ public void run(String... args) throws Exception { if (benchmarkConfiguration.isRunPgadapterBenchmark() || benchmarkConfiguration.isRunJdbcBenchmark() - || benchmarkConfiguration.isRunSpannerBenchmark()) { + || benchmarkConfiguration.isRunSpannerBenchmark() + || benchmarkConfiguration.isRunGapicBenchmark()) { List runners = new ArrayList<>(); LOG.info("Starting benchmarks"); Statistics statistics = new Statistics(benchmarkConfiguration); @@ -129,6 +130,17 @@ public void run(String... args) throws Exception { executor.submit(runner); runners.add(runner); } + if (benchmarkConfiguration.isRunGapicBenchmark()) { + GapicBenchmarkRunner runner = + new GapicBenchmarkRunner( + "Generated Client Benchmarks", + statistics, + benchmarkConfiguration, + pgAdapterConfiguration, + spannerConfiguration); + executor.submit(runner); + runners.add(runner); + } executor.shutdown(); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java new file mode 100644 index 000000000..4fa6677fa --- /dev/null +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java @@ -0,0 +1,206 @@ +package com.google.cloud.pgadapter.benchmark; + +import static com.google.cloud.spanner.connection.ThreadFactoryUtil.createVirtualOrDaemonThreadFactory; +import static org.junit.Assert.assertEquals; + +import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.api.gax.grpc.ChannelPoolSettings; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import com.google.cloud.pgadapter.benchmark.config.PGAdapterConfiguration; +import com.google.cloud.pgadapter.benchmark.config.SpannerConfiguration; +import com.google.cloud.spanner.v1.SpannerClient; +import com.google.cloud.spanner.v1.SpannerSettings; +import com.google.common.base.Stopwatch; +import com.google.protobuf.ListValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.google.spanner.v1.CommitRequest; +import com.google.spanner.v1.CreateSessionRequest; +import com.google.spanner.v1.ExecuteSqlRequest; +import com.google.spanner.v1.PartialResultSet; +import com.google.spanner.v1.ResultSet; +import com.google.spanner.v1.Session; +import com.google.spanner.v1.TransactionOptions; +import com.google.spanner.v1.TransactionOptions.ReadWrite; +import com.google.spanner.v1.TransactionSelector; +import java.io.FileInputStream; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; + +class GapicBenchmarkRunner extends AbstractBenchmarkRunner { + + private final SpannerConfiguration spannerConfiguration; + + private final PGAdapterConfiguration pgAdapterConfiguration; + + private SpannerClient client; + + GapicBenchmarkRunner( + String name, + Statistics statistics, + BenchmarkConfiguration benchmarkConfiguration, + PGAdapterConfiguration pgAdapterConfiguration, + SpannerConfiguration spannerConfiguration) { + super(name, statistics, benchmarkConfiguration); + this.spannerConfiguration = spannerConfiguration; + this.pgAdapterConfiguration = pgAdapterConfiguration; + } + + @Override + public void run() { + try (SpannerClient client = createClient()) { + this.client = client; + super.run(); + client.shutdown(); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + @Override + List loadIdentifiers() { + try { + List result = new ArrayList<>(benchmarkConfiguration.getRecordCount()); + Session session = + client.createSession( + CreateSessionRequest.newBuilder() + .setDatabase(spannerConfiguration.getDatabaseId().getName()) + .build()); + Iterator iterator = + client + .executeStreamingSqlCallable() + .call( + ExecuteSqlRequest.newBuilder() + .setSql("select id from benchmark_all_types") + .setSession(session.getName()) + .build()) + .iterator(); + PartialResultSet previous = null; + while (iterator.hasNext()) { + PartialResultSet resultSet = iterator.next(); + int firstIndex = (previous != null && previous.getChunkedValue()) ? 1 : 0; + int lastIndex = + resultSet.getChunkedValue() + ? (resultSet.getValuesCount() - 1) + : resultSet.getValuesCount(); + if (firstIndex == 1) { + result.add( + previous.getValues(previous.getValuesCount() - 1).getStringValue() + + resultSet.getValues(0).getStringValue()); + } + for (int row = firstIndex; row < lastIndex; row++) { + result.add(resultSet.getValues(row).getStringValue()); + } + previous = resultSet; + } + client.deleteSession(session.getName()); + return result; + } catch (Exception exception) { + exception.printStackTrace(); + throw new RuntimeException(exception); + } + } + + @Override + String getParameterName(int index) { + return "$" + index; + } + + @Override + void runQuery( + String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations) { + try { + Session session = + client.createSession( + CreateSessionRequest.newBuilder() + .setDatabase(spannerConfiguration.getDatabaseId().getName()) + .build()); + for (int n = 0; n < iterations; n++) { + if (!benchmarkConfiguration.getMaxRandomWait().isZero()) { + long sleepDuration = + ThreadLocalRandom.current() + .nextLong(benchmarkConfiguration.getMaxRandomWait().toMillis()); + Thread.sleep(sleepDuration); + } + String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); + Stopwatch watch = Stopwatch.createStarted(); + ExecuteSqlRequest.Builder builder = + ExecuteSqlRequest.newBuilder() + .setSession(session.getName()) + .setSql(sql) + .setSeqno(1L) + .setParams( + Struct.newBuilder() + .putFields("p1", Value.newBuilder().setStringValue(id).build()) + .build()); + if (!autoCommit) { + builder.setTransaction( + TransactionSelector.newBuilder() + .setBegin( + TransactionOptions.newBuilder() + .setReadWrite(ReadWrite.newBuilder().build()) + .build()) + .build()); + } + ResultSet resultSet = client.executeSql(builder.build()); + consumeResultSet(resultSet); + if (!autoCommit) { + client.commit( + CommitRequest.newBuilder() + .setSession(session.getName()) + .setTransactionId(resultSet.getMetadata().getTransaction().getId()) + .build()); + } + statistics.incOperations(); + durations.add(watch.elapsed()); + } + client.deleteSession(session.getName()); + } catch (InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + } catch (Exception exception) { + exception.printStackTrace(); + throw new RuntimeException(exception); + } + } + + private void consumeResultSet(ResultSet resultSet) { + for (ListValue row : resultSet.getRowsList()) { + int col = 0; + for (Value value : row.getValuesList()) { + assertEquals(value, row.getValues(col)); + col++; + } + } + } + + SpannerClient createClient() throws Exception { + InstantiatingGrpcChannelProvider channelProvider = + InstantiatingGrpcChannelProvider.newBuilder() + .setEndpoint(SpannerSettings.getDefaultEndpoint()) + .setExecutor( + Executors.newCachedThreadPool( + createVirtualOrDaemonThreadFactory("grpc-virtual-executor"))) + .setChannelPoolSettings( + ChannelPoolSettings.builder() + .setInitialChannelCount(pgAdapterConfiguration.getNumChannels()) + .setMaxChannelCount(pgAdapterConfiguration.getNumChannels()) + .setPreemptiveRefreshEnabled(true) + .build()) + .build(); + return SpannerClient.create( + SpannerSettings.newBuilder() + .setCredentialsProvider( + FixedCredentialsProvider.create( + GoogleCredentials.fromStream( + new FileInputStream(pgAdapterConfiguration.getCredentials())))) + .setTransportChannelProvider(channelProvider) + .build()); + } +} diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java index 552babd57..9b4b224ff 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -9,6 +9,7 @@ import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.SessionPoolOptions; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; @@ -128,7 +129,19 @@ private Spanner createSpanner() throws IOException { SpannerOptions.Builder builder = SpannerOptions.newBuilder() .setProjectId(spannerConfiguration.getProject()) - .setNumChannels(pgAdapterConfiguration.getNumChannels()); + .setNumChannels(pgAdapterConfiguration.getNumChannels()) + .setSessionPoolOption( + SessionPoolOptions.newBuilder() + .setMinSessions( + benchmarkConfiguration.getParallelism().stream() + .max(Integer::compare) + .orElse(100)) + .setMaxSessions( + benchmarkConfiguration.getParallelism().stream() + .max(Integer::compare) + .orElse(400)) + .build()) + .setUseVirtualThreads(spannerConfiguration.isUseVirtualThreads()); if (!Strings.isNullOrEmpty(pgAdapterConfiguration.getCredentials())) { builder.setCredentials( GoogleCredentials.fromStream( diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java index 27894a461..ac086179d 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java @@ -20,6 +20,8 @@ public class BenchmarkConfiguration { private boolean runSpannerBenchmark; + private boolean runGapicBenchmark; + private Duration benchmarkDuration; private int recordCount; @@ -85,6 +87,14 @@ public void setRunSpannerBenchmark(boolean runSpannerBenchmark) { this.runSpannerBenchmark = runSpannerBenchmark; } + public boolean isRunGapicBenchmark() { + return runGapicBenchmark; + } + + public void setRunGapicBenchmark(boolean runGapicBenchmark) { + this.runGapicBenchmark = runGapicBenchmark; + } + public Duration getBenchmarkDuration() { return benchmarkDuration; } diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java index 467190757..bb518a20d 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java @@ -1,5 +1,8 @@ package com.google.cloud.pgadapter.benchmark.config; +import com.google.cloud.spanner.DatabaseId; +import com.google.common.base.Suppliers; +import java.util.function.Supplier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -10,6 +13,15 @@ public class SpannerConfiguration { private String instance; private String database; + private boolean useVirtualThreads; + + private final Supplier databaseIdSupplier = + Suppliers.memoize(() -> DatabaseId.of(project, instance, database)); + + public DatabaseId getDatabaseId() { + return databaseIdSupplier.get(); + } + public String getProject() { return project; } @@ -33,4 +45,12 @@ public String getDatabase() { public void setDatabase(String database) { this.database = database; } + + public boolean isUseVirtualThreads() { + return useVirtualThreads; + } + + public void setUseVirtualThreads(boolean useVirtualThreads) { + this.useVirtualThreads = useVirtualThreads; + } } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index a36f9792d..908ce8c6e 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -14,26 +14,28 @@ benchmark.load-data-threads=32 benchmark.truncate-before-load=true # Run the various benchmarks. -benchmark.run-pgadapter-benchmark=true -benchmark.run-jdbc-benchmark=true -benchmark.run-spanner-benchmark=true +benchmark.run-pgadapter-benchmark=false +benchmark.run-jdbc-benchmark=false +benchmark.run-spanner-benchmark=false +benchmark.run-gapic-benchmark=true # The max duration that the benchmark should run in ISO-8601 notation. # E.g. PT60S (60 seconds), PT10M (10 minutes), PT2H (2 hours), P1D (1 day) -benchmark.benchmark-duration=PT300S +benchmark.benchmark-duration=PT2H # The number of rows to load into the benchmark table. -benchmark.record-count=1000 +benchmark.record-count=1000000 # The number of iterations that each benchmark should run. # A higher number means a longer execution time, but gives a better indication of how the # performance will be over time. -benchmark.iterations=100 +benchmark.iterations=2000 # The maximum random wait time between each iteration. The average wait time will be half this # value. Set this value to PT0S to disable any wait times between iterations. benchmark.max-random-wait=PT0S # The number of parallel clients to use for benchmarks. -benchmark.parallelism=4 +benchmark.parallelism=1200 # The benchmarks to run -benchmark.benchmarks=SelectOneValueAutoCommit,SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction +benchmark.benchmarks=SelectOneValueAutoCommit + #,SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction # --- Possible optimizations for TPC-C --- # benchmark.use-read-only-transactions=false @@ -43,7 +45,7 @@ benchmark.lock-scanned-ranges=false spanner.project=appdev-soda-spanner-staging spanner.instance=knut-test-ycsb spanner.database=knut-test-db - +spanner.use-virtual-threads=false # --- IN-PROCESS PGADAPTER --- # @@ -52,7 +54,7 @@ spanner.database=knut-test-db # Set this to true to instruct the benchmark runner to start a PGAdapter instance in-process with # the benchmark application. pgadapter.in-process=true -pgadapter.num-channels=4 +pgadapter.num-channels=50 # Set this if you want the in-process PGAdapter instance to use a specific service account # credentials file. From ca50c5e75ca267c11d26d41166111e0c358ff301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Tue, 2 Jan 2024 07:46:59 +0100 Subject: [PATCH 25/46] fix: remove virtual threads config --- .../cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java index 9b4b224ff..52867e001 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -140,8 +140,7 @@ private Spanner createSpanner() throws IOException { benchmarkConfiguration.getParallelism().stream() .max(Integer::compare) .orElse(400)) - .build()) - .setUseVirtualThreads(spannerConfiguration.isUseVirtualThreads()); + .build()); if (!Strings.isNullOrEmpty(pgAdapterConfiguration.getCredentials())) { builder.setCredentials( GoogleCredentials.fromStream( From 02dc49e22cedd2bf1e1d7d035362d429c9034f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sat, 6 Jan 2024 16:36:32 +0100 Subject: [PATCH 26/46] perf: randomize pool for high qps --- .../benchmark/BenchmarkApplication.java | 4 ++-- .../benchmark/SpannerBenchmarkRunner.java | 17 +++++++++++------ .../src/main/resources/application.properties | 10 +++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index b0612e900..a93a906f0 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -71,8 +71,8 @@ public void run(String... args) throws Exception { spannerConfiguration.getDatabase(), pgAdapterConfiguration.getNumChannels()); try { - SchemaService schemaService = new SchemaService(pgAdapterConnectionUrl); - schemaService.createSchema(); + // SchemaService schemaService = new SchemaService(pgAdapterConnectionUrl); + // schemaService.createSchema(); if (benchmarkConfiguration.isLoadData()) { LOG.info("Starting data load"); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java index 52867e001..594666c39 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -15,6 +15,7 @@ import com.google.cloud.spanner.Statement; import com.google.common.base.Stopwatch; import com.google.common.base.Strings; +import com.google.common.util.concurrent.Uninterruptibles; import java.io.FileInputStream; import java.io.IOException; import java.time.Duration; @@ -54,6 +55,7 @@ public void run() { spannerConfiguration.getProject(), spannerConfiguration.getInstance(), spannerConfiguration.getDatabase())); + Uninterruptibles.sleepUninterruptibly(Duration.ofMillis(2000L)); super.run(); } catch (IOException exception) { throw new RuntimeException(exception); @@ -130,16 +132,19 @@ private Spanner createSpanner() throws IOException { SpannerOptions.newBuilder() .setProjectId(spannerConfiguration.getProject()) .setNumChannels(pgAdapterConfiguration.getNumChannels()) + .setUseVirtualThreads(spannerConfiguration.isUseVirtualThreads()) .setSessionPoolOption( SessionPoolOptions.newBuilder() .setMinSessions( - benchmarkConfiguration.getParallelism().stream() - .max(Integer::compare) - .orElse(100)) + 2 + * benchmarkConfiguration.getParallelism().stream() + .max(Integer::compare) + .orElse(100)) .setMaxSessions( - benchmarkConfiguration.getParallelism().stream() - .max(Integer::compare) - .orElse(400)) + 2 + * benchmarkConfiguration.getParallelism().stream() + .max(Integer::compare) + .orElse(400)) .build()); if (!Strings.isNullOrEmpty(pgAdapterConfiguration.getCredentials())) { builder.setCredentials( diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index 908ce8c6e..fcd90ba4e 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -16,8 +16,8 @@ benchmark.truncate-before-load=true # Run the various benchmarks. benchmark.run-pgadapter-benchmark=false benchmark.run-jdbc-benchmark=false -benchmark.run-spanner-benchmark=false -benchmark.run-gapic-benchmark=true +benchmark.run-spanner-benchmark=true +benchmark.run-gapic-benchmark=false # The max duration that the benchmark should run in ISO-8601 notation. # E.g. PT60S (60 seconds), PT10M (10 minutes), PT2H (2 hours), P1D (1 day) benchmark.benchmark-duration=PT2H @@ -32,7 +32,7 @@ benchmark.iterations=2000 # value. Set this value to PT0S to disable any wait times between iterations. benchmark.max-random-wait=PT0S # The number of parallel clients to use for benchmarks. -benchmark.parallelism=1200 +benchmark.parallelism=2000 # The benchmarks to run benchmark.benchmarks=SelectOneValueAutoCommit #,SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction @@ -45,7 +45,7 @@ benchmark.lock-scanned-ranges=false spanner.project=appdev-soda-spanner-staging spanner.instance=knut-test-ycsb spanner.database=knut-test-db -spanner.use-virtual-threads=false +spanner.use-virtual-threads=true # --- IN-PROCESS PGADAPTER --- # @@ -54,7 +54,7 @@ spanner.use-virtual-threads=false # Set this to true to instruct the benchmark runner to start a PGAdapter instance in-process with # the benchmark application. pgadapter.in-process=true -pgadapter.num-channels=50 +pgadapter.num-channels=32 # Set this if you want the in-process PGAdapter instance to use a specific service account # credentials file. From d9c0a05e157ca9baafa07ee3f1cc541067ba4fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sat, 6 Jan 2024 21:53:49 +0100 Subject: [PATCH 27/46] feat: set min/max sessions for connections --- .../benchmark/AbstractBenchmarkRunner.java | 9 ++++---- .../benchmark/BenchmarkApplication.java | 22 +++++++++++++++---- .../benchmark/GapicBenchmarkRunner.java | 2 +- .../src/main/resources/application.properties | 4 ++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index 079fa8364..f6d98afe2 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -1,6 +1,5 @@ package com.google.cloud.pgadapter.benchmark; -import static com.google.cloud.spanner.connection.ThreadFactoryUtil.createVirtualOrDaemonThreadFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -135,9 +134,11 @@ void benchmarkSelect(String benchmarkName, int parallelism, String sql, boolean ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); - ExecutorService executor = - Executors.newFixedThreadPool( - parallelism, createVirtualOrDaemonThreadFactory("benchmark-worker")); + // ExecutorService executor = + // Executors.newFixedThreadPool( + // parallelism, createVirtualOrDaemonThreadFactory("benchmark-worker")); + ExecutorService executor = Executors.newFixedThreadPool(parallelism); + for (int task = 0; task < parallelism; task++) { executor.submit( () -> runQuery(sql, autoCommit, benchmarkConfiguration.getIterations(), durations)); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index a93a906f0..5a1041ea7 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -5,6 +5,7 @@ import com.google.cloud.pgadapter.benchmark.config.SpannerConfiguration; import com.google.cloud.pgadapter.benchmark.dataloader.DataLoadStatus; import com.google.cloud.pgadapter.benchmark.dataloader.DataLoader; +import com.google.cloud.spanner.SessionPoolOptions; import com.google.cloud.spanner.pgadapter.ProxyServer; import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata; import com.google.common.base.Stopwatch; @@ -62,17 +63,19 @@ public void run(String... args) throws Exception { server.getLocalPort(), spannerConfiguration.getDatabase()); String spannerConnectionUrl = String.format( - "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?numChannels=%d" + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?numChannels=%d;minSessions=%d;maxSessions=%d" + (pgAdapterConfiguration.getCredentials() == null ? "" : ";credentials=" + pgAdapterConfiguration.getCredentials()), spannerConfiguration.getProject(), spannerConfiguration.getInstance(), spannerConfiguration.getDatabase(), - pgAdapterConfiguration.getNumChannels()); + pgAdapterConfiguration.getNumChannels(), + benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(100), + benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(400)); try { - // SchemaService schemaService = new SchemaService(pgAdapterConnectionUrl); - // schemaService.createSchema(); + SchemaService schemaService = new SchemaService(pgAdapterConnectionUrl); + schemaService.createSchema(); if (benchmarkConfiguration.isLoadData()) { LOG.info("Starting data load"); @@ -184,6 +187,17 @@ private ProxyServer startPGAdapter() { .setInstance(spannerConfiguration.getInstance()) .setDatabase(spannerConfiguration.getDatabase()) .setNumChannels(pgAdapterConfiguration.getNumChannels()) + .setSessionPoolOptions( + SessionPoolOptions.newBuilder() + .setMinSessions( + benchmarkConfiguration.getParallelism().stream() + .max(Integer::compare) + .orElse(100)) + .setMaxSessions( + benchmarkConfiguration.getParallelism().stream() + .max(Integer::compare) + .orElse(400)) + .build()) .disableUnixDomainSockets(); if (pgAdapterConfiguration.isEnableOpenTelemetry()) { builder diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java index 4fa6677fa..58c786205 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java @@ -1,6 +1,6 @@ package com.google.cloud.pgadapter.benchmark; -import static com.google.cloud.spanner.connection.ThreadFactoryUtil.createVirtualOrDaemonThreadFactory; +import static com.google.cloud.spanner.ThreadFactoryUtil.createVirtualOrDaemonThreadFactory; import static org.junit.Assert.assertEquals; import com.google.api.gax.core.FixedCredentialsProvider; diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index fcd90ba4e..3dcab3ca8 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -14,9 +14,9 @@ benchmark.load-data-threads=32 benchmark.truncate-before-load=true # Run the various benchmarks. -benchmark.run-pgadapter-benchmark=false +benchmark.run-pgadapter-benchmark=true benchmark.run-jdbc-benchmark=false -benchmark.run-spanner-benchmark=true +benchmark.run-spanner-benchmark=false benchmark.run-gapic-benchmark=false # The max duration that the benchmark should run in ISO-8601 notation. # E.g. PT60S (60 seconds), PT10M (10 minutes), PT2H (2 hours), P1D (1 day) From 888f56f7a865ff3b925a261a45183d86e18da16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 7 Jan 2024 22:01:24 +0100 Subject: [PATCH 28/46] chore: make virtual threads configurable --- .../benchmark/AbstractBenchmarkRunner.java | 10 +++++-- .../benchmark/BenchmarkApplication.java | 1 + .../benchmark/GapicBenchmarkRunner.java | 7 +++-- .../benchmark/JdbcBenchmarkRunner.java | 3 +- .../benchmark/SpannerBenchmarkRunner.java | 3 +- .../spanner/pgadapter/ConnectionHandler.java | 1 + .../cloud/spanner/pgadapter/ProxyServer.java | 9 ++++-- .../pgadapter/metadata/OptionsMetadata.java | 30 +++++++++++++++++++ .../pgadapter/utils/MutationWriter.java | 2 +- 9 files changed, 55 insertions(+), 11 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index f6d98afe2..99957aa50 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -139,12 +140,17 @@ void benchmarkSelect(String benchmarkName, int parallelism, String sql, boolean // parallelism, createVirtualOrDaemonThreadFactory("benchmark-worker")); ExecutorService executor = Executors.newFixedThreadPool(parallelism); + List> futures = new ArrayList<>(parallelism); for (int task = 0; task < parallelism; task++) { - executor.submit( - () -> runQuery(sql, autoCommit, benchmarkConfiguration.getIterations(), durations)); + futures.add( + executor.submit( + () -> runQuery(sql, autoCommit, benchmarkConfiguration.getIterations(), durations))); } executor.shutdown(); assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); + for (Future future : futures) { + future.get(); + } assertEquals(totalOperations, durations.size()); BenchmarkResult result = new BenchmarkResult(benchmarkName, parallelism, durations); results.add(result); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index 5a1041ea7..db43bbbf5 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -198,6 +198,7 @@ private ProxyServer startPGAdapter() { .max(Integer::compare) .orElse(400)) .build()) + .setDisableVirtualThreads(!spannerConfiguration.isUseVirtualThreads()) .disableUnixDomainSockets(); if (pgAdapterConfiguration.isEnableOpenTelemetry()) { builder diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java index 58c786205..c62241ffb 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java @@ -10,6 +10,7 @@ import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; import com.google.cloud.pgadapter.benchmark.config.PGAdapterConfiguration; import com.google.cloud.pgadapter.benchmark.config.SpannerConfiguration; +import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.v1.SpannerClient; import com.google.cloud.spanner.v1.SpannerSettings; import com.google.common.base.Stopwatch; @@ -163,9 +164,8 @@ void runQuery( } client.deleteSession(session.getName()); } catch (InterruptedException interruptedException) { - Thread.currentThread().interrupt(); + throw SpannerExceptionFactory.propagateInterrupt(interruptedException); } catch (Exception exception) { - exception.printStackTrace(); throw new RuntimeException(exception); } } @@ -186,7 +186,8 @@ SpannerClient createClient() throws Exception { .setEndpoint(SpannerSettings.getDefaultEndpoint()) .setExecutor( Executors.newCachedThreadPool( - createVirtualOrDaemonThreadFactory("grpc-virtual-executor"))) + createVirtualOrDaemonThreadFactory( + "grpc-virtual-executor", spannerConfiguration.isUseVirtualThreads()))) .setChannelPoolSettings( ChannelPoolSettings.builder() .setInitialChannelCount(pgAdapterConfiguration.getNumChannels()) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java index e7c8d76f0..b23b7d34e 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; +import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.jdbc.CloudSpannerJdbcConnection; import com.google.common.base.Stopwatch; import java.lang.reflect.Array; @@ -107,7 +108,7 @@ void runQuery( } catch (SQLException exception) { throw new RuntimeException(exception); } catch (InterruptedException interruptedException) { - Thread.currentThread().interrupt(); + throw SpannerExceptionFactory.propagateInterrupt(interruptedException); } } } diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java index 594666c39..a984344d6 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -11,6 +11,7 @@ import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.SessionPoolOptions; import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; import com.google.common.base.Stopwatch; @@ -113,7 +114,7 @@ void runQuery( durations.add(watch.elapsed()); } } catch (InterruptedException interruptedException) { - Thread.currentThread().interrupt(); + throw SpannerExceptionFactory.propagateInterrupt(interruptedException); } } 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 315cbd2b9..9526fda02 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java @@ -264,6 +264,7 @@ static String buildConnectionURL( // only used to determine what dialect the database should have that is being created on the // emulator when 'autoConfigEmulator=true'. uri = uri + ";dialect=postgresql"; + uri = uri + ";useVirtualThreads=" + options.useVirtualThreads(); if (System.getProperty(CHANNEL_PROVIDER_PROPERTY) != null) { uri = uri diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java index 6d1a4f7c8..3e179957f 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java @@ -127,12 +127,15 @@ public ProxyServer( this.localPort = optionsMetadata.getProxyPort(); this.properties = properties; this.debugMode = optionsMetadata.isDebugMode(); - this.threadFactory = createThreadFactory("ConnectionHandler"); + this.threadFactory = + createThreadFactory("ConnectionHandler", optionsMetadata.useVirtualThreads()); addConnectionProperties(); } - public static ThreadFactory createThreadFactory(String baseNameFormat) { - ThreadFactory virtualThreadFactory = tryCreateVirtualThreadFactory(baseNameFormat); + public static ThreadFactory createThreadFactory( + String baseNameFormat, boolean tryVirtualThreads) { + ThreadFactory virtualThreadFactory = + tryVirtualThreads ? tryCreateVirtualThreadFactory(baseNameFormat) : null; if (virtualThreadFactory != null) { return virtualThreadFactory; } diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java index 8dcc56de9..975e326eb 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java @@ -83,6 +83,7 @@ public static class Builder { private SslMode sslMode; private int port; private String unixDomainSocketDirectory; + private boolean disableVirtualThreads; private boolean autoConfigEmulator; private boolean debugMode; private String endpoint; @@ -295,6 +296,17 @@ public Builder disableUnixDomainSockets() { return this; } + /** + * Disables the use of virtual threads for connection handlers. Virtual threads are only + * supported on Java 21 and higher (and in the standard Docker image), and is automatically + * disabled for Java versions prior to 21. Use this method to explicitly disable it for Java 21 + * and higher. + */ + public Builder setDisableVirtualThreads(boolean disableVirtualThreads) { + this.disableVirtualThreads = disableVirtualThreads; + return this; + } + /** * Instructs PGAdapter to connect to the Cloud Spanner emulator and to automatically create the * instance and the database in the connection request if the instance or database does not yet @@ -376,6 +388,9 @@ private String[] toCommandLineArguments() { if (skipLocalhostCheck) { addOption(args, OPTION_DISABLE_LOCALHOST_CHECK); } + if (disableVirtualThreads) { + addOption(args, OPTION_DISABLE_VIRTUAL_THREADS); + } if (sslMode != null) { addLongOption(args, OPTION_SSL, sslMode.name()); } @@ -494,6 +509,7 @@ public enum DdlTransactionMode { private static final String OPTION_OPEN_TELEMETRY_TRACE_RATIO = "otel_trace_ratio"; private static final String OPTION_SSL = "ssl"; private static final String OPTION_DISABLE_AUTO_DETECT_CLIENT = "disable_auto_detect_client"; + private static final String OPTION_DISABLE_VIRTUAL_THREADS = "disable_virtual_threads"; private static final String OPTION_DISABLE_DEFAULT_LOCAL_STATEMENTS = "disable_default_local_statements"; private static final String OPTION_DISABLE_PG_CATALOG_REPLACEMENTS = @@ -539,6 +555,7 @@ public enum DdlTransactionMode { private final boolean disableAutoDetectClient; private final boolean disableDefaultLocalStatements; private final boolean disablePgCatalogReplacements; + private final boolean disableVirtualThreads; private final boolean requiresMatcher; private final DdlTransactionMode ddlTransactionMode; private final boolean replaceJdbcMetadataQueries; @@ -624,6 +641,7 @@ private OptionsMetadata(Builder builder) { commandLine.hasOption(OPTION_DISABLE_DEFAULT_LOCAL_STATEMENTS); this.disablePgCatalogReplacements = commandLine.hasOption(OPTION_DISABLE_PG_CATALOG_REPLACEMENTS); + this.disableVirtualThreads = commandLine.hasOption(OPTION_DISABLE_VIRTUAL_THREADS); this.requiresMatcher = commandLine.hasOption(OPTION_PSQL_MODE) || commandLine.hasOption(OPTION_COMMAND_METADATA_FILE); @@ -698,6 +716,7 @@ public OptionsMetadata( this.disableAutoDetectClient = false; this.disableDefaultLocalStatements = false; this.disablePgCatalogReplacements = false; + this.disableVirtualThreads = false; this.requiresMatcher = requiresMatcher; this.ddlTransactionMode = DdlTransactionMode.AutocommitImplicitTransaction; this.replaceJdbcMetadataQueries = replaceJdbcMetadataQueries; @@ -1163,6 +1182,13 @@ private CommandLine buildOptions(String[] args) { + "have been tested with PGAdapter have been tested using the default value for this option. Changing " + "the value of this option could cause a client or driver to alter its behavior and cause unexpected " + "errors when used with PGAdapter."); + options.addOption( + null, + OPTION_DISABLE_VIRTUAL_THREADS, + false, + "PGAdapter by default uses virtual threads for connection handlers. Virtual threads use " + + "less memory and other system resources, and allows PGAdapter to handle more connections " + + "simultaneously than when using platform threads."); options.addOption( OPTION_INTERNAL_DEBUG_MODE, "internal-debug-mode", @@ -1382,6 +1408,10 @@ public boolean replacePgCatalogTables() { return !this.disablePgCatalogReplacements; } + public boolean useVirtualThreads() { + return !this.disableVirtualThreads; + } + public boolean requiresMatcher() { return this.requiresMatcher; } diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java b/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java index cba8ae8a2..b48f3034f 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java @@ -100,7 +100,7 @@ public enum CopyTransactionMode { private static final Logger logger = Logger.getLogger(MutationWriter.class.getName()); - private static final ThreadFactory THREAD_FACTORY = createThreadFactory("copy-worker"); + private static final ThreadFactory THREAD_FACTORY = createThreadFactory("copy-worker", true); private final CopyTransactionMode transactionMode; private long rowCount; From b308003413d0525e60ec8df2abca8a04d90b5a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 8 Jan 2024 12:12:41 +0100 Subject: [PATCH 29/46] chore: make everything configurable --- benchmarks/client-comparisons/pom.xml | 7 +++- .../benchmark/BenchmarkApplication.java | 8 +++- .../benchmark/SpannerBenchmarkRunner.java | 5 +++ .../config/SpannerConfiguration.java | 42 +++++++++++++++++++ .../src/main/resources/application.properties | 16 ++++--- pom.xml | 2 +- 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/benchmarks/client-comparisons/pom.xml b/benchmarks/client-comparisons/pom.xml index abc2c590e..c4903a8ad 100644 --- a/benchmarks/client-comparisons/pom.xml +++ b/benchmarks/client-comparisons/pom.xml @@ -37,7 +37,7 @@ com.google.cloud google-cloud-spanner - 6.55.1-SNAPSHOT + 6.56.0 com.google.cloud @@ -49,6 +49,11 @@ commons-lang3 3.14.0 + + org.springframework.boot + spring-boot-configuration-processor + true + diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index db43bbbf5..655c3ee6e 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -63,7 +63,7 @@ public void run(String... args) throws Exception { server.getLocalPort(), spannerConfiguration.getDatabase()); String spannerConnectionUrl = String.format( - "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?numChannels=%d;minSessions=%d;maxSessions=%d" + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?numChannels=%d;minSessions=%d;maxSessions=%d;useVirtualThreads=%s;useStickySessions=%s" + (pgAdapterConfiguration.getCredentials() == null ? "" : ";credentials=" + pgAdapterConfiguration.getCredentials()), @@ -72,10 +72,14 @@ public void run(String... args) throws Exception { spannerConfiguration.getDatabase(), pgAdapterConfiguration.getNumChannels(), benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(100), - benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(400)); + benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(400), + spannerConfiguration.isUseVirtualThreads(), + spannerConfiguration.isUseStickySessionClient()); try { + System.out.println("Checking schema"); SchemaService schemaService = new SchemaService(pgAdapterConnectionUrl); schemaService.createSchema(); + System.out.println("Checked schema, starting benchmark"); if (benchmarkConfiguration.isLoadData()) { LOG.info("Starting data load"); diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java index a984344d6..702a6e42a 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -146,6 +146,11 @@ private Spanner createSpanner() throws IOException { * benchmarkConfiguration.getParallelism().stream() .max(Integer::compare) .orElse(400)) + .setOptimizeSessionPoolFuture( + spannerConfiguration.isOptimizeSessionPoolFuture()) + .setOptimizeUnbalancedCheck(spannerConfiguration.isOptimizeUnbalancedCheck()) + .setRandomizePositionTransactionsPerSecondThreshold( + spannerConfiguration.getRandomizePositionTransactionsPerSecondThreshold()) .build()); if (!Strings.isNullOrEmpty(pgAdapterConfiguration.getCredentials())) { builder.setCredentials( diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java index bb518a20d..f69afe677 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java @@ -14,6 +14,10 @@ public class SpannerConfiguration { private String database; private boolean useVirtualThreads; + private long randomizePositionTransactionsPerSecondThreshold; + private boolean optimizeSessionPoolFuture; + private boolean optimizeUnbalancedCheck; + private boolean useStickySessionClient; private final Supplier databaseIdSupplier = Suppliers.memoize(() -> DatabaseId.of(project, instance, database)); @@ -53,4 +57,42 @@ public boolean isUseVirtualThreads() { public void setUseVirtualThreads(boolean useVirtualThreads) { this.useVirtualThreads = useVirtualThreads; } + + public long getRandomizePositionTransactionsPerSecondThreshold() { + return randomizePositionTransactionsPerSecondThreshold; + } + + public void setRandomizePositionTransactionsPerSecondThreshold( + long randomizePositionTransactionsPerSecondThreshold) { + this.randomizePositionTransactionsPerSecondThreshold = + randomizePositionTransactionsPerSecondThreshold; + } + + public boolean isOptimizeSessionPoolFuture() { + return optimizeSessionPoolFuture; + } + + public void setOptimizeSessionPoolFuture(boolean optimizeSessionPoolFuture) { + this.optimizeSessionPoolFuture = optimizeSessionPoolFuture; + } + + public boolean isOptimizeUnbalancedCheck() { + return optimizeUnbalancedCheck; + } + + public void setOptimizeUnbalancedCheck(boolean optimizeUnbalancedCheck) { + this.optimizeUnbalancedCheck = optimizeUnbalancedCheck; + } + + public Supplier getDatabaseIdSupplier() { + return databaseIdSupplier; + } + + public boolean isUseStickySessionClient() { + return useStickySessionClient; + } + + public void setUseStickySessionClient(boolean useStickySessionClient) { + this.useStickySessionClient = useStickySessionClient; + } } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index 3dcab3ca8..2a90d6720 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -14,9 +14,9 @@ benchmark.load-data-threads=32 benchmark.truncate-before-load=true # Run the various benchmarks. -benchmark.run-pgadapter-benchmark=true +benchmark.run-pgadapter-benchmark=false benchmark.run-jdbc-benchmark=false -benchmark.run-spanner-benchmark=false +benchmark.run-spanner-benchmark=true benchmark.run-gapic-benchmark=false # The max duration that the benchmark should run in ISO-8601 notation. # E.g. PT60S (60 seconds), PT10M (10 minutes), PT2H (2 hours), P1D (1 day) @@ -32,7 +32,7 @@ benchmark.iterations=2000 # value. Set this value to PT0S to disable any wait times between iterations. benchmark.max-random-wait=PT0S # The number of parallel clients to use for benchmarks. -benchmark.parallelism=2000 +benchmark.parallelism=1,2,4,8,16,24,32,48,64,100,150,200 # The benchmarks to run benchmark.benchmarks=SelectOneValueAutoCommit #,SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction @@ -45,7 +45,11 @@ benchmark.lock-scanned-ranges=false spanner.project=appdev-soda-spanner-staging spanner.instance=knut-test-ycsb spanner.database=knut-test-db -spanner.use-virtual-threads=true +spanner.use-virtual-threads=false +spanner.randomize-position-transactions-per-second-threshold=0 +spanner.optimize-session-pool-future=false +spanner.optimize-unbalanced-check=false +spanner.use-sticky-session-client=false # --- IN-PROCESS PGADAPTER --- # @@ -54,12 +58,12 @@ spanner.use-virtual-threads=true # Set this to true to instruct the benchmark runner to start a PGAdapter instance in-process with # the benchmark application. pgadapter.in-process=true -pgadapter.num-channels=32 +pgadapter.num-channels=4 # Set this if you want the in-process PGAdapter instance to use a specific service account # credentials file. # Leave unset if the application should use the APPLICATION_DEFAULT_CREDENTIALS. -pgadapter.credentials=/Users/loite/Downloads/appdev-soda-spanner-staging.json +pgadapter.credentials=/home/loite/appdev-soda-spanner-staging.json # Set this to true to disable automatic retries of aborted transactions by PGAdapter. Disabling this # will propagate all aborted transaction errors to the application, and the transaction will be diff --git a/pom.xml b/pom.xml index 2d821eceb..2241438b1 100644 --- a/pom.xml +++ b/pom.xml @@ -103,7 +103,7 @@ com.google.cloud google-cloud-spanner - 6.55.1-SNAPSHOT + 6.56.0 io.opentelemetry From 9fdea45144710fa4954c42f8442952cf87a35f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 8 Jan 2024 13:36:57 +0100 Subject: [PATCH 30/46] feat: store results in database --- .../benchmark/BenchmarkApplication.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index 655c3ee6e..adff56deb 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -10,6 +10,13 @@ import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata; import com.google.common.base.Stopwatch; import com.google.common.base.Strings; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; @@ -54,6 +61,9 @@ public BenchmarkApplication( @Override public void run(String... args) throws Exception { + String name = args == null || args.length == 0 ? "(no name)" : args[0]; + Timestamp executedAt = Timestamp.from(Instant.now()); + System.out.println("Running benchmark with name " + name + ", executed at " + executedAt); ProxyServer server = pgAdapterConfiguration.isInProcess() ? startPGAdapter() : null; String pgAdapterConnectionUrl = server == null @@ -163,10 +173,26 @@ public void run(String... args) throws Exception { throw new TimeoutException("Timed out while waiting for benchmark runner to shut down"); } - for (AbstractBenchmarkRunner runner : runners) { - System.out.println(runner.getName()); - for (BenchmarkResult result : runner.getResults()) { - System.out.println(result); + try (Connection connection = DriverManager.getConnection(spannerConnectionUrl); + PreparedStatement statement = + connection.prepareStatement( + "insert into benchmark_results (name, executed_at, parallelism, avg, p50, p90, p95, p99) values (?, ?, ?, ?, ?, ?, ?, ?)")) { + for (AbstractBenchmarkRunner runner : runners) { + System.out.println(runner.getName()); + for (BenchmarkResult result : runner.getResults()) { + System.out.println(result); + int param = 0; + statement.setString(++param, name + " - " + runner.getName() + " - " + result.name); + statement.setTimestamp(++param, executedAt); + statement.setInt(++param, result.parallelism); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.avg)); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.p50)); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.p90)); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.p95)); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.p99)); + statement.addBatch(); + } + statement.executeBatch(); } } } @@ -178,6 +204,10 @@ public void run(String... args) throws Exception { } } + static BigDecimal toBigDecimalSeconds(Duration duration) { + return BigDecimal.valueOf(duration.getSeconds()).add(BigDecimal.valueOf(duration.getNano(), 9)); + } + private long loadData(DataLoadStatus status, String connectionUrl) throws Exception { try (DataLoader loader = new DataLoader(status, connectionUrl, benchmarkConfiguration)) { return loader.loadData(); From 21a0ffbec360e31b90a02d4ed7ad5b37f093758d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 8 Jan 2024 14:53:39 +0100 Subject: [PATCH 31/46] fix: use default session pool --- .../pgadapter/benchmark/BenchmarkApplication.java | 8 ++++++-- .../benchmark/SpannerBenchmarkRunner.java | 14 ++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index adff56deb..810e849dd 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -81,8 +81,12 @@ public void run(String... args) throws Exception { spannerConfiguration.getInstance(), spannerConfiguration.getDatabase(), pgAdapterConfiguration.getNumChannels(), - benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(100), - benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(400), + Math.max( + 100, + benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(100)), + Math.max( + 400, + benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(400)), spannerConfiguration.isUseVirtualThreads(), spannerConfiguration.isUseStickySessionClient()); try { diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java index 702a6e42a..64280fb8d 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -137,15 +137,17 @@ private Spanner createSpanner() throws IOException { .setSessionPoolOption( SessionPoolOptions.newBuilder() .setMinSessions( - 2 - * benchmarkConfiguration.getParallelism().stream() + Math.max( + 100, + benchmarkConfiguration.getParallelism().stream() .max(Integer::compare) - .orElse(100)) + .orElse(100))) .setMaxSessions( - 2 - * benchmarkConfiguration.getParallelism().stream() + Math.max( + 400, + benchmarkConfiguration.getParallelism().stream() .max(Integer::compare) - .orElse(400)) + .orElse(400))) .setOptimizeSessionPoolFuture( spannerConfiguration.isOptimizeSessionPoolFuture()) .setOptimizeUnbalancedCheck(spannerConfiguration.isOptimizeUnbalancedCheck()) From b8f6933e73be6a1348b75fe651712cb2ea09f766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 14 Jan 2024 09:07:56 +0100 Subject: [PATCH 32/46] feat: use optimization options --- .../pgadapter/benchmark/BenchmarkApplication.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index 810e849dd..10856e8f6 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -73,7 +73,7 @@ public void run(String... args) throws Exception { server.getLocalPort(), spannerConfiguration.getDatabase()); String spannerConnectionUrl = String.format( - "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?numChannels=%d;minSessions=%d;maxSessions=%d;useVirtualThreads=%s;useStickySessions=%s" + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?numChannels=%d;minSessions=%d;maxSessions=%d;useVirtualThreads=%s;useStickySessions=%s;optimizeSessionPool=%s" + (pgAdapterConfiguration.getCredentials() == null ? "" : ";credentials=" + pgAdapterConfiguration.getCredentials()), @@ -88,7 +88,10 @@ public void run(String... args) throws Exception { 400, benchmarkConfiguration.getParallelism().stream().max(Integer::compare).orElse(400)), spannerConfiguration.isUseVirtualThreads(), - spannerConfiguration.isUseStickySessionClient()); + spannerConfiguration.isUseStickySessionClient(), + spannerConfiguration.isOptimizeUnbalancedCheck() + && spannerConfiguration.isOptimizeSessionPoolFuture() + && spannerConfiguration.getRandomizePositionTransactionsPerSecondThreshold() > 0L); try { System.out.println("Checking schema"); SchemaService schemaService = new SchemaService(pgAdapterConnectionUrl); @@ -235,6 +238,11 @@ private ProxyServer startPGAdapter() { benchmarkConfiguration.getParallelism().stream() .max(Integer::compare) .orElse(400)) + .setOptimizeSessionPoolFuture( + spannerConfiguration.isOptimizeSessionPoolFuture()) + .setOptimizeUnbalancedCheck(spannerConfiguration.isOptimizeUnbalancedCheck()) + .setRandomizePositionTransactionsPerSecondThreshold( + spannerConfiguration.getRandomizePositionTransactionsPerSecondThreshold()) .build()) .setDisableVirtualThreads(!spannerConfiguration.isUseVirtualThreads()) .disableUnixDomainSockets(); From 80bd8491f12f3867765a89a76f4f5e57511b107d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 14 Jan 2024 09:23:50 +0100 Subject: [PATCH 33/46] feat: add option for using streaming sql for gapic --- .../benchmark/GapicBenchmarkRunner.java | 31 +++++++++++++++++-- .../config/SpannerConfiguration.java | 9 ++++++ .../src/main/resources/application.properties | 1 + 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java index c62241ffb..38f004d7b 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java @@ -6,6 +6,7 @@ import com.google.api.gax.core.FixedCredentialsProvider; import com.google.api.gax.grpc.ChannelPoolSettings; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.rpc.ServerStream; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; import com.google.cloud.pgadapter.benchmark.config.PGAdapterConfiguration; @@ -22,6 +23,7 @@ import com.google.spanner.v1.ExecuteSqlRequest; import com.google.spanner.v1.PartialResultSet; import com.google.spanner.v1.ResultSet; +import com.google.spanner.v1.ResultSetMetadata; import com.google.spanner.v1.Session; import com.google.spanner.v1.TransactionOptions; import com.google.spanner.v1.TransactionOptions.ReadWrite; @@ -150,13 +152,21 @@ void runQuery( .build()) .build()); } - ResultSet resultSet = client.executeSql(builder.build()); - consumeResultSet(resultSet); + ResultSetMetadata metadata; + if (spannerConfiguration.isUseStreamingSql()) { + ServerStream stream = + client.executeStreamingSqlCallable().call(builder.build()); + metadata = consumeStream(stream); + } else { + ResultSet resultSet = client.executeSql(builder.build()); + consumeResultSet(resultSet); + metadata = resultSet.getMetadata(); + } if (!autoCommit) { client.commit( CommitRequest.newBuilder() .setSession(session.getName()) - .setTransactionId(resultSet.getMetadata().getTransaction().getId()) + .setTransactionId(metadata.getTransaction().getId()) .build()); } statistics.incOperations(); @@ -180,6 +190,21 @@ private void consumeResultSet(ResultSet resultSet) { } } + private ResultSetMetadata consumeStream(ServerStream stream) { + ResultSetMetadata metadata = ResultSetMetadata.getDefaultInstance(); + for (PartialResultSet partialResultSet : stream) { + if (partialResultSet.hasMetadata()) { + metadata = partialResultSet.getMetadata(); + } + int col = 0; + for (Value value : partialResultSet.getValuesList()) { + assertEquals(value, partialResultSet.getValues(col)); + col++; + } + } + return metadata; + } + SpannerClient createClient() throws Exception { InstantiatingGrpcChannelProvider channelProvider = InstantiatingGrpcChannelProvider.newBuilder() diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java index f69afe677..2588a3755 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/SpannerConfiguration.java @@ -18,6 +18,7 @@ public class SpannerConfiguration { private boolean optimizeSessionPoolFuture; private boolean optimizeUnbalancedCheck; private boolean useStickySessionClient; + private boolean useStreamingSql; private final Supplier databaseIdSupplier = Suppliers.memoize(() -> DatabaseId.of(project, instance, database)); @@ -95,4 +96,12 @@ public boolean isUseStickySessionClient() { public void setUseStickySessionClient(boolean useStickySessionClient) { this.useStickySessionClient = useStickySessionClient; } + + public boolean isUseStreamingSql() { + return useStreamingSql; + } + + public void setUseStreamingSql(boolean useStreamingSql) { + this.useStreamingSql = useStreamingSql; + } } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index 2a90d6720..ed725fc62 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -50,6 +50,7 @@ spanner.randomize-position-transactions-per-second-threshold=0 spanner.optimize-session-pool-future=false spanner.optimize-unbalanced-check=false spanner.use-sticky-session-client=false +spanner.use-streaming-sql=false # --- IN-PROCESS PGADAPTER --- # From b6f78de9db48063519537ed05b59971eb0ba02bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 14 Jan 2024 14:30:39 +0100 Subject: [PATCH 34/46] feat: increase virtual thread pool size --- .../cloud/pgadapter/benchmark/BenchmarkApplication.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index 10856e8f6..e86313c5c 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -222,6 +222,15 @@ private long loadData(DataLoadStatus status, String connectionUrl) throws Except } private ProxyServer startPGAdapter() { + int parallelism = Runtime.getRuntime().availableProcessors() * 3; + int maxPoolSize = Math.max(256, parallelism * 3); + if (!System.getProperties().containsKey("jdk.virtualThreadScheduler.parallelism")) { + System.setProperty("jdk.virtualThreadScheduler.parallelism", String.valueOf(parallelism)); + } + if (!System.getProperties().containsKey("jdk.virtualThreadScheduler.maxPoolSize")) { + System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", String.valueOf(maxPoolSize)); + } + OptionsMetadata.Builder builder = OptionsMetadata.newBuilder() .setProject(spannerConfiguration.getProject()) From 139bd46386a4588a7e49eeb07f950ba3a563a5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 14 Jan 2024 18:13:05 +0100 Subject: [PATCH 35/46] perf: various optimizations --- .../benchmark/BenchmarkApplication.java | 51 +++++++++++-------- .../wireprotocol/ControlMessage.java | 4 +- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index e86313c5c..885088966 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -19,6 +19,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -34,6 +35,8 @@ public class BenchmarkApplication implements CommandLineRunner { private static final Logger LOG = LoggerFactory.getLogger(BenchmarkApplication.class); + private static final String NO_NAME = "(no name)"; + public static void main(String[] args) { try { SpringApplication.run(BenchmarkApplication.class, args); @@ -61,7 +64,7 @@ public BenchmarkApplication( @Override public void run(String... args) throws Exception { - String name = args == null || args.length == 0 ? "(no name)" : args[0]; + String name = args == null || args.length == 0 ? NO_NAME : args[0]; Timestamp executedAt = Timestamp.from(Instant.now()); System.out.println("Running benchmark with name " + name + ", executed at " + executedAt); ProxyServer server = pgAdapterConfiguration.isInProcess() ? startPGAdapter() : null; @@ -180,26 +183,33 @@ public void run(String... args) throws Exception { throw new TimeoutException("Timed out while waiting for benchmark runner to shut down"); } - try (Connection connection = DriverManager.getConnection(spannerConnectionUrl); - PreparedStatement statement = - connection.prepareStatement( - "insert into benchmark_results (name, executed_at, parallelism, avg, p50, p90, p95, p99) values (?, ?, ?, ?, ?, ?, ?, ?)")) { - for (AbstractBenchmarkRunner runner : runners) { - System.out.println(runner.getName()); - for (BenchmarkResult result : runner.getResults()) { - System.out.println(result); - int param = 0; - statement.setString(++param, name + " - " + runner.getName() + " - " + result.name); - statement.setTimestamp(++param, executedAt); - statement.setInt(++param, result.parallelism); - statement.setBigDecimal(++param, toBigDecimalSeconds(result.avg)); - statement.setBigDecimal(++param, toBigDecimalSeconds(result.p50)); - statement.setBigDecimal(++param, toBigDecimalSeconds(result.p90)); - statement.setBigDecimal(++param, toBigDecimalSeconds(result.p95)); - statement.setBigDecimal(++param, toBigDecimalSeconds(result.p99)); - statement.addBatch(); + for (AbstractBenchmarkRunner runner : runners) { + System.out.println(runner.getName()); + for (BenchmarkResult result : runner.getResults()) { + System.out.println(result); + } + } + + if (!Objects.equals(NO_NAME, name)) { + try (Connection connection = DriverManager.getConnection(spannerConnectionUrl); + PreparedStatement statement = + connection.prepareStatement( + "insert into benchmark_results (name, executed_at, parallelism, avg, p50, p90, p95, p99) values (?, ?, ?, ?, ?, ?, ?, ?)")) { + for (AbstractBenchmarkRunner runner : runners) { + for (BenchmarkResult result : runner.getResults()) { + int param = 0; + statement.setString(++param, name + " - " + runner.getName() + " - " + result.name); + statement.setTimestamp(++param, executedAt); + statement.setInt(++param, result.parallelism); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.avg)); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.p50)); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.p90)); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.p95)); + statement.setBigDecimal(++param, toBigDecimalSeconds(result.p99)); + statement.addBatch(); + } + statement.executeBatch(); } - statement.executeBatch(); } } } @@ -239,6 +249,7 @@ private ProxyServer startPGAdapter() { .setNumChannels(pgAdapterConfiguration.getNumChannels()) .setSessionPoolOptions( SessionPoolOptions.newBuilder() + .setTrackStackTraceOfSessionCheckout(false) .setMinSessions( benchmarkConfiguration.getParallelism().stream() .max(Integer::compare) diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java index 935799236..56a5e93e6 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java @@ -542,8 +542,8 @@ public Long call() throws Exception { rows++; hasData = resultSet.next(); - if (rows % 1000 == 0) { - logger.log(Level.INFO, String.format("Sent %d rows", rows)); + if (logger.isLoggable(Level.FINER) && rows % 1000 == 0L) { + logger.log(Level.FINER, String.format("Sent %d rows", rows)); } if (rows == maxRows) { break; From baacb71edff0703ea312775e39e1191cda32c02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 14 Jan 2024 20:22:42 +0100 Subject: [PATCH 36/46] feat: make virtual thread factor configurable --- .../benchmark/AbstractBenchmarkRunner.java | 8 +++---- .../benchmark/BenchmarkApplication.java | 24 ++++++++++++------- .../config/BenchmarkConfiguration.java | 10 ++++++++ .../src/main/resources/application.properties | 17 ++++++------- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index 99957aa50..e57461243 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -1,5 +1,6 @@ package com.google.cloud.pgadapter.benchmark; +import static com.google.cloud.spanner.ThreadFactoryUtil.createVirtualOrDaemonThreadFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -135,10 +136,9 @@ void benchmarkSelect(String benchmarkName, int parallelism, String sql, boolean ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); - // ExecutorService executor = - // Executors.newFixedThreadPool( - // parallelism, createVirtualOrDaemonThreadFactory("benchmark-worker")); - ExecutorService executor = Executors.newFixedThreadPool(parallelism); + ExecutorService executor = + Executors.newFixedThreadPool( + parallelism, createVirtualOrDaemonThreadFactory("benchmark-worker", false)); List> futures = new ArrayList<>(parallelism); for (int task = 0; task < parallelism; task++) { diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java index 885088966..665b642b7 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/BenchmarkApplication.java @@ -67,6 +67,7 @@ public void run(String... args) throws Exception { String name = args == null || args.length == 0 ? NO_NAME : args[0]; Timestamp executedAt = Timestamp.from(Instant.now()); System.out.println("Running benchmark with name " + name + ", executed at " + executedAt); + configureVirtualThreads(); ProxyServer server = pgAdapterConfiguration.isInProcess() ? startPGAdapter() : null; String pgAdapterConnectionUrl = server == null @@ -231,16 +232,23 @@ private long loadData(DataLoadStatus status, String connectionUrl) throws Except } } - private ProxyServer startPGAdapter() { - int parallelism = Runtime.getRuntime().availableProcessors() * 3; - int maxPoolSize = Math.max(256, parallelism * 3); - if (!System.getProperties().containsKey("jdk.virtualThreadScheduler.parallelism")) { - System.setProperty("jdk.virtualThreadScheduler.parallelism", String.valueOf(parallelism)); - } - if (!System.getProperties().containsKey("jdk.virtualThreadScheduler.maxPoolSize")) { - System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", String.valueOf(maxPoolSize)); + private void configureVirtualThreads() { + if (benchmarkConfiguration.getVirtualThreadsFactor() > 1) { + int parallelism = + Runtime.getRuntime().availableProcessors() + * benchmarkConfiguration.getVirtualThreadsFactor(); + int maxPoolSize = + Math.max(256, parallelism * benchmarkConfiguration.getVirtualThreadsFactor()); + if (!System.getProperties().containsKey("jdk.virtualThreadScheduler.parallelism")) { + System.setProperty("jdk.virtualThreadScheduler.parallelism", String.valueOf(parallelism)); + } + if (!System.getProperties().containsKey("jdk.virtualThreadScheduler.maxPoolSize")) { + System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", String.valueOf(maxPoolSize)); + } } + } + private ProxyServer startPGAdapter() { OptionsMetadata.Builder builder = OptionsMetadata.newBuilder() .setProject(spannerConfiguration.getProject()) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java index ac086179d..9dcd0912b 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/config/BenchmarkConfiguration.java @@ -32,6 +32,8 @@ public class BenchmarkConfiguration { private List parallelism; + private int virtualThreadsFactor; + private List benchmarks; /** --- Optimizations --- */ @@ -143,6 +145,14 @@ public void setParallelism(List parallelism) { this.parallelism = parallelism; } + public int getVirtualThreadsFactor() { + return virtualThreadsFactor; + } + + public void setVirtualThreadsFactor(int virtualThreadsFactor) { + this.virtualThreadsFactor = virtualThreadsFactor; + } + public boolean isUseReadOnlyTransactions() { return useReadOnlyTransactions; } diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index ed725fc62..934378888 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -14,9 +14,9 @@ benchmark.load-data-threads=32 benchmark.truncate-before-load=true # Run the various benchmarks. -benchmark.run-pgadapter-benchmark=false +benchmark.run-pgadapter-benchmark=true benchmark.run-jdbc-benchmark=false -benchmark.run-spanner-benchmark=true +benchmark.run-spanner-benchmark=false benchmark.run-gapic-benchmark=false # The max duration that the benchmark should run in ISO-8601 notation. # E.g. PT60S (60 seconds), PT10M (10 minutes), PT2H (2 hours), P1D (1 day) @@ -33,6 +33,7 @@ benchmark.iterations=2000 benchmark.max-random-wait=PT0S # The number of parallel clients to use for benchmarks. benchmark.parallelism=1,2,4,8,16,24,32,48,64,100,150,200 +benchmark.virtual-threads-factor=1 # The benchmarks to run benchmark.benchmarks=SelectOneValueAutoCommit #,SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction @@ -45,10 +46,10 @@ benchmark.lock-scanned-ranges=false spanner.project=appdev-soda-spanner-staging spanner.instance=knut-test-ycsb spanner.database=knut-test-db -spanner.use-virtual-threads=false -spanner.randomize-position-transactions-per-second-threshold=0 -spanner.optimize-session-pool-future=false -spanner.optimize-unbalanced-check=false +spanner.use-virtual-threads=true +spanner.randomize-position-transactions-per-second-threshold=20 +spanner.optimize-session-pool-future=true +spanner.optimize-unbalanced-check=true spanner.use-sticky-session-client=false spanner.use-streaming-sql=false @@ -59,12 +60,12 @@ spanner.use-streaming-sql=false # Set this to true to instruct the benchmark runner to start a PGAdapter instance in-process with # the benchmark application. pgadapter.in-process=true -pgadapter.num-channels=4 +pgadapter.num-channels=32 # Set this if you want the in-process PGAdapter instance to use a specific service account # credentials file. # Leave unset if the application should use the APPLICATION_DEFAULT_CREDENTIALS. -pgadapter.credentials=/home/loite/appdev-soda-spanner-staging.json +pgadapter.credentials=/Users/loite/Downloads/appdev-soda-spanner-staging.json # Set this to true to disable automatic retries of aborted transactions by PGAdapter. Disabling this # will propagate all aborted transaction errors to the application, and the transaction will be From 74c4c527576261d1a537116e7df1cbdcbb44042d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 14 Jan 2024 20:42:08 +0100 Subject: [PATCH 37/46] chore: reduce iterations for select100 tests --- .../benchmark/AbstractBenchmarkRunner.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index e57461243..49c85e23d 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -93,6 +93,7 @@ void benchmarkSelectOneValueAutoCommit(String name, int parallelism) throws Exce benchmarkSelect( name, parallelism, + benchmarkConfiguration.getIterations(), "select col_varchar from benchmark_all_types where id=" + getParameterName(1), true); } @@ -101,6 +102,7 @@ void benchmarkSelectOneRowAutoCommit(String name, int parallelism) throws Except benchmarkSelect( name, parallelism, + benchmarkConfiguration.getIterations(), "select * from benchmark_all_types where id=" + getParameterName(1), true); } @@ -109,6 +111,7 @@ void benchmarkSelect100RowsRowAutoCommit(String name, int parallelism) throws Ex benchmarkSelect( name, parallelism, + benchmarkConfiguration.getIterations() / 10, "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit 100", true); } @@ -117,6 +120,7 @@ void benchmarkSelectOneRowTransaction(String name, int parallelism) throws Excep benchmarkSelect( name, parallelism, + benchmarkConfiguration.getIterations(), "select * from benchmark_all_types where id=" + getParameterName(1), false); } @@ -125,13 +129,15 @@ void benchmarkSelect100RowsRowTransaction(String name, int parallelism) throws E benchmarkSelect( name, parallelism, + benchmarkConfiguration.getIterations() / 10, "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit 100", false); } - void benchmarkSelect(String benchmarkName, int parallelism, String sql, boolean autoCommit) + void benchmarkSelect( + String benchmarkName, int parallelism, int iterations, String sql, boolean autoCommit) throws Exception { - int totalOperations = parallelism * benchmarkConfiguration.getIterations(); + int totalOperations = parallelism * iterations; statistics.reset(this.name, benchmarkName, parallelism, totalOperations); ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); @@ -142,9 +148,7 @@ void benchmarkSelect(String benchmarkName, int parallelism, String sql, boolean List> futures = new ArrayList<>(parallelism); for (int task = 0; task < parallelism; task++) { - futures.add( - executor.submit( - () -> runQuery(sql, autoCommit, benchmarkConfiguration.getIterations(), durations))); + futures.add(executor.submit(() -> runQuery(sql, autoCommit, iterations, durations))); } executor.shutdown(); assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); From 3d71b1825d9c05f7d7cce1d164854c8bcd939b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 15 Jan 2024 10:43:46 +0100 Subject: [PATCH 38/46] deps: stop maven from complaining --- benchmarks/client-comparisons/pom.xml | 5 +++ pom.xml | 64 ++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/benchmarks/client-comparisons/pom.xml b/benchmarks/client-comparisons/pom.xml index c4903a8ad..455cfa51f 100644 --- a/benchmarks/client-comparisons/pom.xml +++ b/benchmarks/client-comparisons/pom.xml @@ -44,6 +44,11 @@ google-cloud-spanner-pgadapter 0.27.2-SNAPSHOT + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure-spi + 1.33.0 + org.apache.commons commons-lang3 diff --git a/pom.xml b/pom.xml index 2241438b1..6a66fca8a 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,34 @@ + + com.google.cloud + google-cloud-core-bom + 2.29.0 + pom + import + + + com.google.api + gax-bom + 2.39.0 + pom + import + + + io.grpc + grpc-bom + 1.60.0 + pom + import + + + com.google.cloud + google-cloud-spanner-bom + 6.56.0 + pom + import + com.google.cloud libraries-bom @@ -91,6 +119,41 @@ + + com.google.api + api-common + 2.22.0 + + + com.google.errorprone + error_prone_annotations + 2.23.0 + + + com.google.auth + google-auth-library-oauth2-http + 1.21.0 + + + com.google.api.grpc + proto-google-iam-v1 + 1.25.0 + + + com.google.api.grpc + proto-google-common-protos + 2.30.0 + + + com.google.api.grpc + grpc-google-common-protos + 2.30.0 + + + com.google.auth + google-auth-library-credentials + 1.21.0 + com.google.auto.value auto-value-annotations @@ -103,7 +166,6 @@ com.google.cloud google-cloud-spanner - 6.56.0 io.opentelemetry From aeabd956cf359670b3cb75e095533ae593271045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 15 Jan 2024 10:52:40 +0100 Subject: [PATCH 39/46] chore: reduce iterations for 100 rows in transaction --- .../cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index 49c85e23d..6a7170937 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -129,7 +129,7 @@ void benchmarkSelect100RowsRowTransaction(String name, int parallelism) throws E benchmarkSelect( name, parallelism, - benchmarkConfiguration.getIterations() / 10, + benchmarkConfiguration.getIterations() / 100, "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit 100", false); } From 637fb801e2c67e57e2f3e977adbab6fb13e9feec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 15 Jan 2024 14:17:45 +0100 Subject: [PATCH 40/46] feat: reduce number of rows in transaction --- .../benchmark/AbstractBenchmarkRunner.java | 27 ++++++++++--------- .../src/main/resources/application.properties | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index 6a7170937..2f16698f4 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -46,9 +46,9 @@ interface BenchmarkMethod { this.benchmarkConfiguration = benchmarkConfiguration; this.benchmarks.put("SelectOneValueAutoCommit", this::benchmarkSelectOneValueAutoCommit); this.benchmarks.put("SelectOneRowAutoCommit", this::benchmarkSelectOneRowAutoCommit); - this.benchmarks.put("Select100RowsAutoCommit", this::benchmarkSelect100RowsRowAutoCommit); + this.benchmarks.put("Select100RowsAutoCommit", this::benchmarkSelect100RowsAutoCommit); this.benchmarks.put("SelectOneRowTransaction", this::benchmarkSelectOneRowTransaction); - this.benchmarks.put("Select100RowsTransaction", this::benchmarkSelect100RowsRowTransaction); + this.benchmarks.put("Select10RowsTransaction", this::benchmarkSelect10RowsTransaction); for (String benchmark : benchmarkConfiguration.getBenchmarks()) { if (!this.benchmarks.containsKey(benchmark)) { throw new IllegalArgumentException( @@ -107,13 +107,8 @@ void benchmarkSelectOneRowAutoCommit(String name, int parallelism) throws Except true); } - void benchmarkSelect100RowsRowAutoCommit(String name, int parallelism) throws Exception { - benchmarkSelect( - name, - parallelism, - benchmarkConfiguration.getIterations() / 10, - "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit 100", - true); + void benchmarkSelect100RowsAutoCommit(String name, int parallelism) throws Exception { + benchmarkSelectNRows(name, parallelism, benchmarkConfiguration.getIterations(), 100, true); } void benchmarkSelectOneRowTransaction(String name, int parallelism) throws Exception { @@ -125,13 +120,19 @@ void benchmarkSelectOneRowTransaction(String name, int parallelism) throws Excep false); } - void benchmarkSelect100RowsRowTransaction(String name, int parallelism) throws Exception { + void benchmarkSelect10RowsTransaction(String name, int parallelism) throws Exception { + benchmarkSelectNRows(name, parallelism, benchmarkConfiguration.getIterations(), 10, false); + } + + void benchmarkSelectNRows( + String name, int parallelism, int iterations, int numRows, boolean autoCommit) + throws Exception { benchmarkSelect( name, parallelism, - benchmarkConfiguration.getIterations() / 100, - "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit 100", - false); + iterations, + "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit " + numRows, + autoCommit); } void benchmarkSelect( diff --git a/benchmarks/client-comparisons/src/main/resources/application.properties b/benchmarks/client-comparisons/src/main/resources/application.properties index 934378888..104e23cfc 100644 --- a/benchmarks/client-comparisons/src/main/resources/application.properties +++ b/benchmarks/client-comparisons/src/main/resources/application.properties @@ -36,7 +36,7 @@ benchmark.parallelism=1,2,4,8,16,24,32,48,64,100,150,200 benchmark.virtual-threads-factor=1 # The benchmarks to run benchmark.benchmarks=SelectOneValueAutoCommit - #,SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select100RowsTransaction + #,SelectOneRowAutoCommit,Select100RowsAutoCommit,SelectOneRowTransaction,Select10RowsTransaction # --- Possible optimizations for TPC-C --- # benchmark.use-read-only-transactions=false From 2d2c69ed5a327ff924b32c46b6a1f12b63d7578a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 15 Jan 2024 14:51:06 +0100 Subject: [PATCH 41/46] fix: use array to select multiple rows --- .../benchmark/AbstractBenchmarkRunner.java | 46 +++++++++++++++---- .../benchmark/GapicBenchmarkRunner.java | 6 ++- .../benchmark/JdbcBenchmarkRunner.java | 13 ++++-- .../benchmark/SpannerBenchmarkRunner.java | 6 ++- 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index 2f16698f4..96a3dd2cf 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -7,13 +7,16 @@ import com.google.cloud.pgadapter.benchmark.config.BenchmarkConfiguration; import java.time.Duration; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,6 +90,18 @@ public void run() { abstract List loadIdentifiers(); + String getRandomId() { + return identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); + } + + Object[] getRandomIds(int numIds) { + Set result = new HashSet<>(numIds); + while (result.size() < numIds) { + result.add(getRandomId()); + } + return result.toArray(); + } + abstract String getParameterName(int index); void benchmarkSelectOneValueAutoCommit(String name, int parallelism) throws Exception { @@ -95,7 +110,8 @@ void benchmarkSelectOneValueAutoCommit(String name, int parallelism) throws Exce parallelism, benchmarkConfiguration.getIterations(), "select col_varchar from benchmark_all_types where id=" + getParameterName(1), - true); + true, + 1); } void benchmarkSelectOneRowAutoCommit(String name, int parallelism) throws Exception { @@ -104,7 +120,8 @@ void benchmarkSelectOneRowAutoCommit(String name, int parallelism) throws Except parallelism, benchmarkConfiguration.getIterations(), "select * from benchmark_all_types where id=" + getParameterName(1), - true); + true, + 1); } void benchmarkSelect100RowsAutoCommit(String name, int parallelism) throws Exception { @@ -117,7 +134,8 @@ void benchmarkSelectOneRowTransaction(String name, int parallelism) throws Excep parallelism, benchmarkConfiguration.getIterations(), "select * from benchmark_all_types where id=" + getParameterName(1), - false); + false, + 1); } void benchmarkSelect10RowsTransaction(String name, int parallelism) throws Exception { @@ -131,12 +149,20 @@ void benchmarkSelectNRows( name, parallelism, iterations, - "select * from benchmark_all_types where id>=" + getParameterName(1) + " limit " + numRows, - autoCommit); + "select * from benchmark_all_types where id in (select * from unnest(" + + getParameterName(1) + + "::varchar[]))", + autoCommit, + numRows); } void benchmarkSelect( - String benchmarkName, int parallelism, int iterations, String sql, boolean autoCommit) + String benchmarkName, + int parallelism, + int iterations, + String sql, + boolean autoCommit, + int numRows) throws Exception { int totalOperations = parallelism * iterations; statistics.reset(this.name, benchmarkName, parallelism, totalOperations); @@ -149,7 +175,7 @@ void benchmarkSelect( List> futures = new ArrayList<>(parallelism); for (int task = 0; task < parallelism; task++) { - futures.add(executor.submit(() -> runQuery(sql, autoCommit, iterations, durations))); + futures.add(executor.submit(() -> runQuery(sql, autoCommit, iterations, numRows, durations))); } executor.shutdown(); assertTrue(executor.awaitTermination(1L, TimeUnit.HOURS)); @@ -162,5 +188,9 @@ void benchmarkSelect( } abstract void runQuery( - String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations); + String sql, + boolean autoCommit, + int iterations, + int numRows, + ConcurrentLinkedQueue durations); } diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java index 38f004d7b..0d683c0c8 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java @@ -118,7 +118,11 @@ String getParameterName(int index) { @Override void runQuery( - String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations) { + String sql, + boolean autoCommit, + int iterations, + int numRows, + ConcurrentLinkedQueue durations) { try { Session session = client.createSession( diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java index b23b7d34e..a827501a7 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java @@ -57,7 +57,11 @@ String getParameterName(int index) { @Override void runQuery( - String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations) { + String sql, + boolean autoCommit, + int iterations, + int numRows, + ConcurrentLinkedQueue durations) { try (Connection connection = DriverManager.getConnection(connectionUrl)) { connection.setAutoCommit(autoCommit); for (int n = 0; n < iterations; n++) { @@ -67,10 +71,13 @@ void runQuery( .nextLong(benchmarkConfiguration.getMaxRandomWait().toMillis()); Thread.sleep(sleepDuration); } - String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); Stopwatch watch = Stopwatch.createStarted(); try (PreparedStatement statement = connection.prepareStatement(sql)) { - statement.setString(1, id); + if (numRows == 1) { + statement.setString(1, getRandomId()); + } else { + statement.setArray(1, connection.createArrayOf("varchar", getRandomIds(numRows))); + } try (ResultSet resultSet = statement.executeQuery()) { while (resultSet.next()) { for (int col = 1; col <= resultSet.getMetaData().getColumnCount(); col++) { diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java index 64280fb8d..88fc74473 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -83,7 +83,11 @@ String getParameterName(int index) { @Override void runQuery( - String sql, boolean autoCommit, int iterations, ConcurrentLinkedQueue durations) { + String sql, + boolean autoCommit, + int iterations, + int numRows, + ConcurrentLinkedQueue durations) { try { for (int n = 0; n < iterations; n++) { if (!benchmarkConfiguration.getMaxRandomWait().isZero()) { From af50ae2ca88ac91f24e791a4f81178ee053654ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 15 Jan 2024 15:02:52 +0100 Subject: [PATCH 42/46] fix: use text as data type --- .../cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java | 2 +- .../cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java index 96a3dd2cf..32134b23d 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/AbstractBenchmarkRunner.java @@ -151,7 +151,7 @@ void benchmarkSelectNRows( iterations, "select * from benchmark_all_types where id in (select * from unnest(" + getParameterName(1) - + "::varchar[]))", + + "::text[]))", autoCommit, numRows); } diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java index a827501a7..cb6267a1d 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/JdbcBenchmarkRunner.java @@ -76,9 +76,10 @@ void runQuery( if (numRows == 1) { statement.setString(1, getRandomId()); } else { - statement.setArray(1, connection.createArrayOf("varchar", getRandomIds(numRows))); + statement.setArray(1, connection.createArrayOf("text", getRandomIds(numRows))); } try (ResultSet resultSet = statement.executeQuery()) { + int rowCount = 0; while (resultSet.next()) { for (int col = 1; col <= resultSet.getMetaData().getColumnCount(); col++) { if (connection.isWrapperFor(CloudSpannerJdbcConnection.class) @@ -103,7 +104,9 @@ void runQuery( resultSet.getString(resultSet.getMetaData().getColumnLabel(col))); } } + rowCount++; } + assertEquals(numRows, rowCount); } } if (!autoCommit) { From 1ccc78cacd50602a1c2392681bd2abea26d98aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 15 Jan 2024 15:17:40 +0100 Subject: [PATCH 43/46] feat: support arrays for Spanner and gapic --- .../benchmark/GapicBenchmarkRunner.java | 41 +++++++++++++++---- .../benchmark/SpannerBenchmarkRunner.java | 25 ++++++++--- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java index 0d683c0c8..4243b0073 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/GapicBenchmarkRunner.java @@ -31,11 +31,13 @@ import java.io.FileInputStream; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; class GapicBenchmarkRunner extends AbstractBenchmarkRunner { @@ -137,16 +139,32 @@ void runQuery( Thread.sleep(sleepDuration); } String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); + Value paramValue; + if (numRows == 1) { + paramValue = Value.newBuilder().setStringValue(id).build(); + } else { + paramValue = + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + Arrays.stream(getRandomIds(numRows)) + .map( + element -> + Value.newBuilder() + .setStringValue(element.toString()) + .build()) + .collect(Collectors.toList())) + .build()) + .build(); + } Stopwatch watch = Stopwatch.createStarted(); ExecuteSqlRequest.Builder builder = ExecuteSqlRequest.newBuilder() .setSession(session.getName()) .setSql(sql) .setSeqno(1L) - .setParams( - Struct.newBuilder() - .putFields("p1", Value.newBuilder().setStringValue(id).build()) - .build()); + .setParams(Struct.newBuilder().putFields("p1", paramValue).build()); if (!autoCommit) { builder.setTransaction( TransactionSelector.newBuilder() @@ -160,10 +178,10 @@ void runQuery( if (spannerConfiguration.isUseStreamingSql()) { ServerStream stream = client.executeStreamingSqlCallable().call(builder.build()); - metadata = consumeStream(stream); + metadata = consumeStream(stream, numRows); } else { ResultSet resultSet = client.executeSql(builder.build()); - consumeResultSet(resultSet); + consumeResultSet(resultSet, numRows); metadata = resultSet.getMetadata(); } if (!autoCommit) { @@ -184,7 +202,8 @@ void runQuery( } } - private void consumeResultSet(ResultSet resultSet) { + private void consumeResultSet(ResultSet resultSet, int expectedNumRows) { + assertEquals(expectedNumRows, resultSet.getRowsList().size()); for (ListValue row : resultSet.getRowsList()) { int col = 0; for (Value value : row.getValuesList()) { @@ -194,18 +213,24 @@ private void consumeResultSet(ResultSet resultSet) { } } - private ResultSetMetadata consumeStream(ServerStream stream) { + private ResultSetMetadata consumeStream( + ServerStream stream, int expectedNumRows) { ResultSetMetadata metadata = ResultSetMetadata.getDefaultInstance(); + int expectedNumCells = -1; + int numCells = 0; for (PartialResultSet partialResultSet : stream) { if (partialResultSet.hasMetadata()) { metadata = partialResultSet.getMetadata(); + expectedNumCells = metadata.getRowType().getFieldsCount() * expectedNumRows; } int col = 0; for (Value value : partialResultSet.getValuesList()) { assertEquals(value, partialResultSet.getValues(col)); col++; + numCells++; } } + assertEquals(expectedNumCells, numCells); return metadata; } diff --git a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java index 88fc74473..dc53c1955 100644 --- a/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java +++ b/benchmarks/client-comparisons/src/main/java/com/google/cloud/pgadapter/benchmark/SpannerBenchmarkRunner.java @@ -21,9 +21,11 @@ import java.io.IOException; import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -96,12 +98,22 @@ void runQuery( .nextLong(benchmarkConfiguration.getMaxRandomWait().toMillis()); Thread.sleep(sleepDuration); } - String id = identifiers.get(ThreadLocalRandom.current().nextInt(identifiers.size())); Stopwatch watch = Stopwatch.createStarted(); - Statement statement = Statement.newBuilder(sql).bind("p1").to(id).build(); + Statement.Builder builder = Statement.newBuilder(sql); + if (numRows == 1) { + builder.bind("p1").to(getRandomId()).build(); + } else { + builder + .bind("p1") + .toStringArray( + Arrays.stream(getRandomIds(numRows)) + .map(Object::toString) + .collect(Collectors.toList())); + } + Statement statement = builder.build(); if (autoCommit) { try (ResultSet resultSet = databaseClient.singleUse().executeQuery(statement)) { - consumeResultSet(resultSet); + consumeResultSet(resultSet, numRows); } } else { databaseClient @@ -109,7 +121,7 @@ void runQuery( .run( transaction -> { try (ResultSet resultSet = transaction.executeQuery(statement)) { - consumeResultSet(resultSet); + consumeResultSet(resultSet, numRows); } return 0L; }); @@ -122,14 +134,17 @@ void runQuery( } } - private void consumeResultSet(ResultSet resultSet) { + private void consumeResultSet(ResultSet resultSet, int expectedNumRows) { + int numRows = 0; while (resultSet.next()) { for (int col = 0; col < resultSet.getColumnCount(); col++) { assertEquals( resultSet.getValue(col), resultSet.getValue(resultSet.getMetadata().getRowType().getFields(col).getName())); } + numRows++; } + assertEquals(expectedNumRows, numRows); } private Spanner createSpanner() throws IOException { From 69a463ded1e56ee453d146d4c83f6f2fe780f218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 15 Jan 2024 15:36:56 +0100 Subject: [PATCH 44/46] test: try without internal retries --- .../com/google/cloud/spanner/pgadapter/ConnectionHandler.java | 1 + 1 file changed, 1 insertion(+) 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 9526fda02..7c41546eb 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java @@ -265,6 +265,7 @@ static String buildConnectionURL( // emulator when 'autoConfigEmulator=true'. uri = uri + ";dialect=postgresql"; uri = uri + ";useVirtualThreads=" + options.useVirtualThreads(); + uri = uri + ";retryAbortsInternally=false"; if (System.getProperty(CHANNEL_PROVIDER_PROPERTY) != null) { uri = uri From 2a04b52bcc732f190474c38e9da37a5d4376e639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 15 Jan 2024 17:24:28 +0100 Subject: [PATCH 45/46] chore: turn on checksums --- .../com/google/cloud/spanner/pgadapter/ConnectionHandler.java | 1 - 1 file changed, 1 deletion(-) 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 7c41546eb..9526fda02 100644 --- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java +++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java @@ -265,7 +265,6 @@ static String buildConnectionURL( // emulator when 'autoConfigEmulator=true'. uri = uri + ";dialect=postgresql"; uri = uri + ";useVirtualThreads=" + options.useVirtualThreads(); - uri = uri + ";retryAbortsInternally=false"; if (System.getProperty(CHANNEL_PROVIDER_PROPERTY) != null) { uri = uri From 47562d7640f0a85f2b2da1b3345015b3f0d348af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Sun, 21 Jan 2024 10:51:43 +0100 Subject: [PATCH 46/46] deps: update spanner deps --- benchmarks/client-comparisons/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/client-comparisons/pom.xml b/benchmarks/client-comparisons/pom.xml index c4903a8ad..bd5ff3361 100644 --- a/benchmarks/client-comparisons/pom.xml +++ b/benchmarks/client-comparisons/pom.xml @@ -37,7 +37,7 @@ com.google.cloud google-cloud-spanner - 6.56.0 + 6.56.1-SNAPSHOT com.google.cloud diff --git a/pom.xml b/pom.xml index 2241438b1..74ae9f060 100644 --- a/pom.xml +++ b/pom.xml @@ -103,7 +103,7 @@ com.google.cloud google-cloud-spanner - 6.56.0 + 6.56.1-SNAPSHOT io.opentelemetry