Skip to content

[Data Validation] Add Spanner failure injection test + Base class - #4124

Draft
aasthabharill wants to merge 8 commits into
mainfrom
dv-spanner-failure
Draft

[Data Validation] Add Spanner failure injection test + Base class#4124
aasthabharill wants to merge 8 commits into
mainfrom
dv-spanner-failure

Conversation

@aasthabharill

@aasthabharill aasthabharill commented Aug 10, 2026

Copy link
Copy Markdown
Member

@aasthabharill aasthabharill changed the title [Data Validation] Add failure injection tests [Data Validation] Add Spanner failure injection test + Base class Aug 10, 2026
@aasthabharill aasthabharill added the addition New feature or request label Aug 10, 2026
@aasthabharill
aasthabharill marked this pull request as ready for review August 10, 2026 12:24
@aasthabharill
aasthabharill requested a review from a team as a code owner August 10, 2026 12:24
@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 infrastructure to test the resilience of the GCS-to-Spanner Data Validation pipeline against transient Spanner errors. It adds a dedicated Maven profile for failure injection, updates the pipeline configuration to support error injection parameters, and includes a new integration test that simulates 'UNAVAILABLE' errors to ensure the pipeline's read operations recover correctly.

Highlights

  • Failure Injection Infrastructure: Added a new 'failureInjectionTest' Maven profile and 'GCSSpannerDVFTBase' class to support testing pipeline resilience under failure conditions.
  • Pipeline Configuration: Introduced 'failureInjectionParameter' to 'GCSSpannerDV' options, allowing the injection of transient errors during Spanner read operations.
  • Resilience Testing: Added 'GCSSpannerDVSpannerReadFT' to verify that the pipeline correctly handles transient 'UNAVAILABLE' errors from Spanner using exponential backoff.
  • Policy Improvements: Refactored 'InitialLimitedDurationErrorInjectionPolicy' to use 'AtomicLong' for thread-safe call counting.
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 failure injection testing capabilities for the gcs-spanner-dv template. It updates the InitialLimitedDurationErrorInjectionPolicy to use static fields for tracking state across instances, adds Maven profiles to switch between real and failure-injected Spanner services, and implements a new integration test (GCSSpannerDVSpannerReadFT) using a Flex Template launcher. Feedback focuses on addressing thread-safety issues caused by synchronizing on this for static fields, preventing state leakage across test runs in the same JVM, and correcting a parameter mismatch in the test base class where testName was used instead of the passed testId.

@aasthabharill
aasthabharill marked this pull request as draft August 10, 2026 12:32
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 55.93%. Comparing base (7d5cefd) to head (894aa41).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
...on/InitialLimitedDurationErrorInjectionPolicy.java 90.90% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #4124      +/-   ##
============================================
+ Coverage     55.89%   55.93%   +0.03%     
+ Complexity     7476     7023     -453     
============================================
  Files          1134     1133       -1     
  Lines         70135    70098      -37     
  Branches       8014     8013       -1     
============================================
+ Hits          39202    39207       +5     
+ Misses        28387    28345      -42     
  Partials       2546     2546              
Components Coverage Δ
spanner-templates 84.84% <ø> (+0.19%) ⬆️
spanner-import-export 68.91% <ø> (ø)
spanner-live-forward-migration 88.66% <ø> (ø)
spanner-live-reverse-replication 81.27% <ø> (ø)
spanner-bulk-migration 89.06% <ø> (ø)
gcs-spanner-dv 88.50% <ø> (+0.63%) ⬆️
Files with missing lines Coverage Δ
...on/InitialLimitedDurationErrorInjectionPolicy.java 92.98% <90.90%> (+9.64%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aasthabharill
aasthabharill marked this pull request as ready for review August 24, 2026 07:27

@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 failure injection testing capabilities to the GCS to Spanner Data Validator (gcs-spanner-dv) pipeline. It updates InitialLimitedDurationErrorInjectionPolicy to use static thread-safe fields (volatile and AtomicLong) with class-level synchronization, adds a failureInjectionParameter template option to GCSSpannerDV, and configures Maven profiles to support building with a failure-injected Spanner service. Additionally, it adds integration tests (GCSSpannerDVSpannerReadFT) to verify pipeline resilience against transient Spanner read failures. Feedback on the changes highlights that errorCodeToBeInjected in InitialLimitedDurationErrorInjectionPolicy is not initialized with a default value, which could leave it as null when the input is blank. Furthermore, the newly added test constructor_shouldUseDefaultErrorCodeIfBlank is missing assertions to verify this default behavior.

private static final AtomicLong callCount = new AtomicLong(0);
private final Duration injectionDuration;
private final String effectiveDurationParameter;
private String errorCodeToBeInjected;

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.

medium

The errorCodeToBeInjected field is not initialized with a default value. When the errorCode parameter is blank or missing in the input JSON, the constructor logs that it is using the default DEADLINE_EXCEEDED, but it does not actually assign a value to errorCodeToBeInjected, leaving it as null. Initializing it to Code.DEADLINE_EXCEEDED.name() by default ensures that the policy behaves as documented and avoids potential NullPointerExceptions in the caller.

Suggested change
private String errorCodeToBeInjected;
private String errorCodeToBeInjected = Code.DEADLINE_EXCEEDED.name();


private Instant startTime;
private static volatile Instant startTime = null;
private static final AtomicLong callCount = new AtomicLong(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why was AtomicLong needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Since we made the state static, all the threads on a Dataflow worker are now hitting the exact same counter. So if we were to use synchronized block, every thread would have to acquire the lock and wait. AtomicLong is better as incrementAndGet() allows threads to update the counter without blocking each other.

Comment on lines +129 to +131
synchronized (InitialLimitedDurationErrorInjectionPolicy.class) {
if (startTime == null) {
startTime = Instant.now(clock);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We are synchronising on the class now instead of the object of the class? Do I understand that right? What was the issue happening earlier?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

What was happening earlier:
The original issue was that Beam actually deserializes multiple instances of this policy per worker (usually one per thread or bundle). When the variables weren't static, each instance got its own isolated timer. This meant the duration kept resetting per-thread instead of applying globally - so i kept getting the same fake exception for each retry ultimately failing the job.

I fixed that by making the state static so it's shared across the worker. But once the state is static, locking on this (the instance) is unsafe because threads using different instances would acquire different locks, leading to race conditions. Synchronizing on the Class object ensures all instances share the exact same lock to initialize the global timer safely.

List<Mutation> mutations = new ArrayList<>();
Instant now = Instant.now().truncatedTo(java.time.temporal.ChronoUnit.MILLIS);

// We insert 500 rows to ensure Dataflow has enough data to potentially split bundles

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: replace 500 with NUM_RECORDS

@aasthabharill
aasthabharill marked this pull request as draft August 26, 2026 04:27

@aasthabharill aasthabharill left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

converting to draft as i triggered the failure injection tests using InitialLimitedDurationErrorInjectionPolicy and the bulk and reverse FTs are failing - will debug + add change to trigger FT github workflow on such PRs

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

Labels

addition New feature or request size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants