Skip to content

Commit 328177b

Browse files
committed
Revised AzureBlobStore deletions.
1 parent e2eb047 commit 328177b

File tree

4 files changed

+156
-104
lines changed

4 files changed

+156
-104
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: 88 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,21 @@
2222
import com.azure.storage.blob.models.BlobProperties;
2323
import com.azure.storage.blob.models.BlobStorageException;
2424
import com.azure.storage.blob.specialized.BlockBlobClient;
25-
import com.google.common.collect.AbstractIterator;
2625
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;
3941
import java.util.stream.Stream;
4042
import javax.annotation.Nullable;
@@ -53,13 +55,14 @@
5355
import org.geowebcache.storage.StorageException;
5456
import org.geowebcache.storage.TileObject;
5557
import org.geowebcache.storage.TileRange;
56-
import org.geowebcache.storage.TileRangeIterator;
5758
import org.geowebcache.util.TMSKeyBuilder;
5859
import org.springframework.http.HttpStatus;
5960

6061
public class AzureBlobStore implements BlobStore {
6162

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

6467
private final TMSKeyBuilder keyBuilder;
6568
private final BlobStoreListenerList listeners = new BlobStoreListenerList();
@@ -190,58 +193,105 @@ public boolean delete(TileRange tileRange) throws StorageException {
190193
return false;
191194
}
192195

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-
};
196+
List<BlobItem> blobsToDelete = findTileBlobsToDelete(tileRange, coordsPrefix);
205197

206-
// if no listeners, we don't need to gather extra tile info, use a dedicated fast path
207198
if (listeners.isEmpty()) {
208199
// if there are no listeners, don't bother requesting every tile
209200
// metadata to notify the listeners
210-
Iterator<String> keysIterator = Iterators.transform(
211-
tileLocations, tl -> keyBuilder.forLocation(coordsPrefix, tl, tileRange.getMimeType()));
212201
// split the iteration in parts to avoid memory accumulation
213-
Iterator<List<String>> partition = Iterators.partition(keysIterator, DeleteManager.PAGE_SIZE);
202+
List<String> keysToDelete =
203+
blobsToDelete.stream().map(BlobItem::getName).collect(Collectors.toList());
204+
205+
Iterator<List<String>> partition = Iterators.partition(keysToDelete.iterator(), DeleteManager.PAGE_SIZE);
214206

215207
while (partition.hasNext() && !shutDown) {
216-
List<String> locations = partition.next();
217-
deleteManager.deleteParallel(locations);
208+
deleteManager.deleteParallel(partition.next());
218209
}
219210

220211
} else {
221212
// if we need to gather info, we'll end up just calling "delete" on each tile
222213
// this is run here instead of inside the delete manager as we need high level info
223214
// 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);
231-
tile.setParametersId(tileRange.getParametersId());
232-
return (Callable<Object>) () -> delete(tile);
233-
});
234-
Iterator<List<Callable<?>>> partition = Iterators.partition(tilesIterator, DeleteManager.PAGE_SIZE);
235-
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+
List<Callable<?>> tilesDeletions = blobsToDelete.stream()
216+
.map(blobItem -> {
217+
TileObject tile = createTileObject(blobItem, tileRange);
218+
tile.setParametersId(tileRange.getParametersId());
219+
return (Callable<Object>) () -> delete(tile);
220+
})
221+
.collect(Collectors.toList());
222+
223+
executeParallelDeletions(tilesDeletions);
240224
}
241225

242226
return true;
243227
}
244228

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

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import com.azure.storage.blob.BlobContainerClient;
2727
import com.azure.storage.blob.BlobServiceClient;
2828
import com.azure.storage.blob.BlobServiceClientBuilder;
29+
import com.azure.storage.blob.batch.BlobBatchClient;
30+
import com.azure.storage.blob.batch.BlobBatchClientBuilder;
2931
import com.azure.storage.blob.models.BlobDownloadContentResponse;
3032
import com.azure.storage.blob.models.BlobHttpHeaders;
3133
import com.azure.storage.blob.models.BlobItem;
@@ -55,6 +57,7 @@ public class AzureClient {
5557

5658
private AzureBlobStoreData configuration;
5759
private final BlobContainerClient container;
60+
private final BlobBatchClient batch;
5861

5962
public AzureClient(AzureBlobStoreData configuration) throws StorageException {
6063
this.configuration = configuration;
@@ -64,6 +67,7 @@ public AzureClient(AzureBlobStoreData configuration) throws StorageException {
6467

6568
String containerName = configuration.getContainer();
6669
this.container = getOrCreateContainer(serviceClient, containerName);
70+
this.batch = new BlobBatchClientBuilder(serviceClient).buildClient();
6771
} catch (StorageException e) {
6872
throw e;
6973
} catch (RuntimeException e) {
@@ -284,6 +288,10 @@ public BlobContainerClient getContainer() {
284288
return container;
285289
}
286290

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

0 commit comments

Comments
 (0)