Repository navigation
feat(storage): the session gives a fast open read to the matching readAs #14832
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
base: fast-open/01-first-reads-in-open
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -34,7 +34,10 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Iterator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Locale; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map.Entry; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentHashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ExecutionException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ScheduledExecutorService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.locks.ReentrantLock; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -52,21 +55,29 @@ final class ObjectReadSessionImpl implements ObjectReadSession { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final ConcurrentIdentityMap<ObjectReadSessionStream, ObjectReadSessionState> children; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fast open reads: reads sent in the request that opened the stream, keyed by the config each was | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // created from. The first readAs with an equal config is given the read instead of sending a new | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // one. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final ConcurrentMap<ReadProjectionConfig<?>, ObjectReadSessionStreamRead<?>> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fastOpenReads; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private volatile boolean open; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectReadSessionImpl( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ScheduledExecutorService executor, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ZeroCopyBidiStreamingCallable<BidiReadObjectRequest, BidiReadObjectResponse> callable, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectReadSessionStream stream, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectReadSessionState state, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RetryContextProvider retryContextProvider) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RetryContextProvider retryContextProvider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<ReadProjectionConfig<?>, ObjectReadSessionStreamRead<?>> fastOpenReads) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.executor = executor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.callable = callable; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.stream = stream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.state = state; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.resource = state.getMetadata(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.retryContextProvider = retryContextProvider; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.children = new ConcurrentIdentityMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.fastOpenReads = new ConcurrentHashMap<>(fastOpenReads); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.open = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
66
to
82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the constructor to accept the queue-based map of fast-open reads.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -80,6 +91,14 @@ public <Projection> Projection readAs(ReadProjectionConfig<Projection> config) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkState(open, "Session already closed"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (config.getType()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case STREAM_READ: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectReadSessionStreamRead<?> fastOpenRead = fastOpenReads.remove(config); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fastOpenRead != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // configs are final classes with a class-exact equals, so an equal config has the same | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Projection type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @SuppressWarnings("unchecked") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Projection projection = (Projection) fastOpenRead.project(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return projection; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+94
to
+101
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| long readId = state.newReadId(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectReadSessionStreamRead<Projection> read = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.cast().newRead(readId, retryContextProvider.create()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -113,6 +132,11 @@ public void close() throws IOException { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (InterruptedException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Thread.currentThread().interrupt(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new InterruptedIOException(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // a fast open read nobody asked for may already be complete and out of the state, so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // stream.close() above does not reach it; close it here to release its buffers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GrpcUtils.closeAll(fastOpenReads.values()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fastOpenReads.clear(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+135
to
140
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -27,11 +27,14 @@ | |||||||||||||||||||||||||||||||||
| import com.google.cloud.storage.ReadProjectionConfig.ProjectionType; | ||||||||||||||||||||||||||||||||||
| import com.google.cloud.storage.RetryContext.RetryContextProvider; | ||||||||||||||||||||||||||||||||||
| import com.google.common.collect.ImmutableList; | ||||||||||||||||||||||||||||||||||
| import com.google.common.collect.ImmutableMap; | ||||||||||||||||||||||||||||||||||
| import com.google.storage.v2.BidiReadObjectRequest; | ||||||||||||||||||||||||||||||||||
| import com.google.storage.v2.BidiReadObjectResponse; | ||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||
| import java.time.Duration; | ||||||||||||||||||||||||||||||||||
| import java.util.HashMap; | ||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ScheduledExecutorService; | ||||||||||||||||||||||||||||||||||
| import java.util.concurrent.TimeUnit; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -66,6 +69,9 @@ ApiFuture<ObjectReadSession> readSession(BidiReadObjectRequest req, GrpcCallCont | |||||||||||||||||||||||||||||||||
| * Open a new {@link ObjectReadSession} using fast open: the ranges in {@code fastOpenConfigs} are | ||||||||||||||||||||||||||||||||||
| * sent in the same request that opens the session, saving a round trip before the first bytes. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * <p>The first {@code readAs} with a config equal to one of these is given that fast open read | ||||||||||||||||||||||||||||||||||
| * instead of sending a new one. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * @param fastOpenConfigs the reads to send with the open, for example {@code | ||||||||||||||||||||||||||||||||||
| * ReadProjectionConfigs.asFutureBytes().withRangeSpec(RangeSpec.of(0, 1024))}; empty to only | ||||||||||||||||||||||||||||||||||
| * open the session | ||||||||||||||||||||||||||||||||||
|
|
@@ -77,6 +83,7 @@ ApiFuture<ObjectReadSession> readSession( | |||||||||||||||||||||||||||||||||
| checkArgument( | ||||||||||||||||||||||||||||||||||
| req.getReadRangesList().isEmpty(), | ||||||||||||||||||||||||||||||||||
| "ranges included in the initial request are not supported"); | ||||||||||||||||||||||||||||||||||
| checkArgument(fastOpenConfigs.size() <= 1, "at most one fast open read is supported"); | ||||||||||||||||||||||||||||||||||
| ObjectReadSessionState state = new ObjectReadSessionState(ctx, req); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ZeroCopyBidiStreamingCallable<BidiReadObjectRequest, BidiReadObjectResponse> callable = | ||||||||||||||||||||||||||||||||||
|
|
@@ -86,18 +93,21 @@ ApiFuture<ObjectReadSession> readSession( | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Add each fast open read to the session state. The open request below is built from the | ||||||||||||||||||||||||||||||||||
| // state, so these reads are sent as part of the open. | ||||||||||||||||||||||||||||||||||
| Map<ReadProjectionConfig<?>, ObjectReadSessionStreamRead<?>> fastOpenReads = new HashMap<>(); | ||||||||||||||||||||||||||||||||||
| for (ReadProjectionConfig<?> config : fastOpenConfigs) { | ||||||||||||||||||||||||||||||||||
| long readId = state.newReadId(); | ||||||||||||||||||||||||||||||||||
| ObjectReadSessionStreamRead<?> read = | ||||||||||||||||||||||||||||||||||
| config.cast().newRead(readId, retryContextProvider.create()); | ||||||||||||||||||||||||||||||||||
| state.putOutstandingRead(readId, read); | ||||||||||||||||||||||||||||||||||
| fastOpenReads.put(config, read); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+96
to
103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update
Suggested change
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ApiFuture<ObjectReadSession> objectReadSessionFuture = | ||||||||||||||||||||||||||||||||||
| ApiFutures.transform( | ||||||||||||||||||||||||||||||||||
| stream, | ||||||||||||||||||||||||||||||||||
| nowOpen -> | ||||||||||||||||||||||||||||||||||
| new ObjectReadSessionImpl(executor, callable, stream, state, retryContextProvider), | ||||||||||||||||||||||||||||||||||
| new ObjectReadSessionImpl( | ||||||||||||||||||||||||||||||||||
| executor, callable, stream, state, retryContextProvider, fastOpenReads), | ||||||||||||||||||||||||||||||||||
| executor); | ||||||||||||||||||||||||||||||||||
| stream.send(state.getOpenArguments().getReq()); | ||||||||||||||||||||||||||||||||||
| return objectReadSessionFuture; | ||||||||||||||||||||||||||||||||||
|
|
@@ -132,7 +142,7 @@ <Projection> ApiFuture<FastOpenObjectReadSession<Projection>> fastOpenReadSessio | |||||||||||||||||||||||||||||||||
| nowOpen -> | ||||||||||||||||||||||||||||||||||
| new FastOpenObjectReadSession<>( | ||||||||||||||||||||||||||||||||||
| new ObjectReadSessionImpl( | ||||||||||||||||||||||||||||||||||
| executor, callable, stream, state, retryContextProvider), | ||||||||||||||||||||||||||||||||||
| executor, callable, stream, state, retryContextProvider, ImmutableMap.of()), | ||||||||||||||||||||||||||||||||||
| read, | ||||||||||||||||||||||||||||||||||
| stream), | ||||||||||||||||||||||||||||||||||
| executor); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
fastOpenConfigscontains duplicate configs (e.g., requesting the same range multiple times), using a simpleMapwhere the key is the config will cause subsequent duplicate configs to overwrite previous ones. This results in only the last fast-open read being preserved, while the others are leaked/wasted and subsequentreadAscalls for those configs will unnecessarily trigger new requests.To support duplicate configs correctly and safely under concurrent access, we can map each config to a thread-safe queue (such as
ConcurrentLinkedQueue) of outstanding reads.