Conversation

arpan14

SessionImpl class today has two responsibilities - a) POJO for holding session metadata and b) holding the current transaction context (transaction ID, span, etc.) . Given it has the dual responsibility, it is used both in control plane (during session creation) as well as data-plane (transaction execution).

This approach used to work for regular sessions since regular sessions only had 1 transaction running concurrently. For multiplexed sessions, since there can be multiple transactions running per SessionImpl object, it can no longer hold the transaction context.

This PR does the refactoring in two steps -

  1. Create a new class SessionInstance which is a stateless POJO representing 1 session. This does not hold any transaction context. Refactor all member variables which is specific to a session (session name, databaseID, etc.) from SessionImpl to SessionInstance.
  2. For multiplexed sessions, while creating a session store the reference as AtomicReference<SettableApiFuture<SessionInstance>>. There is only object for this stored as a global variable. This is only replaced when a new session gets created by maintainer.
  3. For multiplexed session, when a new request comes, it will first create a new span. This span is stored for all objects on the data-plane (MultiplexedSessionFutureWrapper, MultiplexedSessionFuture and MultiplexedSession). Finally, we invoke markBusy to set the span on the SessionImpl object. This works since we now have 1 reference of SessionImpl per new request
SessionImpl sessionImpl =
            new SessionImpl(
                sessionClient.getSpanner(), this.multiplexedSessionSettableApiFuture.get());

Alternate Approach - An alternate approach was explored at - #3016 where I was refactoring the setSpan() method and removing the method from SessionTransaction interface. This approach somewhat worked for RO transactions but it will be difficult to extend it for RW transactions in future. Also the approach involves a lot of refactoring/testing for all other classes (AbstractReadContext, AsyncTransactionManagerImpl, TransactionRunnerImpl) that implement SessionTransaction interface. So, it's best to not go by this approach to minimize the changes on the client library side.


Testing - Parameterized the SpanTest class and tested that this refactoring works for Multiplexed sessions. Also locally tested multiplexed session via integration tests, performance tests.

@arpan14arpan14 requested a review from a team as a code owner April 11, 2024 17:39
@product-auto-labelproduct-auto-label bot added size: lPull request size is large.api: spannerIssues related to the googleapis/java-spanner API.labels Apr 11, 2024
@arpan14arpan14 requested a review from olavloite April 11, 2024 17:39
@@ -198,7 +198,7 @@ DatabaseId getDatabaseId() {
}

/** Create a single session. */
SessionImpl createSession() {
SessionInstance createSession() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olavloite Presently I have not modified this for multiplexed session method. If you like the approach, I can do a bit more refactoring to ensure that the session creation flow (control plane) only uses the new SessionInstance class and we use SessionImpl on the data-plane side of things. Also I haven't modified the class docs as of now. Will do after we finalize the approach, naming conventions, etc.

assertThat(spans.size()).isEqualTo(3);

if (useMultiplexed) {
assertThat(spans.size()).isEqualTo(4);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm noticing a flaky behavior due to the new assertions for multiplexed sessions. This is because multiplexed session creation does not block on application startup. Probably we can plug some logic in existing setWaitForMinSessions method to also wait for multiplexed session creation?

To start with this could be used for tests. @olavloite WDYT? This also goes back to our previous discussion that it's ok to block application startup if its an option that customers opt-into and is not switched on by default.

}

@Override
public MultiplexedSessionFuture get() {
try {
return this.multiplexedSessionSettableApiFuture.get();
// Creating a new reference where the request's span state can be stored.
SessionImpl sessionImpl =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result of this block of code should be cached in a field in the SessionFutureWrapper object, so multiple calls to get() return the same instance every time.

(We probably don't call it multiple times in our current code, although I'm not 100% sure, but as this method implements Future#get(), we should adhere to the contract of that method.)

* Spanner database. Sessions are managed internally by the client library, and users need not be
* aware of the actual session management, pooling and handling.
*/
class SessionInstance {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe SessionReference? As in: This class is just a reference to a backend session.

settableFuture.set(multiplexedSession);
MultiplexedSessionFuture multiplexedSessionFuture =
new MultiplexedSessionFuture(settableFuture, span);
if (multiplexedSessionFuture != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

  1. The field multiplexedSessionFuture should be volatile.
  2. The lazy initialization should use a lock (the lock only needs to be taken during the actual initialization)

See this for a concrete example: https://www.baeldung.com/java-singleton-double-checked-locking#implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@arpan14arpan14 added automergeMerge the pull request once unit tests and other checks pass.owlbot:runAdd this label to trigger the Owlbot post processor.labels Apr 15, 2024
@gcf-owl-botgcf-owl-bot bot removed the owlbot:runAdd this label to trigger the Owlbot post processor.label Apr 15, 2024
@gcf-merge-on-greengcf-merge-on-green bot merged commit b82decf into googleapis:main Apr 15, 2024
@gcf-merge-on-greengcf-merge-on-green bot removed the automergeMerge the pull request once unit tests and other checks pass.label Apr 15, 2024
@arpan14arpan14 deleted the mux-pr-session-impl-refactor branch April 15, 2024 10:10
Sign up for free to join this conversation on . Already have an account? Sign in to comment
api: spannerIssues related to the googleapis/java-spanner API.size: lPull request size is large.
None yet

Successfully merging this pull request may close these issues.