Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2340,6 +2340,7 @@ public void processElement(ProcessContext c, BoundedWindow window) throws Except

if (!uniqueMutationKeys.add(getKey(mutation))) {
flushBatch(contextAdapter);
uniqueMutationKeys.add(getKey(mutation));

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.

Let's verify the return value here as well.

@noseglid noseglid May 21, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I guess there's a risk a conflicting key was added after the flush, and before this add happens.

To truly address that we can't just have another check because it might happen again. Instead I think we should loop until we successfully add this mutation (and flush every time we fail). I'm happy to add that if you agree.

}

if (mutations.size() > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,52 @@ public void testDatastoreWriterFnWithDuplicateEntities() throws Exception {
.containsAll(Arrays.asList(expectedRequest1, expectedRequest2)));
}

/**
* Tests that three mutations for the same entity key result in three separate batches, each
* containing at most one mutation per key.
*/
@Test
public void testDatastoreWriterFnWithTripleDuplicateEntities() throws Exception {
List<Mutation> mutations = new ArrayList<>();
for (int i : Arrays.asList(0, 1, 0, 0)) {
mutations.add(
makeUpsert(
Entity.newBuilder()
.setKey(makeKey("key" + i))
.putProperties("value", makeValue(UUID.randomUUID().toString()).build())
.build())
.build());
}

ArgumentCaptor<CommitRequest> requestCaptor = ArgumentCaptor.forClass(CommitRequest.class);
CommitResponse response = CommitResponse.getDefaultInstance();
when(mockDatastore.commit(requestCaptor.capture()))
.thenReturn(response)
.thenReturn(response)
.thenReturn(response);

DatastoreWriterFn datastoreWriter =
new DatastoreWriterFn(
StaticValueProvider.of(PROJECT_ID),
null,
mockDatastoreFactory,
new FakeWriteBatcher());
DoFnTester<Mutation, DatastoreV1.WriteSuccessSummary> doFnTester =
DoFnTester.of(datastoreWriter);
doFnTester.setCloningBehavior(CloningBehavior.DO_NOT_CLONE);
doFnTester.processBundle(mutations);

// Verify that no commit request contains multiple mutations for the same entity key.
for (CommitRequest req : requestCaptor.getAllValues()) {
long distinctKeys =
req.getMutationsList().stream().map(m -> m.getUpsert().getKey()).distinct().count();
assertEquals(
"Commit request must not contain duplicate entity keys",
req.getMutationsCount(),
distinctKeys);
}
}

/** Tests {@link DatastoreWriterFn} with a failed request which is retried. */
@Test
public void testDatastoreWriterFnRetriesErrors() throws Exception {
Expand Down
Loading