Skip to content

Commit 0dff911

Browse files
fix(bigtable): count mutations wrapped from protos towards the row limits
Mutation.MAX_MUTATIONS and MAX_BYTE_SIZE are enforced in addMutation, against counters only addMutation maintains. fromProtoUnsafe(List), fromProtoUnsafe(Iterable) and fromProto(List) add to the mutation list directly and leave both counters at zero, so mutations wrapped from existing protos count towards neither limit. The mutation count is backstopped by RowMutationEntry.toProto() and BulkMutation.add, which re-check the real list size, so it surfaces late and as a different exception type. The byte size is not backstopped anywhere, so a Mutation seeded from protos can exceed 200 MB with nothing client-side objecting. Count wrapped protos in the three factories, so the counters describe the whole row. Deliberately not a checkState in the factories themselves: that would make wrapping an already-over-limit proto throw where it currently does not.
1 parent 7109ecd commit 0dff911

2 files changed

Lines changed: 117 additions & 4 deletions

File tree

  • java-bigtable/google-cloud-bigtable/src

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static Mutation createUnsafe() {
8787
@BetaApi
8888
public static Mutation fromProtoUnsafe(List<com.google.bigtable.v2.Mutation> protos) {
8989
Mutation mutation = new Mutation(true);
90-
mutation.mutations.addAll(protos);
90+
mutation.addAllFromProto(protos);
9191
return mutation;
9292
}
9393

@@ -100,7 +100,7 @@ public static Mutation fromProtoUnsafe(List<com.google.bigtable.v2.Mutation> pro
100100
@BetaApi
101101
public static Mutation fromProtoUnsafe(Iterable<com.google.bigtable.v2.Mutation> protos) {
102102
Mutation mutation = new Mutation(true);
103-
mutation.mutations.addAll(protos);
103+
mutation.addAllFromProto(protos);
104104
return mutation;
105105
}
106106

@@ -116,7 +116,7 @@ public static Mutation fromProtoUnsafe(Iterable<com.google.bigtable.v2.Mutation>
116116
*/
117117
static Mutation fromProto(List<com.google.bigtable.v2.Mutation> protos) {
118118
Mutation mutation = new Mutation(false);
119-
mutation.mutations.addAll(protos);
119+
mutation.addAllFromProto(protos);
120120
return mutation;
121121
}
122122

@@ -346,10 +346,22 @@ private void addMutation(com.google.bigtable.v2.Mutation mutation) {
346346
byteSize + mutation.getSerializedSize() <= MAX_BYTE_SIZE,
347347
"Byte size of mutations is too large");
348348

349+
countTowardsLimits(mutation);
350+
351+
mutations.add(mutation);
352+
}
353+
354+
private void countTowardsLimits(com.google.bigtable.v2.Mutation mutation) {
349355
numMutations++;
350356
byteSize += mutation.getSerializedSize();
357+
}
351358

352-
mutations.add(mutation);
359+
private void addAllFromProto(Iterable<com.google.bigtable.v2.Mutation> protos) {
360+
// One traversal: protos may be a non-repeatable Iterable.
361+
for (com.google.bigtable.v2.Mutation proto : protos) {
362+
mutations.add(proto);
363+
countTowardsLimits(proto);
364+
}
353365
}
354366

355367
private static ByteString wrapByteString(String str) {

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

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.bigtable.v2.Mutation.MergeToCell;
2525
import com.google.bigtable.v2.Mutation.TimestampOrigin;
2626
import com.google.cloud.bigtable.data.v2.models.Range.TimestampRange;
27+
import com.google.common.collect.ImmutableList;
2728
import com.google.common.primitives.Longs;
2829
import com.google.protobuf.ByteString;
2930
import java.io.ByteArrayInputStream;
@@ -32,6 +33,7 @@
3233
import java.io.ObjectInputStream;
3334
import java.io.ObjectOutputStream;
3435
import java.time.Instant;
36+
import java.util.Iterator;
3537
import java.util.List;
3638
import org.junit.Before;
3739
import org.junit.Test;
@@ -270,6 +272,105 @@ public void tooLargeRequest() {
270272
assertThat(actualError).isInstanceOf(IllegalStateException.class);
271273
}
272274

275+
@Test
276+
public void tooManyMutationsCountsWrappedProtosTest() {
277+
Mutation mutation =
278+
Mutation.fromProtoUnsafe(
279+
ImmutableList.of(
280+
com.google.bigtable.v2.Mutation.newBuilder()
281+
.setDeleteFromRow(com.google.bigtable.v2.Mutation.DeleteFromRow.newBuilder())
282+
.build()));
283+
284+
for (int i = 0; i < Mutation.MAX_MUTATIONS - 1; i++) {
285+
mutation.setCell("f", "", "");
286+
}
287+
288+
Exception actualError = null;
289+
try {
290+
mutation.setCell("f", "", "");
291+
} catch (Exception e) {
292+
actualError = e;
293+
}
294+
295+
assertThat(actualError).isInstanceOf(IllegalStateException.class);
296+
assertThat(mutation.getMutations()).hasSize(Mutation.MAX_MUTATIONS);
297+
}
298+
299+
@Test
300+
public void tooLargeRequestCountsWrappedProtosTest() {
301+
Mutation mutation =
302+
Mutation.fromProtoUnsafe(
303+
ImmutableList.of(
304+
com.google.bigtable.v2.Mutation.newBuilder()
305+
.setSetCell(
306+
com.google.bigtable.v2.Mutation.SetCell.newBuilder()
307+
.setFamilyName("f")
308+
.setValue(ByteString.copyFrom(new byte[Mutation.MAX_BYTE_SIZE / 2])))
309+
.build(),
310+
com.google.bigtable.v2.Mutation.newBuilder()
311+
.setSetCell(
312+
com.google.bigtable.v2.Mutation.SetCell.newBuilder()
313+
.setFamilyName("f")
314+
.setValue(ByteString.copyFrom(new byte[Mutation.MAX_BYTE_SIZE / 2])))
315+
.build()));
316+
317+
Exception actualError = null;
318+
try {
319+
mutation.setCell("f", "", "");
320+
} catch (Exception e) {
321+
actualError = e;
322+
}
323+
324+
assertThat(actualError).isInstanceOf(IllegalStateException.class);
325+
}
326+
327+
@Test
328+
public void fromProtoUnsafeTraversesTheIterableOnceTest() {
329+
Iterable<com.google.bigtable.v2.Mutation> oneShot =
330+
oneShot(
331+
com.google.bigtable.v2.Mutation.newBuilder()
332+
.setDeleteFromRow(com.google.bigtable.v2.Mutation.DeleteFromRow.newBuilder())
333+
.build(),
334+
com.google.bigtable.v2.Mutation.newBuilder()
335+
.setDeleteFromRow(com.google.bigtable.v2.Mutation.DeleteFromRow.newBuilder())
336+
.build());
337+
338+
Mutation mutation = Mutation.fromProtoUnsafe(oneShot);
339+
340+
assertThat(mutation.getMutations()).hasSize(2);
341+
342+
for (int i = 0; i < Mutation.MAX_MUTATIONS - 2; i++) {
343+
mutation.setCell("f", "", "");
344+
}
345+
346+
Exception actualError = null;
347+
try {
348+
mutation.setCell("f", "", "");
349+
} catch (Exception e) {
350+
actualError = e;
351+
}
352+
353+
assertThat(actualError).isInstanceOf(IllegalStateException.class);
354+
}
355+
356+
/** An Iterable that refuses a second traversal. */
357+
private static Iterable<com.google.bigtable.v2.Mutation> oneShot(
358+
com.google.bigtable.v2.Mutation... protos) {
359+
List<com.google.bigtable.v2.Mutation> source = ImmutableList.copyOf(protos);
360+
return new Iterable<com.google.bigtable.v2.Mutation>() {
361+
private boolean consumed;
362+
363+
@Override
364+
public Iterator<com.google.bigtable.v2.Mutation> iterator() {
365+
if (consumed) {
366+
throw new IllegalStateException("Iterable traversed more than once");
367+
}
368+
consumed = true;
369+
return source.iterator();
370+
}
371+
};
372+
}
373+
273374
@Test
274375
public void testWithLongValue() {
275376
Mutation mutation =

0 commit comments

Comments
 (0)