Skip to content

feat(datastream-mongodb-to-firestore): implement high-throughput shadowless migration - #4128

Open
michaeltle-goog wants to merge 16 commits into
GoogleCloudPlatform:mainfrom
michaeltle-goog:datastream-shadowless-firestore
Open

feat(datastream-mongodb-to-firestore): implement high-throughput shadowless migration#4128
michaeltle-goog wants to merge 16 commits into
GoogleCloudPlatform:mainfrom
michaeltle-goog:datastream-shadowless-firestore

Conversation

@michaeltle-goog

Copy link
Copy Markdown
Contributor
  • Implement shadowless stateful deduplication and monotonic ordering via StatefulDeduplicationFn and LatestChangeEventCombineFn.
  • Add asynchronous non-transactional bulk write engine with configurable rate ramp-up and multi-collection batching in MongoDbBulkTransforms.
  • Implement two-tier DLQ routing (retryable vs severe permanent failure triage) with ThrottledLogger.
  • Support collision-free BSON type preservation and type-tagged documentIdToString.
  • Add comprehensive unit test suites covering stateful deduplication, combiner pre-compaction, bulk writing, and error classification.

@michaeltle-goog
michaeltle-goog requested a review from a team as a code owner August 10, 2026 18:24
@michaeltle-goog
michaeltle-goog marked this pull request as draft August 10, 2026 18: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 a high-throughput shadowless migration mode for the Datastream to Firestore (MongoDB compatibility) pipeline. By leveraging in-memory stateful processing and an asynchronous bulk write engine, the pipeline can now achieve significantly higher throughput while maintaining data consistency and ordering. The changes also include robust error handling with a two-tier dead-letter queue system and improved BSON type support.

Highlights

  • High-Throughput Shadowless Migration: Implemented a new high-throughput shadowless migration mode that eliminates the need for shadow collections and distributed transactions, significantly improving performance.
  • Stateful Deduplication and Ordering: Added StatefulDeduplicationFn and LatestChangeEventCombineFn to ensure monotonic timestamp ordering and deduplication in-memory, reducing database round-trips.
  • Asynchronous Bulk Write Engine: Introduced an asynchronous non-transactional bulk write engine in MongoDbBulkTransforms with configurable rate ramp-up and multi-collection batching.
  • Enhanced DLQ Routing: Implemented a two-tier DLQ routing system (retryable vs. severe permanent failure triage) supported by a ThrottledLogger to improve error handling and observability.
  • BSON Type Preservation: Added support for collision-free BSON type preservation and type-tagged document ID serialization to ensure data integrity during migration.
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.

…owless migration

- Implement shadowless stateful deduplication and monotonic ordering via StatefulDeduplicationFn and LatestChangeEventCombineFn.
- Add asynchronous non-transactional bulk write engine with configurable rate ramp-up and multi-collection batching in MongoDbBulkTransforms.
- Implement two-tier DLQ routing (retryable vs severe permanent failure triage) with ThrottledLogger.
- Support collision-free BSON type preservation and type-tagged documentIdToString.
- Add comprehensive unit test suites covering stateful deduplication, combiner pre-compaction, bulk writing, and error classification.
@michaeltle-goog
michaeltle-goog force-pushed the datastream-shadowless-firestore branch from d89466e to a0c7bec Compare August 10, 2026 18: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 a high-throughput shadowless mode to the Datastream MongoDB to Firestore template, adding support for in-memory stateful deduplication, pre-compaction, and asynchronous bulk writes with rate limiting. Key feedback highlights critical issues in the new implementation: coalesced events in MongoDbBulkTransforms are prematurely marked as successful before the bulk write is attempted, risking silent data loss; ThrottledLogger.ensureInitialized() lacks thread safety; unhandled exceptions during collection retrieval and document parsing could crash the pipeline; and missing validation checks for connectionUri and the 500-document batchSize limit for Firestore compatibility should be addressed.

Comment on lines +693 to +713
Map<Object, MongoDbChangeEventContext> latestPerDoc = new java.util.LinkedHashMap<>();
for (MongoDbChangeEventContext event : batch) {
Object docId = event.getDocumentId();
MongoDbChangeEventContext existing = latestPerDoc.get(docId);
if (existing == null) {
latestPerDoc.put(docId, event);
} else {
long eventTs = Utils.getTimestampNanos(event.getTimestampDoc());
long existingTs = Utils.getTimestampNanos(existing.getTimestampDoc());
if (eventTs > existingTs || (eventTs == existingTs && event.getIsDlqReconsumed())) {
// Count coalesced older event as resolved/superseded
successQueue.add(existing);
successfulWrites.inc();
latestPerDoc.put(docId, event);
} else {
// Drop current event as superseded by earlier in-batch event
successQueue.add(event);
successfulWrites.inc();
}
}
}

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.

critical

Coalesced/superseded events are added to successQueue and marked as successful before the bulk write is even attempted. If the bulk write fails (either transiently or permanently), these older events will have already been reported as successful, leading to silent data loss or out-of-sync states. They should only be marked as successful after the active event succeeds or is safely written to the DLQ.

      Map<Object, MongoDbChangeEventContext> latestPerDoc = new java.util.LinkedHashMap<>();
      Map<Object, List<MongoDbChangeEventContext>> supersededPerDoc = new java.util.HashMap<>();
      for (MongoDbChangeEventContext event : batch) {
        Object docId = event.getDocumentId();
        MongoDbChangeEventContext existing = latestPerDoc.get(docId);
        if (existing == null) {
          latestPerDoc.put(docId, event);
        } else {
          long eventTs = Utils.getTimestampNanos(event.getTimestampDoc());
          long existingTs = Utils.getTimestampNanos(existing.getTimestampDoc());
          if (eventTs > existingTs || (eventTs == existingTs && event.getIsDlqReconsumed())) {
            supersededPerDoc.computeIfAbsent(docId, k -> new ArrayList<>()).add(existing);
            latestPerDoc.put(docId, event);
          } else {
            supersededPerDoc.computeIfAbsent(docId, k -> new ArrayList<>()).add(event);
          } 
        }
      }

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 48.88031% with 662 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.73%. Comparing base (66dfb9c) to head (6715eda).
⚠️ Report is 36 commits behind head on main.

Files with missing lines Patch % Lines
.../teleport/v2/transforms/MongoDbBulkTransforms.java 49.38% 229 Missing and 59 partials ⚠️
...ort/v2/templates/DataStreamMongoDBToFirestore.java 11.03% 255 Missing and 11 partials ⚠️
.../cloud/teleport/v2/transforms/ThrottledLogger.java 68.46% 27 Missing and 14 partials ⚠️
...emplates/datastream/MongoDbChangeEventContext.java 60.24% 10 Missing and 23 partials ⚠️
...com/google/cloud/teleport/v2/transforms/Utils.java 71.23% 6 Missing and 15 partials ⚠️
...eleport/v2/transforms/StatefulDeduplicationFn.java 86.04% 1 Missing and 5 partials ⚠️
...cloud/teleport/v2/transforms/TimestampSortKey.java 81.81% 4 Missing and 2 partials ⚠️
...d/teleport/v2/transforms/ProcessChangeEventFn.java 85.71% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (48.88%) 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    #4128      +/-   ##
============================================
- Coverage     56.18%   55.73%   -0.46%     
- Complexity     7342     7553     +211     
============================================
  Files          1126     1138      +12     
  Lines         68766    70774    +2008     
  Branches       7785     8128     +343     
============================================
+ Hits          38637    39446     +809     
- Misses        27635    28715    +1080     
- Partials       2494     2613     +119     
Components Coverage Δ
spanner-templates 84.69% <ø> (-2.85%) ⬇️
spanner-import-export 69.02% <ø> (+0.11%) ⬆️
spanner-live-forward-migration 88.81% <ø> (-0.50%) ⬇️
spanner-live-reverse-replication 81.38% <ø> (-2.12%) ⬇️
spanner-bulk-migration 89.14% <ø> (-3.01%) ⬇️
gcs-spanner-dv 88.03% <ø> (-0.57%) ⬇️
Files with missing lines Coverage Δ
...t/v2/templates/datastream/DatastreamConstants.java 100.00% <100.00%> (+100.00%) ⬆️
.../transforms/CreateMongoDbChangeEventContextFn.java 100.00% <100.00%> (ø)
.../v2/transforms/MongoDbChangeEventContextCoder.java 100.00% <100.00%> (ø)
.../teleport/v2/transforms/TimestampSortKeyCoder.java 100.00% <100.00%> (ø)
...d/teleport/v2/transforms/ProcessChangeEventFn.java 75.56% <85.71%> (ø)
...eleport/v2/transforms/StatefulDeduplicationFn.java 86.04% <86.04%> (ø)
...cloud/teleport/v2/transforms/TimestampSortKey.java 81.81% <81.81%> (ø)
...com/google/cloud/teleport/v2/transforms/Utils.java 77.77% <71.23%> (-15.84%) ⬇️
...emplates/datastream/MongoDbChangeEventContext.java 71.57% <60.24%> (-4.77%) ⬇️
.../cloud/teleport/v2/transforms/ThrottledLogger.java 68.46% <68.46%> (ø)
... and 2 more

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

… and address PR 4128 review comments

- Disentangle CDC oplog increment and Backfill wall-clock nanoseconds comparison using composite monotonic TimestampSortKey to fix 30-orphan document discrepancy.
- Defer emission of intra-batch superseded events in MongoDbBulkTransforms until bulkWrite succeeds; properly thread supersededPerDoc through retries and DLQ routing.
- Add connectionUri non-null, non-empty, and protocol prefix (mongodb://, mongodb+srv://) validation in validateOptions().
- Make ThrottledLogger thread-safe with volatile fields, double-checked locking, and readObject deserialization hook.
- Enclose executeBatch operation setup and execution inside try-catch with resilient DLQ fallback.
- Add comprehensive unit tests covering TimestampSortKey, ThrottledLogger concurrency, URI validation, and partial bulk write failure triage.
@michaeltle-goog michaeltle-goog added the addition New feature or request label Aug 11, 2026
…w bottleneck and stream directly to StatefulDeduplication
… coders to eliminate GC overhead in StatefulDeduplication
… before keyed shuffle to eliminate watermark stalls
…mestamps before keyed shuffle to eliminate watermark stalls"

This reverts commit 72bdcd7.
…ram and make stateful deduplication default shadowless implementation
@michaeltle-goog
michaeltle-goog marked this pull request as ready for review August 14, 2026 19:10

@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 a high-throughput shadowless mode for the Datastream MongoDB to Firestore template, featuring stateful deduplication (StatefulDeduplicationFn) to ensure monotonic event ordering, a custom asynchronous bulk writer (MongoDbBulkTransforms) with rate-limiting and error triage, and deterministic binary coders to eliminate serialization overhead. The review feedback provides critical improvements, including implementing state expiry via processing-time timers in StatefulDeduplicationFn to prevent memory leaks, using LONG_CODER instead of INT_CODER in TimestampSortKeyCoder to avoid sub-second truncation bugs, and adding validation for databaseName in validateOptions to ensure early pipeline failures with proper logging.

Comment on lines +21 to +24
import org.apache.beam.sdk.state.StateSpec;
import org.apache.beam.sdk.state.StateSpecs;
import org.apache.beam.sdk.state.ValueState;
import org.apache.beam.sdk.transforms.DoFn;

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.

high

Add imports for Beam's Timer and Joda-Time's Duration to support state expiry and prevent memory leaks in streaming pipelines.

import org.apache.beam.sdk.state.StateSpec;
import org.apache.beam.sdk.state.StateSpecs;
import org.apache.beam.sdk.state.TimeDomain;
import org.apache.beam.sdk.state.Timer;
import org.apache.beam.sdk.state.TimerSpec;
import org.apache.beam.sdk.state.TimerSpecs;
import org.apache.beam.sdk.state.ValueState;
import org.apache.beam.sdk.transforms.DoFn;
import org.joda.time.Duration;

Comment on lines +38 to +40
@StateId("latestTimestamp")
private final StateSpec<ValueState<TimestampSortKey>> latestTimestampSpec =
StateSpecs.value(TimestampSortKeyCoder.of());

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.

high

Define a processing-time timer to clear the state after a period of inactivity (e.g., 3 days) to prevent infinite state growth and memory leaks.

Suggested change
@StateId("latestTimestamp")
private final StateSpec<ValueState<TimestampSortKey>> latestTimestampSpec =
StateSpecs.value(TimestampSortKeyCoder.of());
@StateId("latestTimestamp")
private final StateSpec<ValueState<TimestampSortKey>> latestTimestampSpec =
StateSpecs.value(TimestampSortKeyCoder.of());
@TimerId("expiryTimer")
private final TimerSpec expiryTimerSpec = TimerSpecs.timer(TimeDomain.PROCESSING_TIME);

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/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant