Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ed24d4f

Browse files
authoredJul 11, 2023
fix: fix batch mutation limit (#1808)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://togithub.com/googleapis/java-bigtable/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) Fixes #<issue_number_goes_here> ☕️ If you write sample code, please follow the [samples format]( https://togithub.com/GoogleCloudPlatform/java-docs-samples/blob/main/SAMPLE_FORMAT.md).
1 parent cb160af commit ed24d4f

File tree

8 files changed

+191
-1
lines changed

8 files changed

+191
-1
lines changed
 

‎google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/BulkMutation.java

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.google.cloud.bigtable.data.v2.models;
1717

18+
import static com.google.cloud.bigtable.data.v2.models.RowMutationEntry.MAX_MUTATION;
19+
1820
import com.google.api.core.BetaApi;
1921
import com.google.api.core.InternalApi;
2022
import com.google.bigtable.v2.MutateRowsRequest;
@@ -40,6 +42,8 @@ public final class BulkMutation implements Serializable, Cloneable {
4042
private final String tableId;
4143
private transient MutateRowsRequest.Builder builder;
4244

45+
private long mutationCountSum = 0;
46+
4347
public static BulkMutation create(String tableId) {
4448
return new BulkMutation(tableId);
4549
}
@@ -81,6 +85,14 @@ public BulkMutation add(@Nonnull ByteString rowKey, @Nonnull Mutation mutation)
8185
Preconditions.checkNotNull(rowKey);
8286
Preconditions.checkNotNull(mutation);
8387

88+
long mutationCount = mutation.getMutations().size();
89+
Preconditions.checkArgument(
90+
mutationCountSum + mutationCount <= MAX_MUTATION,
91+
String.format(
92+
"Too many mutations, got %s, limit is %s",
93+
mutationCountSum + mutationCount, MAX_MUTATION));
94+
this.mutationCountSum += mutationCount;
95+
8496
builder.addEntries(
8597
MutateRowsRequest.Entry.newBuilder()
8698
.setRowKey(rowKey)

‎google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/models/RowMutationEntry.java

+7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
public class RowMutationEntry implements MutationApi<RowMutationEntry>, Serializable {
3434
private static final long serialVersionUID = 1974738836742298192L;
3535

36+
static final int MAX_MUTATION = 100000;
37+
3638
private final ByteString key;
3739
private final Mutation mutation;
3840

@@ -180,6 +182,11 @@ public RowMutationEntry deleteRow() {
180182

181183
@InternalApi
182184
public MutateRowsRequest.Entry toProto() {
185+
Preconditions.checkArgument(
186+
mutation.getMutations().size() <= MAX_MUTATION,
187+
String.format(
188+
"Too many mutations, got %s, limit is %s",
189+
mutation.getMutations().size(), MAX_MUTATION));
183190
return MutateRowsRequest.Entry.newBuilder()
184191
.setRowKey(key)
185192
.addAllMutations(mutation.getMutations())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.bigtable.data.v2.stub.mutaterows;
17+
18+
import com.google.api.gax.batching.BatchResource;
19+
import com.google.auto.value.AutoValue;
20+
import com.google.common.base.Preconditions;
21+
22+
/**
23+
* A custom implementation of {@link BatchResource} because MutateRowsRequest has a limit on number
24+
* of mutations.
25+
*/
26+
@AutoValue
27+
abstract class MutateRowsBatchResource implements BatchResource {
28+
29+
static MutateRowsBatchResource create(long elementCount, long byteCount, long mutationCount) {
30+
return new AutoValue_MutateRowsBatchResource(elementCount, byteCount, mutationCount);
31+
}
32+
33+
@Override
34+
public BatchResource add(BatchResource batchResource) {
35+
Preconditions.checkArgument(
36+
batchResource instanceof MutateRowsBatchResource,
37+
"Expected MutateRowsBatchResource, got " + batchResource.getClass());
38+
MutateRowsBatchResource mutateRowsResource = (MutateRowsBatchResource) batchResource;
39+
40+
return new AutoValue_MutateRowsBatchResource(
41+
getElementCount() + mutateRowsResource.getElementCount(),
42+
getByteCount() + mutateRowsResource.getByteCount(),
43+
getMutationCount() + mutateRowsResource.getMutationCount());
44+
}
45+
46+
@Override
47+
public abstract long getElementCount();
48+
49+
@Override
50+
public abstract long getByteCount();
51+
52+
abstract long getMutationCount();
53+
54+
@Override
55+
public boolean shouldFlush(long maxElementThreshold, long maxBytesThreshold) {
56+
return getElementCount() > maxElementThreshold
57+
|| getByteCount() > maxBytesThreshold
58+
|| getMutationCount() > 100000;
59+
}
60+
}

‎google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/mutaterows/MutateRowsBatchingDescriptor.java

+12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import com.google.api.core.InternalApi;
1919
import com.google.api.gax.batching.BatchEntry;
20+
import com.google.api.gax.batching.BatchResource;
2021
import com.google.api.gax.batching.BatchingDescriptor;
2122
import com.google.api.gax.batching.BatchingRequestBuilder;
2223
import com.google.cloud.bigtable.data.v2.models.BulkMutation;
@@ -90,6 +91,17 @@ public long countBytes(RowMutationEntry entry) {
9091
return entry.toProto().getSerializedSize();
9192
}
9293

94+
@Override
95+
public BatchResource createResource(RowMutationEntry element) {
96+
long byteCount = countBytes(element);
97+
return MutateRowsBatchResource.create(1, byteCount, element.toProto().getMutationsCount());
98+
}
99+
100+
@Override
101+
public BatchResource createEmptyResource() {
102+
return MutateRowsBatchResource.create(0, 0, 0);
103+
}
104+
93105
/**
94106
* A {@link BatchingRequestBuilder} that will spool mutations and send them out as a {@link
95107
* BulkMutation}.

‎google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/it/BulkMutateIT.java

+52
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@
1616
package com.google.cloud.bigtable.data.v2.it;
1717

1818
import static com.google.common.truth.Truth.assertThat;
19+
import static com.google.common.truth.TruthJUnit.assume;
1920

2021
import com.google.api.gax.batching.BatcherImpl;
22+
import com.google.api.gax.batching.BatchingSettings;
2123
import com.google.api.gax.batching.FlowControlEventStats;
2224
import com.google.cloud.bigtable.data.v2.BigtableDataClient;
2325
import com.google.cloud.bigtable.data.v2.BigtableDataSettings;
2426
import com.google.cloud.bigtable.data.v2.models.BulkMutation;
2527
import com.google.cloud.bigtable.data.v2.models.Query;
2628
import com.google.cloud.bigtable.data.v2.models.Row;
2729
import com.google.cloud.bigtable.data.v2.models.RowMutationEntry;
30+
import com.google.cloud.bigtable.test_helpers.env.EmulatorEnv;
2831
import com.google.cloud.bigtable.test_helpers.env.TestEnvRule;
2932
import java.io.IOException;
3033
import java.util.Objects;
@@ -33,6 +36,7 @@
3336
import org.junit.Test;
3437
import org.junit.runner.RunWith;
3538
import org.junit.runners.JUnit4;
39+
import org.threeten.bp.Duration;
3640

3741
@RunWith(JUnit4.class)
3842
public class BulkMutateIT {
@@ -83,4 +87,52 @@ public void test() throws IOException, InterruptedException {
8387
assertThat(row.getCells()).hasSize(1);
8488
}
8589
}
90+
91+
@Test
92+
public void testManyMutations() throws IOException, InterruptedException {
93+
// Emulator is very slow and will take a long time for the test to run
94+
assume()
95+
.withMessage("testManyMutations is not supported on Emulator")
96+
.that(testEnvRule.env())
97+
.isNotInstanceOf(EmulatorEnv.class);
98+
99+
BigtableDataSettings settings = testEnvRule.env().getDataClientSettings();
100+
String rowPrefix = UUID.randomUUID().toString();
101+
102+
BatchingSettings batchingSettings =
103+
settings.getStubSettings().bulkMutateRowsSettings().getBatchingSettings();
104+
105+
settings
106+
.toBuilder()
107+
.stubSettings()
108+
.bulkMutateRowsSettings()
109+
.setBatchingSettings(
110+
batchingSettings.toBuilder().setDelayThreshold(Duration.ofHours(1)).build());
111+
try (BigtableDataClient client = BigtableDataClient.create(settings);
112+
BatcherImpl<RowMutationEntry, Void, BulkMutation, Void> batcher =
113+
(BatcherImpl<RowMutationEntry, Void, BulkMutation, Void>)
114+
client.newBulkMutationBatcher(testEnvRule.env().getTableId())) {
115+
116+
String familyId = testEnvRule.env().getFamilyId();
117+
for (int i = 0; i < 2; i++) {
118+
String key = rowPrefix + "test-key";
119+
RowMutationEntry rowMutationEntry = RowMutationEntry.create(key);
120+
// Create mutation entries with many columns. The batcher should flush every time.
121+
for (long j = 0; j < 50001; j++) {
122+
rowMutationEntry.setCell(familyId, "q" + j + i, j);
123+
}
124+
batcher.add(rowMutationEntry);
125+
}
126+
batcher.flush();
127+
// Query a key to make sure the write succeeded
128+
Row row =
129+
testEnvRule
130+
.env()
131+
.getDataClient()
132+
.readRowsCallable()
133+
.first()
134+
.call(Query.create(testEnvRule.env().getTableId()).rowKey(rowPrefix + "test-key"));
135+
assertThat(row.getCells()).hasSize(100002);
136+
}
137+
}
86138
}

‎google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/models/BulkMutationTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.io.IOException;
2929
import java.io.ObjectInputStream;
3030
import java.io.ObjectOutputStream;
31+
import org.junit.Assert;
3132
import org.junit.Test;
3233
import org.junit.runner.RunWith;
3334
import org.junit.runners.JUnit4;
@@ -172,4 +173,31 @@ public void fromProtoTest() {
172173
.matches(NameUtil.formatTableName(projectId, instanceId, TABLE_ID));
173174
assertThat(overriddenRequest.getAppProfileId()).matches(appProfile);
174175
}
176+
177+
@Test
178+
public void testManyMutations() {
179+
BulkMutation bulkMutation = BulkMutation.create(TABLE_ID);
180+
181+
try {
182+
for (int i = 0; i < 3; i++) {
183+
String key = "key" + i;
184+
Mutation mutation = Mutation.create();
185+
for (int j = 0; j < 50000; j++) {
186+
mutation.setCell("f", "q" + j, "value");
187+
}
188+
bulkMutation.add(key, mutation);
189+
}
190+
Assert.fail("Test should fail with IllegalArgumentException");
191+
} catch (IllegalArgumentException e) {
192+
assertThat(e.getMessage()).contains("Too many mutations");
193+
}
194+
195+
// we should be able to add 10000 mutations
196+
bulkMutation = BulkMutation.create(TABLE_ID);
197+
Mutation mutation = Mutation.create();
198+
for (int i = 0; i < 100000; i++) {
199+
mutation.setCell("f", "q" + i, "value");
200+
}
201+
bulkMutation.add("key", mutation);
202+
}
175203
}

‎google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/metrics/MetricsTracerTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,6 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
439439
.reserve(any(Long.class), any(Long.class));
440440
when(flowController.getMaxElementCountLimit()).thenReturn(null);
441441
when(flowController.getMaxRequestBytesLimit()).thenReturn(null);
442-
when(batchingDescriptor.countBytes(any())).thenReturn(1l);
443442
when(batchingDescriptor.newRequestBuilder(any())).thenCallRealMethod();
444443

445444
doAnswer(

‎google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/mutaterows/MutateRowsBatchingDescriptorTest.java

+20
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import com.google.api.core.SettableApiFuture;
2121
import com.google.api.gax.batching.BatchEntry;
22+
import com.google.api.gax.batching.BatchResource;
2223
import com.google.api.gax.batching.BatchingRequestBuilder;
2324
import com.google.api.gax.grpc.GrpcStatusCode;
2425
import com.google.api.gax.rpc.DeadlineExceededException;
@@ -180,4 +181,23 @@ public void splitExceptionWithFailedMutationsTest() {
180181
.hasCauseThat()
181182
.isEqualTo(serverError.getFailedMutations().get(1).getError());
182183
}
184+
185+
@Test
186+
public void shouldFlushTest() {
187+
MutateRowsBatchingDescriptor underTest = new MutateRowsBatchingDescriptor();
188+
RowMutationEntry entryWithManyMutations = RowMutationEntry.create("key1");
189+
for (int i = 0; i < 100000; i++) {
190+
entryWithManyMutations.setCell("f", "q", "v" + i);
191+
}
192+
RowMutationEntry entryWithSingleEntry = RowMutationEntry.create("key1").setCell("f", "q", "v");
193+
BatchResource resourceWithManyMutations = underTest.createResource(entryWithManyMutations);
194+
BatchResource resourceWithSingleMutation = underTest.createResource(entryWithSingleEntry);
195+
196+
assertThat(resourceWithManyMutations.shouldFlush(1, 20 * 1000 * 1000)).isFalse();
197+
assertThat(
198+
resourceWithManyMutations
199+
.add(resourceWithSingleMutation)
200+
.shouldFlush(3, 20 * 1000 * 1000))
201+
.isTrue();
202+
}
183203
}

0 commit comments

Comments
 (0)
Failed to load comments.