Skip to content

Commit e024b00

Browse files
geoserver-botaaime
authored andcommitted
Revised AzureBlobStore deletions.
1 parent e4ae778 commit e024b00

File tree

4 files changed

+173
-117
lines changed

4 files changed

+173
-117
lines changed

geowebcache/azureblob/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
<artifactId>azure-storage-blob</artifactId>
4444
<version>${azure.version}</version>
4545
</dependency>
46+
<dependency>
47+
<groupId>com.azure</groupId>
48+
<artifactId>azure-storage-blob-batch</artifactId>
49+
<version>${azure.version}</version>
50+
</dependency>
4651

4752
<dependency>
4853
<groupId>org.apache.logging.log4j</groupId>

geowebcache/azureblob/src/main/java/org/geowebcache/azure/AzureBlobStore.java

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,30 @@
1515

1616
import static com.google.common.base.Preconditions.checkNotNull;
1717
import static java.util.Objects.isNull;
18+
import static org.geowebcache.azure.DeleteManager.PAGE_SIZE;
1819

1920
import com.azure.core.util.BinaryData;
2021
import com.azure.storage.blob.models.BlobDownloadContentResponse;
2122
import com.azure.storage.blob.models.BlobItem;
2223
import com.azure.storage.blob.models.BlobProperties;
2324
import com.azure.storage.blob.models.BlobStorageException;
2425
import com.azure.storage.blob.specialized.BlockBlobClient;
25-
import com.google.common.collect.AbstractIterator;
26-
import com.google.common.collect.Iterators;
2726
import java.io.IOException;
2827
import java.io.InputStream;
2928
import java.io.UncheckedIOException;
3029
import java.time.OffsetDateTime;
30+
import java.util.ArrayList;
3131
import java.util.Iterator;
3232
import java.util.List;
3333
import java.util.Map;
3434
import java.util.Optional;
3535
import java.util.Properties;
3636
import java.util.concurrent.Callable;
3737
import java.util.logging.Logger;
38+
import java.util.regex.Matcher;
39+
import java.util.regex.Pattern;
3840
import java.util.stream.Collectors;
41+
import java.util.stream.IntStream;
3942
import java.util.stream.Stream;
4043
import javax.annotation.Nullable;
4144
import org.geotools.util.logging.Logging;
@@ -53,13 +56,14 @@
5356
import org.geowebcache.storage.StorageException;
5457
import org.geowebcache.storage.TileObject;
5558
import org.geowebcache.storage.TileRange;
56-
import org.geowebcache.storage.TileRangeIterator;
5759
import org.geowebcache.util.TMSKeyBuilder;
5860
import org.springframework.http.HttpStatus;
5961

6062
public class AzureBlobStore implements BlobStore {
6163

62-
static Logger log = Logging.getLogger(AzureBlobStore.class.getName());
64+
private static final Logger LOG = Logging.getLogger(AzureBlobStore.class.getName());
65+
66+
private static final Pattern TILE_BLOB_NAME_REGEXP = Pattern.compile("(?<z>\\d+)/(?<x>\\d+)/(?<y>\\d+)\\.\\w+$");
6367

6468
private final TMSKeyBuilder keyBuilder;
6569
private final BlobStoreListenerList listeners = new BlobStoreListenerList();
@@ -190,58 +194,98 @@ public boolean delete(TileRange tileRange) throws StorageException {
190194
return false;
191195
}
192196

193-
// open an iterator oer tile locations, to avoid memory accumulation
194-
final Iterator<long[]> tileLocations = new AbstractIterator<>() {
195-
196-
// TileRange iterator with 1x1 meta tiling factor
197-
private TileRangeIterator trIter = new TileRangeIterator(tileRange, new int[] {1, 1});
198-
199-
@Override
200-
protected long[] computeNext() {
201-
long[] gridLoc = trIter.nextMetaGridLocation(new long[3]);
202-
return gridLoc == null ? endOfData() : gridLoc;
203-
}
204-
};
197+
Stream<BlobItem> blobsToDelete = findTileBlobsToDelete(tileRange, coordsPrefix);
205198

206-
// if no listeners, we don't need to gather extra tile info, use a dedicated fast path
207199
if (listeners.isEmpty()) {
208200
// if there are no listeners, don't bother requesting every tile
209201
// metadata to notify the listeners
210-
Iterator<String> keysIterator = Iterators.transform(
211-
tileLocations, tl -> keyBuilder.forLocation(coordsPrefix, tl, tileRange.getMimeType()));
212-
// split the iteration in parts to avoid memory accumulation
213-
Iterator<List<String>> partition = Iterators.partition(keysIterator, DeleteManager.PAGE_SIZE);
214-
215-
while (partition.hasNext() && !shutDown) {
216-
List<String> locations = partition.next();
217-
deleteManager.deleteParallel(locations);
202+
if (!shutDown) {
203+
deleteManager.deleteStreamed(blobsToDelete);
218204
}
219-
220205
} else {
221206
// if we need to gather info, we'll end up just calling "delete" on each tile
222207
// this is run here instead of inside the delete manager as we need high level info
223208
// about tiles, e.g., TileObject, to inform the listeners
224-
String layerName = tileRange.getLayerName();
225-
String gridSetId = tileRange.getGridSetId();
226-
String format = tileRange.getMimeType().getFormat();
227-
Map<String, String> parameters = tileRange.getParameters();
228-
229-
Iterator<Callable<?>> tilesIterator = Iterators.transform(tileLocations, xyz -> {
230-
TileObject tile = TileObject.createQueryTileObject(layerName, xyz, gridSetId, format, parameters);
209+
Stream<Callable<?>> tilesDeletions = blobsToDelete.map(blobItem -> {
210+
TileObject tile = createTileObject(blobItem, tileRange);
231211
tile.setParametersId(tileRange.getParametersId());
232-
return (Callable<Object>) () -> delete(tile);
212+
return () -> delete(tile);
233213
});
234-
Iterator<List<Callable<?>>> partition = Iterators.partition(tilesIterator, DeleteManager.PAGE_SIZE);
235214

236-
// once a page of callables is ready, run them in parallel on the delete manager
237-
while (partition.hasNext() && !shutDown) {
238-
deleteManager.executeParallel(partition.next());
239-
}
215+
executeParallelDeletions(tilesDeletions);
240216
}
241217

242218
return true;
243219
}
244220

221+
private Stream<BlobItem> findTileBlobsToDelete(TileRange tileRange, String coordsPrefix) {
222+
return IntStream.rangeClosed(tileRange.getZoomStart(), tileRange.getZoomStop())
223+
.boxed()
224+
.flatMap(zoom -> {
225+
String zoomPrefix = coordsPrefix + "/" + zoom;
226+
227+
if (!client.prefixExists(zoomPrefix)) {
228+
// empty level, skipping
229+
return Stream.empty();
230+
}
231+
232+
long[] rangeBoundsAtZoom = tileRange.rangeBounds(zoom);
233+
234+
return client.listBlobs(zoomPrefix)
235+
.filter(tb ->
236+
TILE_BLOB_NAME_REGEXP.matcher(tb.getName()).find())
237+
.filter(tb -> isTileBlobInBounds(tb, rangeBoundsAtZoom));
238+
});
239+
}
240+
241+
private boolean isTileBlobInBounds(BlobItem tileBlob, long[] bounds) {
242+
long minX = bounds[0];
243+
long minY = bounds[1];
244+
long maxX = bounds[2];
245+
long maxY = bounds[3];
246+
247+
long[] index = extractTileIndex(tileBlob);
248+
long tileX = index[0];
249+
long tileY = index[1];
250+
251+
return tileX >= minX && tileX <= maxX && tileY >= minY && tileY <= maxY;
252+
}
253+
254+
private TileObject createTileObject(BlobItem blobItem, TileRange tileRange) {
255+
String layerName = tileRange.getLayerName();
256+
String gridSetId = tileRange.getGridSetId();
257+
String format = tileRange.getMimeType().getFormat();
258+
Map<String, String> parameters = tileRange.getParameters();
259+
return TileObject.createQueryTileObject(layerName, extractTileIndex(blobItem), gridSetId, format, parameters);
260+
}
261+
262+
private long[] extractTileIndex(BlobItem blobItem) {
263+
Matcher matcher = TILE_BLOB_NAME_REGEXP.matcher(blobItem.getName());
264+
265+
if (!matcher.find()) {
266+
throw new IllegalArgumentException("Invalid tile blob name");
267+
}
268+
269+
return new long[] {
270+
Long.parseLong(matcher.group("x")), Long.parseLong(matcher.group("y")), Long.parseLong(matcher.group("z"))
271+
};
272+
}
273+
274+
private void executeParallelDeletions(Stream<Callable<?>> tilesDeletions) throws StorageException {
275+
Iterator<Callable<?>> tilesDeletionsIterator = tilesDeletions.iterator();
276+
277+
while (tilesDeletionsIterator.hasNext() && !shutDown) {
278+
279+
// once a page of callables is ready, run them in parallel on the delete manager
280+
List<Callable<?>> callables = new ArrayList<>(PAGE_SIZE);
281+
for (int i = 0; i < PAGE_SIZE && tilesDeletionsIterator.hasNext(); i++) {
282+
callables.add(tilesDeletionsIterator.next());
283+
}
284+
285+
deleteManager.executeParallel(callables);
286+
}
287+
}
288+
245289
@Override
246290
public boolean get(TileObject obj) throws StorageException {
247291
final String key = keyBuilder.forTile(obj);
@@ -373,7 +417,7 @@ public boolean rename(String oldLayerName, String newLayerName) throws StorageEx
373417
// revisit: this seems to hold true only for GeoServerTileLayer, "standalone" TileLayers
374418
// return getName() from getId(), as in AbstractTileLayer. Unfortunately the only option
375419
// for non-GeoServerTileLayers would be copy and delete. Expensive.
376-
log.fine("No need to rename layers, AzureBlobStore uses layer id as key root");
420+
LOG.fine("No need to rename layers, AzureBlobStore uses layer id as key root");
377421
if (client.prefixExists(oldLayerName)) {
378422
listeners.sendLayerRenamed(oldLayerName, newLayerName);
379423
}

geowebcache/azureblob/src/main/java/org/geowebcache/azure/AzureClient.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import com.azure.storage.blob.BlobContainerClient;
2828
import com.azure.storage.blob.BlobServiceClient;
2929
import com.azure.storage.blob.BlobServiceClientBuilder;
30+
import com.azure.storage.blob.batch.BlobBatchClient;
31+
import com.azure.storage.blob.batch.BlobBatchClientBuilder;
3032
import com.azure.storage.blob.models.BlobDownloadContentResponse;
3133
import com.azure.storage.blob.models.BlobHttpHeaders;
3234
import com.azure.storage.blob.models.BlobItem;
@@ -56,6 +58,7 @@ public class AzureClient {
5658

5759
private AzureBlobStoreData configuration;
5860
private final BlobContainerClient container;
61+
private final BlobBatchClient batch;
5962

6063
public AzureClient(AzureBlobStoreData configuration) throws StorageException {
6164
this.configuration = configuration;
@@ -65,6 +68,7 @@ public AzureClient(AzureBlobStoreData configuration) throws StorageException {
6568

6669
String containerName = configuration.getContainer();
6770
this.container = getOrCreateContainer(serviceClient, containerName);
71+
this.batch = new BlobBatchClientBuilder(serviceClient).buildClient();
6872
} catch (StorageException e) {
6973
throw e;
7074
} catch (RuntimeException e) {
@@ -285,6 +289,10 @@ public BlobContainerClient getContainer() {
285289
return container;
286290
}
287291

292+
public BlobBatchClient getBatch() {
293+
return batch;
294+
}
295+
288296
public boolean deleteBlob(String key) {
289297
BlockBlobClient metadata = getBlockBlobClient(key);
290298
return metadata.deleteIfExists();

0 commit comments

Comments
 (0)