refactor: move multiplexed session handling to separate class#3063
refactor: move multiplexed session handling to separate class#3063
Conversation
…a-spanner into mux-benchmark-experiments
…a-spanner into mux-benchmark-experiments
…a-spanner into mux-benchmark-experiments
| * 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 = |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
- 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.
- This maintainer uses a
ScheduledThreadPoolExecutorwith 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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
numCurrentSingleUseTransactionsgets 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 theonReadDone()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; |
There was a problem hiding this comment.
- How is the channel hint managed for multi-use transactions?
- Given we will always set the
optionsatSessionImpl, the random hint logic that was introduced inAbstractReadContextbecomes dead code. Do we clean it up here itself? Or do that in a separate PR? I'm fine either ways.
There was a problem hiding this comment.
- 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.
- The code for assigning a random hint in
AbstractReadContextis still used. We only create anoptionsinstance if the channel hint is not the special valueNO_CHANNEL_HINT. See. What happens in this case is the following:
2.1.MultiplexedSessionDatabaseClientreturnsNO_CHANNEL_HINTif we should just use a random channel:
2.2. That value is then passed to the constructor ofSessionImpl.
2.3.SessionImplchecks if the channel hint isNO_CHANNEL_HINT, and if so does not create anoptionsinstance, but instead callssessionReference.getOptions().
2.4.sessionReference.getOptions()returns null for multiplexed sessions and the affiliated channel for regular sessions.
…a-spanner into mux-benchmark-experiments
| @Override | ||
| public CursorState tryNext() throws SpannerException { | ||
| return delegate.tryNext(); | ||
| return getDelegate().tryNext(); |
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
SessionPoolclass when all operations can be executed on multiplexed sessions.