Skip to content

[DO NOT MERGE] Large export with Databoost temp fix - #4223

Open
darshan-sj wants to merge 1 commit into
mainfrom
large-export-fix
Open

[DO NOT MERGE] Large export with Databoost temp fix#4223
darshan-sj wants to merge 1 commit into
mainfrom
large-export-fix

Conversation

@darshan-sj

Copy link
Copy Markdown
Contributor

No description provided.

@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 implements temporary performance optimizations for large Spanner exports. By introducing custom partitioning logic for both writing Avro files and reading partitions, the changes aim to improve stability and efficiency when handling massive datasets, avoiding common bottlenecks associated with default shuffle operations.

Highlights

  • Export Performance Optimization: Introduced a custom AvroWriter that partitions data into 128 lanes to improve throughput and prevent spilling during large exports.
  • Read Partitioning Strategy: Replaced Reshuffle with a fixed-bucket grouping strategy in BatchSpannerRead to better manage read operation distribution.
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 refactors the Spanner export pipeline by introducing a custom AvroWriter that partitions rows into 128 lanes to write Avro files, and replaces Reshuffle.viaRandomKey() in BatchSpannerRead with a custom 400-bucket grouping and flattening mechanism. Feedback on these changes highlights a potential ArrayIndexOutOfBoundsException in the partitioning logic when dealing with Integer.MIN_VALUE hashes, performance concerns regarding the 128-branch Beam DAG size, and potential serialization issues due to anonymous inner DoFn classes capturing the outer class instance.

Comment on lines +853 to +863
PCollectionList<Struct> partitionedRows =
input.apply(
"Partition by table",
org.apache.beam.sdk.transforms.Partition.of(
128,
new org.apache.beam.sdk.transforms.Partition.PartitionFn<Struct>() {
@Override
public int partitionFor(Struct elem, int numPartitions) {
return Math.abs(elem.getString(0).hashCode()) % numPartitions;
}
}));

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

Using Math.abs(hash) % numPartitions can result in a negative partition index if the hash code is Integer.MIN_VALUE (since Math.abs(Integer.MIN_VALUE) is still Integer.MIN_VALUE). If numPartitions is not a power of two, or if the code is modified in the future, this will throw an ArrayIndexOutOfBoundsException.

To prevent this, use Math.floorMod to ensure a non-negative index.

Suggested change
PCollectionList<Struct> partitionedRows =
input.apply(
"Partition by table",
org.apache.beam.sdk.transforms.Partition.of(
128,
new org.apache.beam.sdk.transforms.Partition.PartitionFn<Struct>() {
@Override
public int partitionFor(Struct elem, int numPartitions) {
return Math.abs(elem.getString(0).hashCode()) % numPartitions;
}
}));
PCollectionList<Struct> partitionedRows =
input.apply(
"Partition by table",
org.apache.beam.sdk.transforms.Partition.of(
128,
new org.apache.beam.sdk.transforms.Partition.PartitionFn<Struct>() {
@Override
public int partitionFor(Struct elem, int numPartitions) {
return Math.floorMod(elem.getString(0).hashCode(), numPartitions);
}
}));

Comment on lines +865 to +880
PCollectionList<KV<String, String>> allResults = PCollectionList.empty(input.getPipeline());
for (int i = 0; i < 128; i++) {
org.apache.beam.sdk.io.WriteFilesResult<String> res =
partitionedRows
.get(i)
.apply(
"Write lane " + i,
AvroIO.<Struct>writeCustomTypeToGenericRecords()
.to(
new SchemaBasedDynamicDestinations(
avroSchemas, uniqueIdView, dialectView, baseDir))
.withTempDirectory(tempDir)
.withNoSpilling());
allResults = allResults.and(res.getPerDestinationOutputFilenames());
}
return allResults.apply("Flatten filenames", Flatten.pCollections());

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

Creating 128 separate branches in the Beam DAG (each with its own AvroIO.write() transform) can significantly increase the pipeline graph size. This can lead to slow pipeline submission times, high memory usage on the job submitter, and potential graph size limit issues in runners like Dataflow.

While this is a temporary fix to avoid OOM/file limits by grouping tables into lanes, consider making the number of lanes configurable or using a single AvroIO.write() with optimized runner settings if possible.

Comment on lines +108 to +135
.apply(
"Assign Fixed Bucket",
ParDo.of(
new DoFn<
PartitionedReadOperation,
org.apache.beam.sdk.values.KV<Integer, PartitionedReadOperation>>() {
@ProcessElement
public void processElement(ProcessContext c) {
c.output(
org.apache.beam.sdk.values.KV.of(
java.util.concurrent.ThreadLocalRandom.current().nextInt(400),
c.element()));
}
}))
.apply("Group by Fixed Bucket", org.apache.beam.sdk.transforms.GroupByKey.create())
.apply(
"Flatten Fixed Buckets",
ParDo.of(
new DoFn<
org.apache.beam.sdk.values.KV<Integer, Iterable<PartitionedReadOperation>>,
PartitionedReadOperation>() {
@ProcessElement
public void processElement(ProcessContext c) {
for (PartitionedReadOperation op : c.element().getValue()) {
c.output(op);
}
}
}))

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 anonymous inner DoFn classes used here are defined within the instance method expand of BatchSpannerRead. In Java, non-static anonymous inner classes capture a reference to their enclosing outer class instance (BatchSpannerRead). This can lead to serialization overhead or NotSerializableException if the outer class or its fields are not serializable or change in the future.

To avoid capturing the outer instance, you can use MapElements and FlatMapElements with lambda expressions, which are cleaner and completely inline.

        .apply(
            "Assign Fixed Bucket",
            org.apache.beam.sdk.transforms.MapElements.into(
                    org.apache.beam.sdk.values.TypeDescriptors.kvs(
                        org.apache.beam.sdk.values.TypeDescriptors.integers(),
                        org.apache.beam.sdk.values.TypeDescriptor.of(PartitionedReadOperation.class)))
                .via(
                    op ->
                        org.apache.beam.sdk.values.KV.of(
                            java.util.concurrent.ThreadLocalRandom.current().nextInt(400),
                            op)))
        .apply("Group by Fixed Bucket", org.apache.beam.sdk.transforms.GroupByKey.create())
        .apply(
            "Flatten Fixed Buckets",
            org.apache.beam.sdk.transforms.FlatMapElements.into(
                    org.apache.beam.sdk.values.TypeDescriptor.of(PartitionedReadOperation.class))
                .via(kv -> kv.getValue()))

@codecov

codecov Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 38.09524% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.93%. Comparing base (ab8f188) to head (1add850).

Files with missing lines Patch % Lines
...google/cloud/teleport/spanner/ExportTransform.java 0.00% 26 Missing ⚠️

❌ Your patch check has failed because the patch coverage (38.09%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #4223      +/-   ##
============================================
- Coverage     55.94%   55.93%   -0.01%     
+ Complexity     7484     7031     -453     
============================================
  Files          1134     1134              
  Lines         70175    70210      +35     
  Branches       8023     8025       +2     
============================================
+ Hits          39258    39271      +13     
- Misses        28370    28388      +18     
- Partials       2547     2551       +4     
Components Coverage Δ
spanner-templates 84.70% <ø> (-0.01%) ⬇️
spanner-import-export 68.89% <38.09%> (-0.13%) ⬇️
spanner-live-forward-migration 88.67% <ø> (-0.02%) ⬇️
spanner-live-reverse-replication 81.38% <ø> (-0.02%) ⬇️
spanner-bulk-migration 89.07% <ø> (-0.01%) ⬇️
gcs-spanner-dv 87.90% <ø> (-0.02%) ⬇️
Files with missing lines Coverage Δ
...d/teleport/spanner/spannerio/BatchSpannerRead.java 95.38% <100.00%> (+0.60%) ⬆️
...google/cloud/teleport/spanner/ExportTransform.java 15.83% <0.00%> (-0.61%) ⬇️

... and 4 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.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant