From 39544d59984985e11cdc02ebb245cd2fbb19114e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Mon, 23 Oct 2023 15:57:40 +0200 Subject: [PATCH 1/2] chore: keep session pool ordering when pinging Pinging sessions would move the sessions that were pinged to either the front or the back of the pool (dependingin the session pool configuration), instead of keeping the sessions in the place where they were when being pinged. Bringing a session that is pinged to the front of the pool means that we will prefer using a session that has not really been used for a while, other than for the ping. Keeping the sessions in place is therefore preferable. --- .../com/google/cloud/spanner/SessionPool.java | 38 +++++++++++++------ .../spanner/SessionPoolMaintainerTest.java | 29 +++++++++----- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java index ca61da80583..0cb3df0ec0d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java @@ -45,6 +45,7 @@ import com.google.api.gax.core.ExecutorProvider; import com.google.api.gax.rpc.ServerStream; import com.google.cloud.Timestamp; +import com.google.cloud.Tuple; import com.google.cloud.grpc.GrpcTransportOptions; import com.google.cloud.grpc.GrpcTransportOptions.ExecutorFactory; import com.google.cloud.spanner.Options.QueryOption; @@ -1864,7 +1865,7 @@ private void keepAliveSessions(Instant currTime) { // Keep chugging till there is no session that needs to be kept alive. while (numSessionsToKeepAlive > 0) { - PooledSession sessionToKeepAlive = null; + Tuple sessionToKeepAlive; synchronized (lock) { sessionToKeepAlive = findSessionToKeepAlive(sessions, keepAliveThreshold, 0); } @@ -1872,10 +1873,10 @@ private void keepAliveSessions(Instant currTime) { break; } try { - logger.log(Level.FINE, "Keeping alive session " + sessionToKeepAlive.getName()); + logger.log(Level.FINE, "Keeping alive session " + sessionToKeepAlive.x().getName()); numSessionsToKeepAlive--; - sessionToKeepAlive.keepAlive(); - releaseSession(sessionToKeepAlive, false); + sessionToKeepAlive.x().keepAlive(); + releaseSession(sessionToKeepAlive); } catch (SpannerException e) { handleException(e, sessionToKeepAlive); } @@ -2291,11 +2292,11 @@ private boolean isClosed() { } } - private void handleException(SpannerException e, PooledSession session) { + private void handleException(SpannerException e, Tuple session) { if (isSessionNotFound(e)) { - invalidateSession(session); + invalidateSession(session.x()); } else { - releaseSession(session, false); + releaseSession(session); } } @@ -2319,7 +2320,7 @@ private void invalidateSession(PooledSession session) { } } - private PooledSession findSessionToKeepAlive( + private Tuple findSessionToKeepAlive( Queue queue, Instant keepAliveThreshold, int numAlreadyChecked) { int numChecked = 0; Iterator iterator = queue.iterator(); @@ -2329,7 +2330,7 @@ private PooledSession findSessionToKeepAlive( PooledSession session = iterator.next(); if (session.lastUseTime.isBefore(keepAliveThreshold)) { iterator.remove(); - return session; + return Tuple.of(session, numChecked); } numChecked++; } @@ -2459,8 +2460,17 @@ private void maybeCreateSession() { } } - /** Releases a session back to the pool. This might cause one of the waiters to be unblocked. */ + private void releaseSession(Tuple sessionWithPosition) { + releaseSession(sessionWithPosition.x(), false, sessionWithPosition.y()); + } + private void releaseSession(PooledSession session, boolean isNewSession) { + releaseSession(session, isNewSession, null); + } + + /** Releases a session back to the pool. This might cause one of the waiters to be unblocked. */ + private void releaseSession( + PooledSession session, boolean isNewSession, @Nullable Integer position) { Preconditions.checkNotNull(session); synchronized (lock) { if (closureFuture != null) { @@ -2480,7 +2490,12 @@ private void releaseSession(PooledSession session, boolean isNewSession) { // more efficient. session.releaseToPosition = options.getReleaseToPosition(); } - if (session.releaseToPosition == Position.RANDOM && !sessions.isEmpty()) { + if (position != null) { + // Make sure we use a valid position, as the number of sessions could have changed in the + // meantime. + int actualPosition = Math.min(position, sessions.size()); + sessions.add(actualPosition, session); + } else if (session.releaseToPosition == Position.RANDOM && !sessions.isEmpty()) { // A session should only be added at a random position the first time it is added to // the pool or if the pool was deemed unbalanced. All following releases into the pool // should normally happen at the default release position (unless the pool is again deemed @@ -2493,6 +2508,7 @@ private void releaseSession(PooledSession session, boolean isNewSession) { } else { sessions.addFirst(session); } + session.releaseToPosition = options.getReleaseToPosition(); } else { waiters.poll().put(session); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolMaintainerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolMaintainerTest.java index 2f7b14fdadf..d6b7ea5c458 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolMaintainerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolMaintainerTest.java @@ -17,6 +17,7 @@ package com.google.cloud.spanner; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -166,14 +167,20 @@ public void testKeepAlive() throws Exception { Session session3 = pool.getSession(); Session session4 = pool.getSession(); Session session5 = pool.getSession(); - // Note that session2 was now the first session in the pool as it was the last to receive a - // ping. - assertThat(session3.getName()).isEqualTo(session2.getName()); - assertThat(session4.getName()).isEqualTo(session1.getName()); + // Pinging a session will put it at the back of the pool. A session that needed a ping to be + // kept alive is not one that should be preferred for use. This means that session2 is the last + // session in the pool, and session1 the second-to-last. + assertEquals(session1.getName(), session3.getName()); + assertEquals(session2.getName(), session4.getName()); session5.close(); session4.close(); session3.close(); // Advance the clock to force pings for the sessions in the pool and do three maintenance loops. + // This should ping the sessions in the following order: + // 1. session3 (=session1) + // 2. session4 (=session2) + // The pinged sessions already contains: {session1: 1, session2: 1} + // Note that the pool only pings up to MinSessions sessions. clock.currentTimeMillis += TimeUnit.MINUTES.toMillis(options.getKeepAliveIntervalMinutes()) + 1; runMaintenanceLoop(clock, pool, 3); assertThat(pingedSessions).containsExactly(session1.getName(), 2, session2.getName(), 2); @@ -181,16 +188,18 @@ public void testKeepAlive() throws Exception { // Advance the clock to idle all sessions in the pool again and then check out one session. This // should cause only one session to get a ping. clock.currentTimeMillis += TimeUnit.MINUTES.toMillis(options.getKeepAliveIntervalMinutes()) + 1; - // We are now checking out session2 because + // This will be session1, as all sessions were pinged in the previous 3 maintenance loops, and + // this will have brought session1 back to the front of the pool. Session session6 = pool.getSession(); // The session that was first in the pool now is equal to the initial first session as each full // round of pings will swap the order of the first MinSessions sessions in the pool. assertThat(session6.getName()).isEqualTo(session1.getName()); runMaintenanceLoop(clock, pool, 3); + // Running 3 cycles will only ping the 2 sessions in the pool once. assertThat(pool.totalSessions()).isEqualTo(3); assertThat(pingedSessions).containsExactly(session1.getName(), 2, session2.getName(), 3); // Update the last use date and release the session to the pool and do another maintenance - // cycle. + // cycle. This should not ping any sessions. ((PooledSessionFuture) session6).get().markUsed(); session6.close(); runMaintenanceLoop(clock, pool, 3); @@ -255,10 +264,10 @@ public void testIdleSessions() throws Exception { Session session3 = pool.getSession().get(); Session session4 = pool.getSession().get(); Session session5 = pool.getSession().get(); - // Note that session2 was now the first session in the pool as it was the last to receive a - // ping. - assertThat(session3.getName()).isEqualTo(session2.getName()); - assertThat(session4.getName()).isEqualTo(session1.getName()); + // Note that pinging sessions does not change the order of the pool. This means that session2 + // is still the last session in the pool. + assertThat(session3.getName()).isEqualTo(session1.getName()); + assertThat(session4.getName()).isEqualTo(session2.getName()); session5.close(); session4.close(); session3.close(); From eea5e00b01f9925a5e3b8ce67625f42223eb463d Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 23 Oct 2023 14:03:22 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20po?= =?UTF-8?q?st-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b29575e58e6..16650a06448 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,13 @@ implementation 'com.google.cloud:google-cloud-spanner' If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-spanner:6.52.0' +implementation 'com.google.cloud:google-cloud-spanner:6.52.1' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.52.0" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.52.1" ``` @@ -432,7 +432,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.52.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.52.1 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles