-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add WithMaxSubscriptions config backed by stream vs unary grpc connection pools #450
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
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f6d4e58
chore: add internal topics grpc connection pool interfaces and classes
anitarua d20d864
feat: add WithMaxSubscriptions config and update topic client to use …
anitarua 8752cb0
chore: add tests for dynamic stream grpc channel pools
anitarua 0fe4ad2
fix formatting
anitarua 6f847ad
extract num concurrent streams per channel into const
anitarua 4a8cafb
implement Closeable
anitarua File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
434 changes: 422 additions & 12 deletions
434
momento-sdk/src/intTest/java/momento/sdk/retry/TopicsSubscriptionInitializationTest.java
Large diffs are not rendered by default.
Oops, something went wrong.
124 changes: 124 additions & 0 deletions
124
momento-sdk/src/main/java/momento/sdk/DynamicStreamGrpcConnectionPool.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| package momento.sdk; | ||
|
|
||
| import grpc.cache_client.pubsub.PubsubGrpc; | ||
| import io.grpc.ManagedChannel; | ||
| import java.io.Closeable; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.CopyOnWriteArrayList; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.IntStream; | ||
| import momento.sdk.auth.CredentialProvider; | ||
| import momento.sdk.config.TopicConfiguration; | ||
| import momento.sdk.exceptions.ClientSdkException; | ||
| import momento.sdk.exceptions.MomentoErrorCode; | ||
| import momento.sdk.internal.GrpcChannelOptions; | ||
|
|
||
| public class DynamicStreamGrpcConnectionPool implements StreamTopicGrpcConnectionPool, Closeable { | ||
| private final CredentialProvider credentialProvider; | ||
| private final TopicConfiguration configuration; | ||
| private final UUID connectionIdKey; | ||
|
|
||
| private final AtomicInteger index = new AtomicInteger(0); | ||
| private final AtomicInteger currentNumStreamGrpcChannels = new AtomicInteger(1); | ||
| private final int maxStreamGrpcChannels; | ||
|
|
||
| private final int currentMaxConcurrentStreams; | ||
| private final AtomicInteger currentNumActiveStreams = new AtomicInteger(0); | ||
|
|
||
| private final CopyOnWriteArrayList<ManagedChannel> streamChannels; | ||
| private final CopyOnWriteArrayList<StreamStubWithCount> streamStubs; | ||
|
|
||
| public DynamicStreamGrpcConnectionPool( | ||
| CredentialProvider credentialProvider, | ||
| TopicConfiguration configuration, | ||
| UUID connectionIdKey) { | ||
| this.currentMaxConcurrentStreams = GrpcChannelOptions.NUM_CONCURRENT_STREAMS_PER_GRPC_CHANNEL; | ||
| this.maxStreamGrpcChannels = | ||
| configuration.getTransportStrategy().getGrpcConfiguration().getNumStreamGrpcChannels(); | ||
|
|
||
| this.credentialProvider = credentialProvider; | ||
| this.configuration = configuration; | ||
| this.connectionIdKey = connectionIdKey; | ||
|
|
||
| this.streamChannels = | ||
| IntStream.range(0, this.currentNumStreamGrpcChannels.get()) | ||
| .mapToObj( | ||
| i -> | ||
| TopicGrpcConnectionPoolUtils.setupConnection( | ||
| credentialProvider, configuration, connectionIdKey)) | ||
| .collect(Collectors.toCollection(CopyOnWriteArrayList::new)); | ||
| this.streamStubs = | ||
| streamChannels.stream() | ||
| .map(PubsubGrpc::newStub) | ||
| .map(StreamStubWithCount::new) | ||
| .collect(Collectors.toCollection(CopyOnWriteArrayList::new)); | ||
| } | ||
|
|
||
| // Multiple threads could get to the point of seeing currentNumActiveStreams == | ||
| // currentMaxConcurrentStreams, | ||
| // but we need to ensure only one thread will add a new channel at a time so that we don't exceed | ||
| // the max number of channels. | ||
| private void addNewChannel() { | ||
| final int updatedCount = this.currentNumStreamGrpcChannels.incrementAndGet(); | ||
|
|
||
| if (updatedCount > this.maxStreamGrpcChannels) { | ||
| this.currentNumStreamGrpcChannels.decrementAndGet(); | ||
| return; | ||
| } | ||
|
|
||
| this.streamChannels.add( | ||
| TopicGrpcConnectionPoolUtils.setupConnection( | ||
| credentialProvider, configuration, connectionIdKey)); | ||
| this.streamStubs.add( | ||
| new StreamStubWithCount( | ||
| PubsubGrpc.newStub( | ||
| TopicGrpcConnectionPoolUtils.setupConnection( | ||
| credentialProvider, configuration, connectionIdKey)))); | ||
| } | ||
|
|
||
| @Override | ||
| public StreamStubWithCount getNextStreamStub() { | ||
| // Check if we've reached the current max number of active streams. | ||
| if (this.currentNumActiveStreams.get() == this.currentMaxConcurrentStreams) { | ||
| // If we have not yet reached the maximum number of channels, add a new channel. | ||
| if (this.currentNumStreamGrpcChannels.get() < this.maxStreamGrpcChannels) { | ||
| this.addNewChannel(); | ||
| } else { | ||
| // Otherwise return an error because all channels and streams are occupied. | ||
| throw new ClientSdkException( | ||
| MomentoErrorCode.CLIENT_RESOURCE_EXHAUSTED, | ||
| "Maximum number of active subscriptions reached"); | ||
| } | ||
| } | ||
|
|
||
| // Try to get a client with capacity for another subscription | ||
| // by round-robining through the stubs. | ||
| // Allow up to maximumActiveSubscriptions attempts to account for large bursts of requests. | ||
| final int maximumActiveSubscriptions = | ||
| this.currentNumStreamGrpcChannels.get() | ||
| * GrpcChannelOptions.NUM_CONCURRENT_STREAMS_PER_GRPC_CHANNEL; | ||
| for (int i = 0; i < maximumActiveSubscriptions; i++) { | ||
| final StreamStubWithCount stubWithCount = | ||
| streamStubs.get(index.getAndIncrement() % this.currentNumStreamGrpcChannels.get()); | ||
| try { | ||
| stubWithCount.acquireStubOrThrow(); | ||
| this.currentNumActiveStreams.incrementAndGet(); | ||
| return stubWithCount; | ||
| } catch (ClientSdkException e) { | ||
| // If the stub is at capacity, continue to the next one. | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| // Otherwise return an error if no stubs have capacity. | ||
| throw new ClientSdkException( | ||
| MomentoErrorCode.CLIENT_RESOURCE_EXHAUSTED, | ||
| "Maximum number of active subscriptions reached"); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| streamChannels.forEach(ManagedChannel::shutdown); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
momento-sdk/src/main/java/momento/sdk/StaticStreamGrpcConnectionPool.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package momento.sdk; | ||
|
|
||
| import grpc.cache_client.pubsub.PubsubGrpc; | ||
| import io.grpc.ManagedChannel; | ||
| import java.io.Closeable; | ||
| import java.util.List; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.IntStream; | ||
| import momento.sdk.auth.CredentialProvider; | ||
| import momento.sdk.config.TopicConfiguration; | ||
| import momento.sdk.exceptions.ClientSdkException; | ||
| import momento.sdk.exceptions.MomentoErrorCode; | ||
| import momento.sdk.internal.GrpcChannelOptions; | ||
|
|
||
| class StaticStreamGrpcConnectionPool implements StreamTopicGrpcConnectionPool, Closeable { | ||
| private final AtomicInteger index = new AtomicInteger(0); | ||
| private final int numStreamGrpcChannels; | ||
| private final List<ManagedChannel> streamChannels; | ||
| private final List<StreamStubWithCount> streamStubs; | ||
|
|
||
| public StaticStreamGrpcConnectionPool( | ||
| CredentialProvider credentialProvider, | ||
| TopicConfiguration configuration, | ||
| UUID connectionIdKey) { | ||
| this.numStreamGrpcChannels = | ||
| configuration.getTransportStrategy().getGrpcConfiguration().getNumStreamGrpcChannels(); | ||
| this.streamChannels = | ||
| IntStream.range(0, this.numStreamGrpcChannels) | ||
| .mapToObj( | ||
| i -> | ||
| TopicGrpcConnectionPoolUtils.setupConnection( | ||
| credentialProvider, configuration, connectionIdKey)) | ||
| .collect(Collectors.toList()); | ||
| this.streamStubs = | ||
| streamChannels.stream() | ||
| .map(PubsubGrpc::newStub) | ||
| .map(StreamStubWithCount::new) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| @Override | ||
| public StreamStubWithCount getNextStreamStub() { | ||
| // Try to get a client with capacity for another subscription | ||
| // by round-robining through the stubs. | ||
| // Allow up to maximumActiveSubscriptions attempts to account for large bursts of requests. | ||
| final int maximumActiveSubscriptions = | ||
| this.numStreamGrpcChannels * GrpcChannelOptions.NUM_CONCURRENT_STREAMS_PER_GRPC_CHANNEL; | ||
| for (int i = 0; i < maximumActiveSubscriptions; i++) { | ||
| final StreamStubWithCount stubWithCount = | ||
| streamStubs.get(index.getAndIncrement() % this.numStreamGrpcChannels); | ||
| try { | ||
| stubWithCount.acquireStubOrThrow(); | ||
| return stubWithCount; | ||
| } catch (ClientSdkException e) { | ||
| // If the stub is at capacity, continue to the next one. | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| // Otherwise return an error if no stubs have capacity. | ||
| throw new ClientSdkException( | ||
| MomentoErrorCode.CLIENT_RESOURCE_EXHAUSTED, | ||
| "Maximum number of active subscriptions reached"); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| streamChannels.forEach(ManagedChannel::shutdown); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.