Skip to content

Commit 63bcbea

Browse files
authored
chore: add a system property to set session pool ordering (#2656)
* chore: add a system property to set session pool ordering Adds support for using a System property to set the default session pool ordering. The default ordering is LIFO. This can be changed to FIFO and RANDOM for users who need/want that. * chore: address review comments
1 parent e51c55d commit 63bcbea

File tree

3 files changed

+169
-4
lines changed

3 files changed

+169
-4
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,6 +1976,7 @@ private void removeLongRunningSessions(
19761976

19771977
enum Position {
19781978
FIRST,
1979+
LAST,
19791980
RANDOM
19801981
}
19811982

@@ -2477,16 +2478,18 @@ private void releaseSession(PooledSession session, boolean isNewSession) {
24772478
// Do not randomize if there are few other sessions checked out and this session has been
24782479
// used. This ensures that this session will be re-used for the next transaction, which is
24792480
// more efficient.
2480-
session.releaseToPosition = Position.FIRST;
2481+
session.releaseToPosition = options.getReleaseToPosition();
24812482
}
24822483
if (session.releaseToPosition == Position.RANDOM && !sessions.isEmpty()) {
24832484
// A session should only be added at a random position the first time it is added to
24842485
// the pool or if the pool was deemed unbalanced. All following releases into the pool
2485-
// should normally happen at the front of the pool (unless the pool is again deemed to be
2486-
// unbalanced).
2487-
session.releaseToPosition = Position.FIRST;
2486+
// should normally happen at the default release position (unless the pool is again deemed
2487+
// to be unbalanced and the insertion would happen at the front of the pool).
2488+
session.releaseToPosition = options.getReleaseToPosition();
24882489
int pos = random.nextInt(sessions.size() + 1);
24892490
sessions.add(pos, session);
2491+
} else if (session.releaseToPosition == Position.LAST) {
2492+
sessions.addLast(session);
24902493
} else {
24912494
sessions.addFirst(session);
24922495
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPoolOptions.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
package com.google.cloud.spanner;
1818

1919
import com.google.cloud.spanner.SessionPool.Clock;
20+
import com.google.cloud.spanner.SessionPool.Position;
2021
import com.google.common.annotations.VisibleForTesting;
2122
import com.google.common.base.Preconditions;
23+
import java.util.Locale;
2224
import java.util.Objects;
2325
import org.threeten.bp.Duration;
2426

@@ -62,6 +64,7 @@ public class SessionPoolOptions {
6264
private final boolean autoDetectDialect;
6365
private final Duration waitForMinSessions;
6466
private final Duration acquireSessionTimeout;
67+
private final Position releaseToPosition;
6568

6669
/** Property for allowing mocking of session maintenance clock. */
6770
private final Clock poolMaintainerClock;
@@ -86,6 +89,7 @@ private SessionPoolOptions(Builder builder) {
8689
this.autoDetectDialect = builder.autoDetectDialect;
8790
this.waitForMinSessions = builder.waitForMinSessions;
8891
this.acquireSessionTimeout = builder.acquireSessionTimeout;
92+
this.releaseToPosition = builder.releaseToPosition;
8993
this.inactiveTransactionRemovalOptions = builder.inactiveTransactionRemovalOptions;
9094
this.poolMaintainerClock = builder.poolMaintainerClock;
9195
}
@@ -114,6 +118,7 @@ public boolean equals(Object o) {
114118
&& Objects.equals(this.autoDetectDialect, other.autoDetectDialect)
115119
&& Objects.equals(this.waitForMinSessions, other.waitForMinSessions)
116120
&& Objects.equals(this.acquireSessionTimeout, other.acquireSessionTimeout)
121+
&& Objects.equals(this.releaseToPosition, other.releaseToPosition)
117122
&& Objects.equals(
118123
this.inactiveTransactionRemovalOptions, other.inactiveTransactionRemovalOptions)
119124
&& Objects.equals(this.poolMaintainerClock, other.poolMaintainerClock);
@@ -138,6 +143,7 @@ public int hashCode() {
138143
this.autoDetectDialect,
139144
this.waitForMinSessions,
140145
this.acquireSessionTimeout,
146+
this.releaseToPosition,
141147
this.inactiveTransactionRemovalOptions,
142148
this.poolMaintainerClock);
143149
}
@@ -254,6 +260,10 @@ Duration getAcquireSessionTimeout() {
254260
return acquireSessionTimeout;
255261
}
256262

263+
Position getReleaseToPosition() {
264+
return releaseToPosition;
265+
}
266+
257267
public static Builder newBuilder() {
258268
return new Builder();
259269
}
@@ -440,9 +450,23 @@ public static class Builder {
440450
private boolean autoDetectDialect = false;
441451
private Duration waitForMinSessions = Duration.ZERO;
442452
private Duration acquireSessionTimeout = Duration.ofSeconds(60);
453+
private Position releaseToPosition = getReleaseToPositionFromSystemProperty();
443454

444455
private Clock poolMaintainerClock;
445456

457+
private static Position getReleaseToPositionFromSystemProperty() {
458+
// NOTE: This System property is a beta feature. Support for it can be removed in the future.
459+
String key = "com.google.cloud.spanner.session_pool_release_to_position";
460+
if (System.getProperties().containsKey(key)) {
461+
try {
462+
return Position.valueOf(System.getProperty(key).toUpperCase(Locale.ENGLISH));
463+
} catch (Throwable ignore) {
464+
// fallthrough and return the default.
465+
}
466+
}
467+
return Position.FIRST;
468+
}
469+
446470
public Builder() {}
447471

448472
private Builder(SessionPoolOptions options) {
@@ -735,6 +759,11 @@ public Builder setAcquireSessionTimeout(Duration acquireSessionTimeout) {
735759
return this;
736760
}
737761

762+
Builder setReleaseToPosition(Position releaseToPosition) {
763+
this.releaseToPosition = Preconditions.checkNotNull(releaseToPosition);
764+
return this;
765+
}
766+
738767
/** Build a SessionPoolOption object */
739768
public SessionPoolOptions build() {
740769
validate();

google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_DEFAULT_LABEL_VALUES;
2424
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS;
2525
import static com.google.cloud.spanner.MetricRegistryConstants.SPANNER_LABEL_KEYS_WITH_TYPE;
26+
import static com.google.cloud.spanner.SpannerOptionsTest.runWithSystemProperty;
2627
import static com.google.common.truth.Truth.assertThat;
2728
import static org.junit.Assert.assertEquals;
29+
import static org.junit.Assert.assertNotEquals;
2830
import static org.junit.Assert.assertNotNull;
2931
import static org.junit.Assert.assertThrows;
3032
import static org.junit.Assert.assertTrue;
@@ -60,6 +62,7 @@
6062
import com.google.cloud.spanner.spi.v1.SpannerRpc;
6163
import com.google.cloud.spanner.spi.v1.SpannerRpc.ResultStreamConsumer;
6264
import com.google.cloud.spanner.v1.stub.SpannerStubSettings;
65+
import com.google.common.collect.Lists;
6366
import com.google.common.util.concurrent.ListenableFuture;
6467
import com.google.common.util.concurrent.Uninterruptibles;
6568
import com.google.protobuf.ByteString;
@@ -82,12 +85,14 @@
8285
import java.util.LinkedList;
8386
import java.util.List;
8487
import java.util.Map;
88+
import java.util.Objects;
8589
import java.util.concurrent.CountDownLatch;
8690
import java.util.concurrent.ExecutorService;
8791
import java.util.concurrent.Executors;
8892
import java.util.concurrent.Future;
8993
import java.util.concurrent.TimeUnit;
9094
import java.util.concurrent.atomic.AtomicBoolean;
95+
import java.util.stream.Collectors;
9196
import org.junit.Before;
9297
import org.junit.Test;
9398
import org.junit.runner.RunWith;
@@ -236,6 +241,134 @@ public void poolLifo() {
236241
session4.close();
237242
}
238243

244+
@Test
245+
public void poolFifo() throws Exception {
246+
setupMockSessionCreation();
247+
runWithSystemProperty(
248+
"com.google.cloud.spanner.session_pool_release_to_position",
249+
"LAST",
250+
() -> {
251+
options =
252+
options
253+
.toBuilder()
254+
.setMinSessions(2)
255+
.setWaitForMinSessions(Duration.ofSeconds(10L))
256+
.build();
257+
pool = createPool();
258+
pool.maybeWaitOnMinSessions();
259+
Session session1 = pool.getSession().get();
260+
Session session2 = pool.getSession().get();
261+
assertNotEquals(session1, session2);
262+
263+
session2.close();
264+
session1.close();
265+
266+
// Check the session out and back in once more to finalize their positions.
267+
session1 = pool.getSession().get();
268+
session2 = pool.getSession().get();
269+
session2.close();
270+
session1.close();
271+
272+
// Verify that we get the sessions in FIFO order, so in this order:
273+
// 1. session2
274+
// 2. session1
275+
Session session3 = pool.getSession().get();
276+
Session session4 = pool.getSession().get();
277+
assertEquals(session2, session3);
278+
assertEquals(session1, session4);
279+
session3.close();
280+
session4.close();
281+
282+
return null;
283+
});
284+
}
285+
286+
@Test
287+
public void poolAllPositions() throws Exception {
288+
int maxAttempts = 100;
289+
setupMockSessionCreation();
290+
for (Position position : Position.values()) {
291+
runWithSystemProperty(
292+
"com.google.cloud.spanner.session_pool_release_to_position",
293+
position.name(),
294+
() -> {
295+
int attempt = 0;
296+
while (attempt < maxAttempts) {
297+
int numSessions = 5;
298+
options =
299+
options
300+
.toBuilder()
301+
.setMinSessions(numSessions)
302+
.setMaxSessions(numSessions)
303+
.setWaitForMinSessions(Duration.ofSeconds(10L))
304+
.build();
305+
pool = createPool();
306+
pool.maybeWaitOnMinSessions();
307+
// First check out and release the sessions twice to the pool, so we know that we have
308+
// finalized the position of them.
309+
for (int n = 0; n < 2; n++) {
310+
checkoutAndReleaseAllSessions();
311+
}
312+
313+
// Now verify that if we get all sessions twice, they will be in random order.
314+
List<List<PooledSessionFuture>> allSessions = new ArrayList<>(2);
315+
for (int n = 0; n < 2; n++) {
316+
allSessions.add(checkoutAndReleaseAllSessions());
317+
}
318+
List<Session> firstTime =
319+
allSessions.get(0).stream()
320+
.map(PooledSessionFuture::get)
321+
.collect(Collectors.toList());
322+
List<Session> secondTime =
323+
allSessions.get(1).stream()
324+
.map(PooledSessionFuture::get)
325+
.collect(Collectors.toList());
326+
switch (position) {
327+
case FIRST:
328+
// LIFO:
329+
// First check out all sessions, so we have 1, 2, 3, 4, ..., N
330+
// Then release them all back into the pool in the same order (1, 2, 3, 4, ..., N)
331+
// That will give us the list N, ..., 4, 3, 2, 1 because each session is added at
332+
// the front of the pool.
333+
assertEquals(firstTime, Lists.reverse(secondTime));
334+
break;
335+
case LAST:
336+
// FIFO:
337+
// First check out all sessions, so we have 1, 2, 3, 4, ..., N
338+
// Then release them all back into the pool in the same order (1, 2, 3, 4, ..., N)
339+
// That will give us the list 1, 2, 3, 4, ..., N because each session is added at
340+
// the end of the pool.
341+
assertEquals(firstTime, secondTime);
342+
break;
343+
case RANDOM:
344+
// Random means that we should not get the same order twice (unless the randomizer
345+
// got lucky, and then we retry).
346+
if (attempt < (maxAttempts - 1)) {
347+
if (Objects.equals(firstTime, secondTime)) {
348+
attempt++;
349+
continue;
350+
}
351+
}
352+
assertNotEquals(firstTime, secondTime);
353+
}
354+
break;
355+
}
356+
return null;
357+
});
358+
}
359+
}
360+
361+
private List<PooledSessionFuture> checkoutAndReleaseAllSessions() {
362+
List<PooledSessionFuture> sessions = new ArrayList<>(pool.totalSessions());
363+
for (int i = 0; i < pool.totalSessions(); i++) {
364+
sessions.add(pool.getSession());
365+
}
366+
for (Session session : sessions) {
367+
session.close();
368+
}
369+
return sessions;
370+
}
371+
239372
@Test
240373
public void poolClosure() throws Exception {
241374
setupMockSessionCreation();

0 commit comments

Comments
 (0)