Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a0c7bec
feat(datastream-mongodb-to-firestore): implement high-throughput shad…
michaeltle-goog Aug 10, 2026
83ce0eb
fix(datastream-mongodb-to-firestore): fix timestamp sort key ordering…
michaeltle-goog Aug 11, 2026
8324c3f
style: apply spotless formatting
michaeltle-goog Aug 11, 2026
a4c6f78
fix(utils): add bool_ prefix to documentIdToString and add distinctne…
michaeltle-goog Aug 11, 2026
6983c6f
refactor: remove redundant read_method and _metadata_dlq_reconsumed c…
michaeltle-goog Aug 11, 2026
0137ad1
fix(review): address performance, thread-safety, defensive casting an…
michaeltle-goog Aug 11, 2026
440496b
perf(datastream-mongodb-to-firestore): eliminate CombineHotKeys windo…
michaeltle-goog Aug 11, 2026
cd1c08b
perf(datastream-mongodb-to-firestore): implement deterministic binary…
michaeltle-goog Aug 11, 2026
72bdcd7
perf(datastream-mongodb-to-firestore): decouple processing timestamps…
michaeltle-goog Aug 11, 2026
f4d5a8f
Revert "perf(datastream-mongodb-to-firestore): decouple processing ti…
michaeltle-goog Aug 11, 2026
ec9a813
refactor(datastream-mongodb-to-firestore): remove orderingStrategy pa…
michaeltle-goog Aug 11, 2026
9e4c0bf
Address review comments: 64-bit subSeconds coder, databaseName valida…
michaeltle-goog Aug 14, 2026
168bd30
Refactor ThrottledLogger, consolidate DatastreamConstants, standardiz…
michaeltle-goog Aug 14, 2026
add53a5
fix(datastream-mongodb-to-firestore): preserve customer document 'dat…
michaeltle-goog Aug 18, 2026
5df3db8
perf(datastream-mongodb-to-firestore): increase default write rate li…
michaeltle-goog Aug 20, 2026
6715eda
Revert "perf(datastream-mongodb-to-firestore): increase default write…
michaeltle-goog Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ private boolean isDlqReconsumed(JsonNode changeEvent) {
.asText()
.equalsIgnoreCase("true");
}
if (changeEvent.has("_metadata_dlq_reconsumed")) {
return changeEvent.get("_metadata_dlq_reconsumed").asText().equalsIgnoreCase("true");
}
return false;
}

Expand Down Expand Up @@ -143,11 +146,13 @@ public MongoDbChangeEventContext(
this.documentId = docIdVal.asDouble();
} else if (docIdVal.isTextual()) {
this.documentId = docIdVal.asText();
} else if (docIdVal.isObject()) {
if (docIdVal.has(OID_FIELD_NAME) && docIdVal.get(OID_FIELD_NAME).isTextual()) {
} else if (docIdVal.isObject() || docIdVal.isArray()) {
if (docIdVal.isObject()
&& docIdVal.has(OID_FIELD_NAME)
&& docIdVal.get(OID_FIELD_NAME).isTextual()) {
this.documentId = new ObjectId(docIdVal.get(OID_FIELD_NAME).asText());
} else {
// Support for generic Object-typed IDs or other complex BSON types (e.g., Binary)
// Support for generic Document (Map), Array (List), Binary, and composite BSON _id types
Document wrapper = Document.parse("{ \"val\": " + docIdVal.toString() + " }");
this.documentId = wrapper.get("val");
}
Expand Down Expand Up @@ -182,7 +187,7 @@ public MongoDbChangeEventContext(
this.isUpdateEvent = isUpdateEvent(changeEvent);

this.jsonStringData = dataAsJsonString();
this.shadowDocument = generateShadowDocument();
this.shadowDocument = null;
this.isDlqReconsumed = isDlqReconsumed(changeEvent);
}

Expand Down Expand Up @@ -245,6 +250,13 @@ public boolean isUpdateEvent() {
}

public Document getShadowDocument() {
if (this.shadowDocument == null && this.shadowCollection != null) {
try {
return generateShadowDocument();
} catch (JsonProcessingException e) {
LOG.warn("Failed to generate shadow document: {}", e.getMessage());
}
}
return shadowDocument;
}

Expand Down Expand Up @@ -317,10 +329,35 @@ public String toString() {
}
}

@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other instanceof MongoDbChangeEventContext) {
return Objects.equals(this.toString(), other.toString());
MongoDbChangeEventContext o = (MongoDbChangeEventContext) other;
return Objects.equals(this.dataCollection, o.dataCollection)
&& Objects.equals(this.documentId, o.documentId)
&& Objects.equals(this.timestampDoc, o.timestampDoc)
&& this.isDeleteEvent == o.isDeleteEvent
&& this.isUpdateEvent == o.isUpdateEvent
&& this.isDlqReconsumed == o.isDlqReconsumed
&& this.retryCount == o.retryCount
&& Objects.equals(this.changeEvent, o.changeEvent);
}
return false;
}

@Override
public int hashCode() {
return Objects.hash(
dataCollection,
documentId,
timestampDoc,
isDeleteEvent,
isUpdateEvent,
isDlqReconsumed,
retryCount,
changeEvent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.cloud.teleport.v2.transforms;

import com.google.cloud.teleport.v2.templates.datastream.MongoDbChangeEventContext;
import org.apache.beam.sdk.transforms.Combine.CombineFn;
import org.bson.Document;

/**
* High-performance CombineFn that compacts bursts of change events for the same document key into
* the latest monotonic event in worker RAM before shuffle and state evaluation.
*/
public class LatestChangeEventCombineFn
extends CombineFn<
MongoDbChangeEventContext, MongoDbChangeEventContext, MongoDbChangeEventContext> {

@Override
public MongoDbChangeEventContext createAccumulator() {
return null;
}

@Override
public MongoDbChangeEventContext addInput(
MongoDbChangeEventContext accumulator, MongoDbChangeEventContext input) {
if (accumulator == null) {
return input;
}
if (input == null) {
return accumulator;
}

Document accTsDoc = accumulator.getTimestampDoc();
Document inputTsDoc = input.getTimestampDoc();

long accTs = Utils.getTimestampNanos(accTsDoc);
long inputTs = Utils.getTimestampNanos(inputTsDoc);

// If input is strictly newer, or equal with DLQ reconsumption, prefer input
if (inputTs > accTs || (inputTs == accTs && input.getIsDlqReconsumed())) {
return input;
}
return accumulator;
}

@Override
public MongoDbChangeEventContext mergeAccumulators(
Iterable<MongoDbChangeEventContext> accumulators) {
MongoDbChangeEventContext winner = null;
long maxTs = Long.MIN_VALUE;

for (MongoDbChangeEventContext acc : accumulators) {
if (acc != null) {
long ts = Utils.getTimestampNanos(acc.getTimestampDoc());
if (winner == null || ts > maxTs || (ts == maxTs && acc.getIsDlqReconsumed())) {
maxTs = ts;
winner = acc;
}
}
}
return winner;
}

@Override
public MongoDbChangeEventContext extractOutput(MongoDbChangeEventContext accumulator) {
return accumulator;
}
}
Loading
Loading