Skip to content

feat: Extract metadata from GCS read channel#333

Open
shrutisinghania wants to merge 6 commits into
GoogleCloudPlatform:mainfrom
shrutisinghania:reflective-metadata-extraction
Open

feat: Extract metadata from GCS read channel#333
shrutisinghania wants to merge 6 commits into
GoogleCloudPlatform:mainfrom
shrutisinghania:reflective-metadata-extraction

Conversation

@shrutisinghania

Copy link
Copy Markdown
Member

Type of Change

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • chore: Changes to the build process or auxiliary tools and libraries such as documentation generation

Description

What?

Implements reflective extraction of object size and generation from the GCS SDK ReadChannel after the first read.

Why?

This extraction allows optimizations like Parquet footer prefetching to run seamlessly after the first read even if file metadata is not provided by the user.

Checklist

  • PR title follows Conventional Commits (e.g., feat(core): ...)
  • All files include the Apache License 2.0 header
  • Documentation has been updated to reflect changes

Generated/Assisted by Agent? [Yes/No]

@shrutisinghania shrutisinghania requested a review from a team as a code owner July 3, 2026 06:23
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a mechanism to extract file metadata (size and generation) from the GCS SDK ReadChannel using reflection. By capturing this information after the initial read, the system can perform optimizations like Parquet footer prefetching without requiring upfront metadata, improving performance in scenarios where such details are not provided by the user.

Highlights

  • Metadata Extraction: Implemented a reflective utility, GcsReadChannelMetadataExtractor, to retrieve object size and generation from the GCS SDK ReadChannel.
  • Performance Optimization: Enabled seamless Parquet footer prefetching by extracting metadata after the first read, even when not explicitly provided by the user.
  • API Updates: Updated ReadStrategy and VectoredSeekableByteChannel interfaces to expose the underlying SDK ReadChannel and item metadata.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces dynamic metadata extraction (size and generation) from GCS SDK ReadChannel instances using reflection via a new GcsReadChannelMetadataExtractor utility. This allows GcsReadChannel to update its metadata after reading when itemInfo is not initially provided, which is then utilized by GcsFooterOptimizer and other components. The code reviewer identified several critical issues: a potential crash from unhandled CancellationException when resolving futures, a performance bottleneck where failed metadata extractions trigger reflection on every subsequent chunk read, and concurrency issues (data races) due to non-volatile fields (itemInfo and itemId) during concurrent reads. Additionally, the reviewer recommended excluding Collection types from fallback size extraction to prevent incorrect size values.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.27273% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.62%. Comparing base (d866f24) to head (bf0ab92).

Files with missing lines Patch % Lines
...cloud/gcs/analyticscore/client/GcsReadChannel.java 93.10% 0 Missing and 2 partials ⚠️
...lyticscore/client/VectoredSeekableByteChannel.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #333      +/-   ##
============================================
+ Coverage     97.44%   97.62%   +0.18%     
- Complexity      439      482      +43     
============================================
  Files            33       35       +2     
  Lines          1407     1516     +109     
  Branches        129      152      +23     
============================================
+ Hits           1371     1480     +109     
+ Misses           20       19       -1     
- Partials         16       17       +1     
Files with missing lines Coverage Δ
...gcs/analyticscore/client/AbstractReadStrategy.java 96.61% <100.00%> (+0.05%) ⬆️
...gcs/analyticscore/client/AdaptiveReadStrategy.java 100.00% <100.00%> (ø)
...d/gcs/analyticscore/client/GcsBidiReadChannel.java 90.09% <100.00%> (+0.09%) ⬆️
...cscore/client/GcsReadChannelMetadataExtractor.java 100.00% <100.00%> (ø)
...s/analyticscore/core/channel/SmartReadChannel.java 99.06% <100.00%> (+<0.01%) ⬆️
...alyticscore/core/optimizer/GcsFooterOptimizer.java 96.92% <100.00%> (+0.14%) ⬆️
...lyticscore/client/VectoredSeekableByteChannel.java 0.00% <0.00%> (ø)
...cloud/gcs/analyticscore/client/GcsReadChannel.java 95.83% <93.10%> (+1.35%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

private int readNextChunk(ByteBuffer dst) throws IOException {
ReadChannel sdkChannel = strategy.getReadChannel(gcsReadChannelPosition, dst.remaining());
int bytesRead = sdkChannel.read(dst);
if (this.itemInfo == null && !metadataExtractionAttempted) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we can drop this

int numOfBytesRead = 0;
while (dataBuffer.hasRemaining()) {
int bytesRead = channel.read(dataBuffer);
if (GcsReadChannel.this.itemInfo == null && !metadataExtractionAttempted) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Drop "GcsReadChannel.this."


private synchronized boolean extractMetadataAfterRead(ReadStrategy strategy) {
metadataExtractionAttempted = true;
GcsReadChannelMetadataExtractor.ExtractedMetadata metadata =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason for keeping this type qualified ?

return true;
}

private void updateItemMetadata(long extractedSize, long extractedGen) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can drop "this"

Use "this" only if the reference is ambigious.

this.itemId = updatedItemId;
}

private volatile boolean metadataExtractionAttempted = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please format. Move this to variable declaration section.

fileSize = source.size();
GcsItemInfo info = source.getItemInfo();
if (info == null) {
return 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change breaks footer prefetching.

In metadata extraction Failure Case:
The item info is never populated.

In metadata extraction Success Case:
The item info is populated only after first read. So the first read will still be served from gcs and not cached.

GcsItemInfo.builder().setItemId(updatedItemId).setSize(extractedSize);
if (genToSet > 0) {
itemInfoBuilder.setContentGeneration(genToSet);
} else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is redundant. we should leave this unset. Please remove.

Class<?> clazz = sdkChannel.getClass();
while (clazz != null) {
for (String methodName :
new String[] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you please create these as static constants instead.

LOG.debug("Method {} not present on class {}", methodName, clazz.getName());
}
}
for (String fieldName : new String[] {"storageObject", "blobInfo", "object", "result"}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you please create these as static constants

return true;
}

private void updateItemMetadata(long extractedSize, long extractedGen) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: A bit confusing to read. Can we reorganize as follows.

extract generation
newline
itemId creation
newline
iteminfo creation
newline
set itemId and itemInfo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants