Skip to content

refactor: move multiplexed session handling to separate class #3063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 52 commits into from
May 2, 2024

Conversation

olavloite
Copy link
Collaborator

Refactors the multiplexed session implementation to use its own class instead of being combined with the existing session pool. This simplifies the code path for multiplexed sessions, and makes it possible to entirely remove the SessionPool class when all operations can be executed on multiplexed sessions.

@product-auto-label product-auto-label bot added size: l Pull request size is large. api: spanner Issues related to the googleapis/java-spanner API. labels Apr 25, 2024
@product-auto-label product-auto-label bot added size: xl Pull request size is extra large. and removed size: l Pull request size is large. labels Apr 26, 2024
@olavloite olavloite requested a review from a team as a code owner April 30, 2024 12:35
@olavloite olavloite added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label May 1, 2024
@yoshi-kokoro yoshi-kokoro removed kokoro:force-run Add this label to force Kokoro to re-run the tests. labels May 1, 2024
* It is enough with one executor to maintain the multiplexed sessions in all the clients, as they
* do not need to be updated often, and the maintenance task is light.
*/
private static final ScheduledExecutorService MAINTAINER_SERVICE =
Copy link
Contributor

Choose a reason for hiding this comment

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

This will be a new thread which will maintain multiplexed sessions? If yes, can you share if re-using the existing thread was a harder implementation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It would have been slightly harder, but that was not my main reason for not using the same thread. My reasoning for creating a separate maintainer and executor was:

  1. This separates multiplexed sessions completely from the session pool. That means that once all operations work on multiplexed sessions, we can just remove the entire session pool implementation.
  2. This maintainer uses a ScheduledThreadPoolExecutor with zero core threads, and only runs the task once every 10 minutes. The additional resource usage is therefore minimal.

* This flag is set to true if the server return UNIMPLEMENTED when we try to create a multiplexed
* session.
*/
private final AtomicBoolean unimplemented = new AtomicBoolean(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: The formatting looks off. Also, should we add a TODO that this is a temporary measure and we can remove it in future when this has a bit of increased usage?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a TODO for removing.

I'm not sure what you mean with the formatting being off (?) The code is formatted using the normal formatter, and I don't see anything strange with this line or the ones directly above.


package com.google.cloud.spanner;

import static com.google.cloud.spanner.SessionImpl.NO_CHANNEL_HINT;
Copy link
Contributor

Choose a reason for hiding this comment

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

Given we are initializing this to -1, can there be an edge case where the value gets decremented to -1? Would using a boxed integer and setting null be a better default than -1?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think that there's a bit of confusion between this value and the numCurrentSingleUseTransactions:

  • The channel hint that is generated will never be -1. It is always calculated by getting the first clear bit from index 0. That means that it will always be >= 0.
  • The numCurrentSingleUseTransactions gets incremented and decremented based on transactions being started and ended. That value should also never get to -1. In theory it could if we were 'over-closing' single-use transactions, but even then we have a safe-guard in the onReadDone() method that only decrements it the first time the method is called, and ignores any second call.

One other scenario that is a bit more probable (but still not much) is that reads never call onReadDone. This could cause numCurrentSingleUseTransactions to get higher than it should. The effect of this would be that we would fall back to using a random channel hint more often than we in the ideal case should. (The same method is also used to end spans, which means that it would also cause spans not to be ended. That is already the case in the current regular session implementation.)

static class MultiplexedSessionTransaction extends SessionImpl {
private final MultiplexedSessionDatabaseClient client;

private final boolean singleUse;
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. How is the channel hint managed for multi-use transactions?
  2. Given we will always set the options at SessionImpl, the random hint logic that was introduced in AbstractReadContext becomes dead code. Do we clean it up here itself? Or do that in a separate PR? I'm fine either ways.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  1. Channel hint for multi-use read-only transactions are always random. In theory we could use the same strategy for them as for single-use read-only transactions, but I think we should not, because it would make the 'channel-hint-bitset' depend on read-only transactions always being closed. Failure to close read-only transactions has been a common source of session leaks in the past, and that is exactly what we want to get rid of with multiplexed sessions. Adding a new dependency on closing all read-only transactions here could in theory give us a bit better latency if someone is running 1qps multi-use read-only transactions, but I don't think the benefit of that outweighs the potential downside.
  2. The code for assigning a random hint in AbstractReadContext is still used. We only create an options instance if the channel hint is not the special value NO_CHANNEL_HINT. See . What happens in this case is the following:
    2.1. MultiplexedSessionDatabaseClient returns NO_CHANNEL_HINT if we should just use a random channel:
    2.2. That value is then passed to the constructor of SessionImpl.
    2.3. SessionImpl checks if the channel hint is NO_CHANNEL_HINT, and if so does not create an options instance, but instead calls sessionReference.getOptions().
    2.4. sessionReference.getOptions() returns null for multiplexed sessions and the affiliated channel for regular sessions.

@olavloite olavloite merged commit b2795a7 into main May 2, 2024
@olavloite olavloite deleted the mux-benchmark-experiments branch May 2, 2024 08:41
}

@Override
public CursorState tryNext() throws SpannerException {
return delegate.tryNext();
return getDelegate().tryNext();

Choose a reason for hiding this comment

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

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: spanner Issues related to the googleapis/java-spanner API. size: xl Pull request size is extra large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants