Skip to content

[Backport to master] Analytics core migration - #1782

Open
shrutisinghania wants to merge 26 commits into
masterfrom
analytics-core-migration
Open

[Backport to master] Analytics core migration#1782
shrutisinghania wants to merge 26 commits into
masterfrom
analytics-core-migration

Conversation

@shrutisinghania

Copy link
Copy Markdown
Collaborator

No description provided.

shrutisinghania and others added 26 commits April 20, 2026 16:43
* Add flag for analytics-write path

* Updated flag name

* Rename setAnalyticsWriteEnabled to setAnalyticsCoreWriteEnabled
…am (#1767)

* Integrate Analytics core write path to connector

* Updated code for Gemini Code Assist comments

* Updated code to wrap Analytics core output stream in BufferedOutputStream

* Fixed buidl failure

* Updated code for the review comments

* Fixed excetion handling with getFileInfo

* Added unit tests for improving code coverage

* Address review comments for Analytics Core write path integration

* Add a TODO for exception handling

* Updated as per review comments
* Map hadoop-connector write flags to gcs-analytics core flags

* Fix flag mapping for checksum

* Updated code as per review comments

* Updated code for review comments
* Add integration tests

* Refactor setupGhfs to accept URI and improve exception assertions in integration tests

* Fixed test formatting

* standardize setupGhfs and add byte slice write test

* Add a TODO and updated tests to not use hard-coded values

* Update tests for the comments

* Update TODO
@sonarqubecloud

Copy link
Copy Markdown

@gemini-code-assist

Copy link
Copy Markdown

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 integrates the GCS Analytics Core library into the GCS Hadoop Connector. It provides the necessary infrastructure to map existing Hadoop configurations to the new library, wraps input and output streams to support the new implementation, and adds feature flags to control the migration. Extensive testing has been included to ensure compatibility and performance.

Highlights

  • Dependency Management: Added the gcs-analytics-core dependency and updated build configurations to support the new library.
  • Configuration Mapping: Introduced AnalyticsCoreConfigMapper to translate existing Hadoop configurations into the format required by the Analytics Core library.
  • Stream Integration: Implemented wrappers for InputStream and OutputStream to bridge the GCS Hadoop Connector with the Analytics Core library, ensuring compatibility.
  • Feature Flagging: Added new configuration properties to enable or disable Analytics Core features, allowing for controlled migration.
  • Testing: Added extensive unit and integration tests to verify the new functionality and ensure existing operations remain stable.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

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

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 integrates the GCS Analytics Core library into the GCS Hadoop Connector, introducing configuration properties to enable it, mapping configurations, and wrapping the input and output streams to route operations through the new library. Feedback on the changes highlights several critical areas for improvement: addressing thread-safety and idempotency issues in the input stream wrapper's close() method; removing redundant synchronized modifiers in the output stream wrapper to adhere to the style guide (and updating tests accordingly); preventing a potential NullPointerException in vectored reads; ensuring proper input validation in readFully(); avoiding eager initialization of the GCS filesystem to preserve lazy initialization; and replacing fragile string-matching exception handling with proper exception translation.

Comment on lines +115 to +121
public void close() throws IOException {
if (channelIsOpen) {
inputStream.close();
channelIsOpen = false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The channelIsOpen flag is not declared as volatile, and the close() method is not synchronized. Since readVectored is not synchronized and can be executed concurrently by multiple threads, there is a visibility and thread-safety issue. If close() is called concurrently with other operations, or called multiple times, it can lead to race conditions or multiple close calls on the underlying stream. Making channelIsOpen volatile and synchronizing close() ensures thread safety and idempotency.

Suggested change
public void close() throws IOException {
if (channelIsOpen) {
inputStream.close();
channelIsOpen = false;
}
}
@Override
public synchronized void close() throws IOException {
if (channelIsOpen) {
channelIsOpen = false;
inputStream.close();
}
}

Comment on lines +42 to +64
@Override
public synchronized void write(int b) throws IOException {
delegate.write(b);
}

@Override
public synchronized void write(byte[] b, int off, int len) throws IOException {
logger.atFine().log("write(byte[], off=%d, len=%d)", off, len);
delegate.write(b, off, len);
}

@Override
public synchronized void close() throws IOException {
if (closed) {
logger.atFiner().log("close(): Stream already closed, ignoring.");
return;
}
try {
delegate.close();
} finally {
closed = true;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

According to the Repository Style Guide (line 31), redundant synchronized modifiers should not be added to wrapper classes or methods where the underlying stream (such as Hadoop's FSDataOutputStream) is single-threaded by specification. Since this output stream wrapper is always used within Hadoop's FSDataOutputStream write path, the synchronized modifiers on write and close methods are redundant and should be removed.

  @Override
  public void write(int b) throws IOException {
    delegate.write(b);
  }

  @Override
  public void write(byte[] b, int off, int len) throws IOException {
    logger.atFine().log("write(byte[], off=%d, len=%d)", off, len);
    delegate.write(b, off, len);
  }

  @Override
  public void close() throws IOException {
    if (closed) {
      logger.atFiner().log("close(): Stream already closed, ignoring.");
      return;
    }
    try {
      delegate.close();
    } finally {
      closed = true;
    }
  }
References
  1. Do NOT add redundant synchronized modifiers to wrapper classes or methods where the underlying stream (such as Hadoop's FSDataOutputStream) is single-threaded by specification. (link)

Comment on lines +72 to +78
@Test
public void methodsAreSynchronized() throws NoSuchMethodException {
verifyMethodIsSynchronized(GcsAnalyticsCoreOutputStreamWrapper.class, "write", int.class);
verifyMethodIsSynchronized(
GcsAnalyticsCoreOutputStreamWrapper.class, "write", byte[].class, int.class, int.class);
verifyMethodIsSynchronized(GcsAnalyticsCoreOutputStreamWrapper.class, "close");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If the redundant synchronized modifiers are removed from GcsAnalyticsCoreOutputStreamWrapper to adhere to the style guide, the methodsAreSynchronized test will fail. This test should be removed or updated accordingly.

Comment on lines +239 to +240
CompletableFuture<?>[] futures =
ranges.stream().map(FileRange::getData).toArray(CompletableFuture[]::new);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If any range's data future is not initialized or is set to null (e.g., due to an unexpected issue in the underlying readVectored implementation), CompletableFuture.allOf(futures) will throw a NullPointerException. Adding a defensive filter to exclude null futures is highly recommended.

    CompletableFuture<?>[] futures =
        ranges.stream()
            .map(FileRange::getData)
            .filter(java.util.Objects::nonNull)
            .toArray(CompletableFuture[]::new);

Comment on lines +387 to +392
public synchronized void readFully(long position, byte[] buffer, int offset, int length)
throws IOException {
if (length == 0) {
return;
}
if (channel instanceof GcsAnalyticsCoreInputStreamWrapper) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When channel is an instance of GcsAnalyticsCoreInputStreamWrapper, the readFully method bypasses super.readFully and directly delegates to the wrapper. However, this also bypasses the standard input validation performed by super.readFully (such as checking if position < 0). To ensure consistent fail-fast behavior and compatibility with the Hadoop specification, an explicit check for position < 0 should be added.

  @Override
  public synchronized void readFully(long position, byte[] buffer, int offset, int length)
      throws IOException {
    if (length == 0) {
      return;
    }
    if (position < 0) {
      throw new java.io.EOFException("Explicit read position cannot be negative: " + position);
    }

Comment on lines +415 to +419
// TODO(user): Initialize analyticsCoreGcsFs lazily when GCS_LAZY_INITIALIZATION_ENABLE is
// true, to avoid eager initialization.
if (isAnalyticsCoreEnabled() || isAnalyticsCoreWriteEnabled()) {
analyticsCoreGcsFs = createAnalyticsGcsFs(config);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Eagerly initializing analyticsCoreGcsFs during initialize() defeats the purpose of lazy initialization when GCS_LAZY_INITIALIZATION_ENABLE is set to true. This can cause unnecessary credential resolution and connection setup on client/driver nodes where GCS is not actually accessed. analyticsCoreGcsFs should be initialized lazily on demand (e.g., inside getAnalyticsCoreGcsFs()).

Comment on lines +240 to +249
} catch (IOException e) {
// TODO(user): Remove this exception message check once analytics-core throws a specific
// FileNotFoundException / NoSuchFileException for non-existent objects.
if (e.getMessage() != null && e.getMessage().startsWith("Object not found:")) {
// If file does not exist, set the generation as 0.
generation = 0L;
} else {
throw e;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Relying on e.getMessage().startsWith("Object not found:") to detect a non-existent file is extremely fragile and prone to breaking if the underlying library changes its error message format or localization. According to the style guide (line 43), lower-level exceptions should be translated into standard Hadoop IOException subclasses (such as FileNotFoundException). The analytics-core library or the connector's adapter layer should throw FileNotFoundException for non-existent objects, allowing this block to catch FileNotFoundException directly.

References
  1. Lower-level GCS/gRPC exceptions must be translated into standard Hadoop IOException subclasses. (link)

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.

7 participants