Skip to content

Commit d14e2a9

Browse files
author
Dhriti Chopra
committed
feat(storage): add blobReadSession overload that takes a fast open read
Seventh step of fast open BiDi reads (go/fast-open-bidi-read, b/552263062). Storage.blobReadSession(BlobId, ReadProjectionConfig, BlobSourceOption...) sends the read described by the config in the same request that opens the session. The first readAs with an equal config is given that read, saving the round trip a separate first read would take. Only asFutureBytes, asFutureByteString and asChannel configs are accepted, and a null config only opens the session. This is additive: blobReadSession(BlobId, BlobSourceOption...) is unchanged. OtelStorageDecorator passes the config through, so fast open also works with OpenTelemetry tracing enabled, and a fast open read that fails is recorded as an error on its readAs span.
1 parent 49d3976 commit d14e2a9

5 files changed

Lines changed: 285 additions & 32 deletions

File tree

‎java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static com.google.cloud.storage.Utils.ifNonNull;
2828
import static com.google.cloud.storage.Utils.projectNameCodec;
2929
import static com.google.common.base.MoreObjects.firstNonNull;
30+
import static com.google.common.base.Preconditions.checkArgument;
3031
import static java.util.Objects.requireNonNull;
3132

3233
import com.google.api.core.ApiFuture;
@@ -55,6 +56,7 @@
5556
import com.google.cloud.storage.HmacKey.HmacKeyState;
5657
import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
5758
import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
59+
import com.google.cloud.storage.ReadProjectionConfig.ProjectionType;
5860
import com.google.cloud.storage.Retrying.Retrier;
5961
import com.google.cloud.storage.Retrying.RetrierWithAlg;
6062
import com.google.cloud.storage.Storage.ComposeRequest.SourceBlob;
@@ -1468,6 +1470,26 @@ public Blob moveBlob(MoveBlobRequest request) {
14681470

14691471
@Override
14701472
public ApiFuture<BlobReadSession> blobReadSession(BlobId id, BlobSourceOption... options) {
1473+
return blobReadSession(id, ImmutableList.of(), options);
1474+
}
1475+
1476+
@Override
1477+
public <Projection> ApiFuture<BlobReadSession> blobReadSession(
1478+
BlobId id,
1479+
@Nullable ReadProjectionConfig<Projection> fastOpenConfig,
1480+
BlobSourceOption... options) {
1481+
if (fastOpenConfig == null) {
1482+
return blobReadSession(id, ImmutableList.of(), options);
1483+
}
1484+
checkArgument(
1485+
fastOpenConfig.getType() == ProjectionType.STREAM_READ,
1486+
"fastOpenConfig must be asFutureBytes(), asFutureByteString() or asChannel(), but was %s",
1487+
fastOpenConfig);
1488+
return blobReadSession(id, ImmutableList.of(fastOpenConfig), options);
1489+
}
1490+
1491+
private ApiFuture<BlobReadSession> blobReadSession(
1492+
BlobId id, List<ReadProjectionConfig<?>> fastOpenConfigs, BlobSourceOption... options) {
14711493
Opts<ObjectSourceOpt> opts = Opts.unwrap(options);
14721494
Object object = codecs.blobId().encode(id);
14731495

@@ -1484,7 +1506,8 @@ public ApiFuture<BlobReadSession> blobReadSession(BlobId id, BlobSourceOption...
14841506
BidiReadObjectRequest req = b.build();
14851507

14861508
GrpcCallContext context = opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault());
1487-
ApiFuture<ObjectReadSession> session = storageDataClient.readSession(req, context);
1509+
ApiFuture<ObjectReadSession> session =
1510+
storageDataClient.readSession(req, context, fastOpenConfigs);
14881511

14891512
return BlobReadSessionAdapter.wrap(session);
14901513
}

‎java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,14 @@ public Blob moveBlob(MoveBlobRequest request) {
14761476

14771477
@Override
14781478
public ApiFuture<BlobReadSession> blobReadSession(BlobId id, BlobSourceOption... options) {
1479+
return blobReadSession(id, null, options);
1480+
}
1481+
1482+
@Override
1483+
public <Projection> ApiFuture<BlobReadSession> blobReadSession(
1484+
BlobId id,
1485+
@Nullable ReadProjectionConfig<Projection> fastOpenConfig,
1486+
BlobSourceOption... options) {
14791487
Span blobReadSessionSpan =
14801488
tracer
14811489
.spanBuilder(BLOB_READ_SESSION)
@@ -1484,7 +1492,8 @@ public ApiFuture<BlobReadSession> blobReadSession(BlobId id, BlobSourceOption...
14841492
try (Scope ignore1 = blobReadSessionSpan.makeCurrent()) {
14851493
Context blobReadSessionContext = Context.current();
14861494
Span ready = tracer.spanBuilder(BLOB_READ_SESSION + "/ready").startSpan();
1487-
ApiFuture<BlobReadSession> blobReadSessionApiFuture = delegate.blobReadSession(id, options);
1495+
ApiFuture<BlobReadSession> blobReadSessionApiFuture =
1496+
delegate.blobReadSession(id, fastOpenConfig, options);
14881497
ApiFuture<BlobReadSession> futureDecorated =
14891498
ApiFutures.transform(
14901499
blobReadSessionApiFuture,

‎java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5955,6 +5955,44 @@ default ApiFuture<BlobReadSession> blobReadSession(BlobId id, BlobSourceOption..
59555955
return throwGrpcOnly(fmtMethodName("blobReadSession", BlobId.class, BlobSourceOption.class));
59565956
}
59575957

5958+
/**
5959+
* Asynchronously set up a new {@link BlobReadSession} for the specified {@link BlobId} and {@code
5960+
* options}, sending the read described by {@code fastOpenConfig} in the same request that opens
5961+
* the session ("fast open"). This saves the round trip a separate first read would take.
5962+
*
5963+
* <p>The first {@link BlobReadSession#readAs(ReadProjectionConfig)} with a config equal to {@code
5964+
* fastOpenConfig} is given that read; any other {@code readAs} sends a new read. If the fast open
5965+
* read fails, the error is reported by the returned future or by that first {@code readAs}.
5966+
*
5967+
* <h4>Example of reading the first 1 KiB of an object with fast open:</h4>
5968+
*
5969+
* <pre>{@code
5970+
* ReadAsFutureBytes first1KiB =
5971+
* ReadProjectionConfigs.asFutureBytes().withRangeSpec(RangeSpec.of(0, 1024));
5972+
* try (BlobReadSession session =
5973+
* storage.blobReadSession(blobId, first1KiB).get(10, TimeUnit.SECONDS)) {
5974+
* byte[] bytes = session.readAs(first1KiB).get(10, TimeUnit.SECONDS);
5975+
* }
5976+
* }</pre>
5977+
*
5978+
* @param id the blob to read from
5979+
* @param fastOpenConfig the read to send with the open, or {@code null} to only open the session.
5980+
* Must be {@link ReadProjectionConfigs#asFutureBytes()}, {@link
5981+
* ReadProjectionConfigs#asFutureByteString()} or {@link ReadProjectionConfigs#asChannel()},
5982+
* optionally with a range.
5983+
* @since 2.77.0 This new api is in preview and is subject to breaking changes.
5984+
*/
5985+
@BetaApi
5986+
@TransportCompatibility({Transport.GRPC})
5987+
default <Projection> ApiFuture<BlobReadSession> blobReadSession(
5988+
BlobId id,
5989+
@Nullable ReadProjectionConfig<Projection> fastOpenConfig,
5990+
BlobSourceOption... options) {
5991+
return throwGrpcOnly(
5992+
fmtMethodName(
5993+
"blobReadSession", BlobId.class, ReadProjectionConfig.class, BlobSourceOption.class));
5994+
}
5995+
59585996
/**
59595997
* Create a new {@link BlobAppendableUpload} for the specified {@code blobInfo} and {@code
59605998
* options}.

0 commit comments

Comments
 (0)