Skip to content

Commit 78f8f28

Browse files
committed
Limit batch deletes to 100, issue serveral batch if limit's exceeded
1 parent bbf3cb6 commit 78f8f28

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

gcloud-java-storage/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
3636
import com.google.api.client.googleapis.json.GoogleJsonError;
3737
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
38-
import com.google.api.client.googleapis.media.MediaHttpDownloader;
3938
import com.google.api.client.http.ByteArrayContent;
4039
import com.google.api.client.http.GenericUrl;
4140
import com.google.api.client.http.HttpHeaders;
@@ -59,16 +58,18 @@
5958
import com.google.api.services.storage.model.Objects;
6059
import com.google.api.services.storage.model.StorageObject;
6160
import com.google.common.base.MoreObjects;
61+
import com.google.common.collect.ImmutableList;
6262
import com.google.common.collect.ImmutableSet;
63+
import com.google.common.collect.Lists;
6364
import com.google.common.collect.Maps;
64-
import com.google.common.primitives.Ints;
6565
import com.google.gcloud.storage.StorageException;
6666
import com.google.gcloud.storage.StorageOptions;
6767

6868
import java.io.ByteArrayOutputStream;
6969
import java.io.IOException;
7070
import java.io.InputStream;
7171
import java.util.ArrayList;
72+
import java.util.Iterator;
7273
import java.util.List;
7374
import java.util.Map;
7475
import java.util.Set;
@@ -82,6 +83,7 @@ public class DefaultStorageRpc implements StorageRpc {
8283
// see: https://cloud.google.com/storage/docs/concepts-techniques#practices
8384
private static final Set<Integer> RETRYABLE_CODES = ImmutableSet.of(504, 503, 502, 500, 429, 408);
8485
private static final long MEGABYTE = 1024L * 1024L;
86+
private static final int MAX_BATCH_DELETES = 100;
8587

8688
public DefaultStorageRpc(StorageOptions options) {
8789
HttpTransport transport = options.httpTransportFactory().create();
@@ -361,6 +363,24 @@ public byte[] load(StorageObject from, Map<Option, ?> options)
361363

362364
@Override
363365
public BatchResponse batch(BatchRequest request) throws StorageException {
366+
List<List<Tuple<StorageObject, Map<Option, ?>>>> partitionedToDelete =
367+
Lists.partition(request.toDelete, MAX_BATCH_DELETES);
368+
Iterator<List<Tuple<StorageObject, Map<Option, ?>>>> iterator = partitionedToDelete.iterator();
369+
BatchRequest chunkRequest = new BatchRequest(iterator.hasNext() ? iterator.next() :
370+
ImmutableList.<Tuple<StorageObject, Map<Option, ?>>>of(), request.toUpdate, request.toGet);
371+
BatchResponse response = batchChunk(chunkRequest);
372+
Map<StorageObject, Tuple<Boolean, StorageException>> deletes =
373+
Maps.newHashMapWithExpectedSize(request.toDelete.size());
374+
deletes.putAll(response.deletes);
375+
while (iterator.hasNext()) {
376+
chunkRequest = new BatchRequest(iterator.next(), null, null);
377+
BatchResponse deleteBatchResponse = batchChunk(chunkRequest);
378+
deletes.putAll(deleteBatchResponse.deletes);
379+
}
380+
return new BatchResponse(deletes, response.updates, response.gets);
381+
}
382+
383+
private BatchResponse batchChunk(BatchRequest request) {
364384
com.google.api.client.googleapis.batch.BatchRequest batch = storage.batch();
365385
final Map<StorageObject, Tuple<Boolean, StorageException>> deletes =
366386
Maps.newConcurrentMap();

gcloud-java-storage/src/test/java/com/google/gcloud/storage/ITStorageTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import static org.junit.Assert.assertTrue;
2626
import static org.junit.Assert.fail;
2727

28+
import com.google.api.client.util.Lists;
2829
import com.google.common.collect.ImmutableList;
2930
import com.google.common.collect.ImmutableMap;
3031
import com.google.gcloud.Page;
@@ -65,6 +66,7 @@ public class ITStorageTest {
6566
private static final String CONTENT_TYPE = "text/plain";
6667
private static final byte[] BLOB_BYTE_CONTENT = {0xD, 0xE, 0xA, 0xD};
6768
private static final String BLOB_STRING_CONTENT = "Hello Google Cloud Storage!";
69+
private static final int MAX_BATCH_DELETES = 100;
6870

6971
@BeforeClass
7072
public static void beforeClass() {
@@ -623,6 +625,54 @@ public void testBatchRequest() {
623625
assertTrue(deleteResponse.deletes().get(1).get());
624626
}
625627

628+
@Test
629+
public void testBatchRequestManyDeletes() {
630+
List<BlobId> blobsToDelete = Lists.newArrayListWithCapacity(2 * MAX_BATCH_DELETES);
631+
for (int i = 0; i < 2 * MAX_BATCH_DELETES; i++) {
632+
blobsToDelete.add(BlobId.of(BUCKET, "test-batch-request-many-deletes-blob-" + i));
633+
}
634+
BatchRequest.Builder builder = BatchRequest.builder();
635+
for (BlobId blob : blobsToDelete) {
636+
builder.delete(blob);
637+
}
638+
String sourceBlobName1 = "test-batch-request-many-deletes-source-blob-1";
639+
String sourceBlobName2 = "test-batch-request-many-deletes-source-blob-2";
640+
BlobInfo sourceBlob1 = BlobInfo.builder(BUCKET, sourceBlobName1).build();
641+
BlobInfo sourceBlob2 = BlobInfo.builder(BUCKET, sourceBlobName2).build();
642+
assertNotNull(storage.create(sourceBlob1));
643+
assertNotNull(storage.create(sourceBlob2));
644+
BlobInfo updatedBlob2 = sourceBlob2.toBuilder().contentType(CONTENT_TYPE).build();
645+
646+
BatchRequest updateRequest = builder
647+
.get(BUCKET, sourceBlobName1)
648+
.update(updatedBlob2)
649+
.build();
650+
BatchResponse response = storage.apply(updateRequest);
651+
assertEquals(2 * MAX_BATCH_DELETES, response.deletes().size());
652+
assertEquals(1, response.updates().size());
653+
assertEquals(1, response.gets().size());
654+
655+
// Check deletes
656+
for (BatchResponse.Result<Boolean> deleteResult : response.deletes()) {
657+
assertFalse(deleteResult.failed());
658+
assertFalse(deleteResult.get());
659+
}
660+
661+
// Check updates
662+
BlobInfo remoteUpdatedBlob2 = response.updates().get(0).get();
663+
assertEquals(sourceBlob2.bucket(), remoteUpdatedBlob2.bucket());
664+
assertEquals(sourceBlob2.name(), remoteUpdatedBlob2.name());
665+
assertEquals(updatedBlob2.contentType(), remoteUpdatedBlob2.contentType());
666+
667+
// Check gets
668+
BlobInfo remoteBlob1 = response.gets().get(0).get();
669+
assertEquals(sourceBlob1.bucket(), remoteBlob1.bucket());
670+
assertEquals(sourceBlob1.name(), remoteBlob1.name());
671+
672+
assertTrue(storage.delete(BUCKET, sourceBlobName1));
673+
assertTrue(storage.delete(BUCKET, sourceBlobName2));
674+
}
675+
626676
@Test
627677
public void testBatchRequestFail() {
628678
String blobName = "test-batch-request-blob-fail";

0 commit comments

Comments
 (0)