Skip to content

Commit e8fef4f

Browse files
committed
Refactored tile range deletion to be streamed.
1 parent 328177b commit e8fef4f

File tree

1 file changed

+83
-45
lines changed

1 file changed

+83
-45
lines changed

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

Lines changed: 83 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
import java.io.UncheckedIOException;
2929
import java.time.OffsetDateTime;
3030
import java.util.ArrayList;
31+
import java.util.Collections;
3132
import java.util.Iterator;
3233
import java.util.List;
3334
import java.util.Map;
35+
import java.util.NoSuchElementException;
3436
import java.util.Optional;
3537
import java.util.Properties;
3638
import java.util.concurrent.Callable;
@@ -39,6 +41,7 @@
3941
import java.util.regex.Pattern;
4042
import java.util.stream.Collectors;
4143
import java.util.stream.Stream;
44+
import java.util.stream.StreamSupport;
4245
import javax.annotation.Nullable;
4346
import org.geotools.util.logging.Logging;
4447
import org.geowebcache.filter.parameters.ParametersUtils;
@@ -187,66 +190,101 @@ public boolean delete(TileObject obj) throws StorageException {
187190

188191
@Override
189192
public boolean delete(TileRange tileRange) throws StorageException {
190-
// see if there is anything to delete in that range by computing a prefix
191-
final String coordsPrefix = keyBuilder.coordinatesPrefix(tileRange, false);
192-
if (!client.prefixExists(coordsPrefix)) {
193-
return false;
194-
}
193+
try {
194+
// see if there is anything to delete in that range by computing a prefix
195+
final String coordsPrefix = keyBuilder.coordinatesPrefix(tileRange, false);
196+
if (!client.prefixExists(coordsPrefix)) {
197+
return false;
198+
}
195199

196-
List<BlobItem> blobsToDelete = findTileBlobsToDelete(tileRange, coordsPrefix);
200+
findTileBlobsToDelete(tileRange, coordsPrefix).forEach(blobs -> deleteTileBlobs(blobs, tileRange));
197201

198-
if (listeners.isEmpty()) {
199-
// if there are no listeners, don't bother requesting every tile
200-
// metadata to notify the listeners
201-
// split the iteration in parts to avoid memory accumulation
202-
List<String> keysToDelete =
203-
blobsToDelete.stream().map(BlobItem::getName).collect(Collectors.toList());
202+
return true;
204203

205-
Iterator<List<String>> partition = Iterators.partition(keysToDelete.iterator(), DeleteManager.PAGE_SIZE);
204+
} catch (RuntimeException e) {
205+
throw new StorageException("Failed to delete tile range", e);
206+
}
207+
}
206208

207-
while (partition.hasNext() && !shutDown) {
208-
deleteManager.deleteParallel(partition.next());
209-
}
209+
/** Splits the deletion in parts to avoid memory accumulation. */
210+
private Stream<List<BlobItem>> findTileBlobsToDelete(TileRange tileRange, String coordsPrefix) {
210211

211-
} else {
212-
// if we need to gather info, we'll end up just calling "delete" on each tile
213-
// this is run here instead of inside the delete manager as we need high level info
214-
// about tiles, e.g., TileObject, to inform the listeners
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);
224-
}
212+
Iterable<List<BlobItem>> iterable = () -> new Iterator<>() {
213+
private final int zoomStart = tileRange.getZoomStart();
214+
private final int zoomStop = tileRange.getZoomStop();
215+
private int currentZoom = zoomStart;
216+
private Iterator<BlobItem> currentBlobIterator = Collections.emptyIterator();
225217

226-
return true;
227-
}
218+
@Override
219+
public boolean hasNext() {
220+
while (!currentBlobIterator.hasNext() && currentZoom <= zoomStop) {
221+
String zoomPrefix = coordsPrefix + "/" + currentZoom;
222+
223+
if (!client.prefixExists(zoomPrefix)) {
224+
// empty level, skipping
225+
currentZoom++;
226+
continue;
227+
}
228228

229-
private List<BlobItem> findTileBlobsToDelete(TileRange tileRange, String coordsPrefix) {
229+
long[] rangeBoundsAtZoom = tileRange.rangeBounds(currentZoom);
230+
currentBlobIterator = client.listBlobs(zoomPrefix)
231+
.filter(tb -> isTileBlobInBounds(tb, rangeBoundsAtZoom))
232+
.iterator();
230233

231-
List<BlobItem> blobsToDelete = new ArrayList<>();
234+
currentZoom++;
235+
}
232236

233-
for (int zoom = tileRange.getZoomStart(); zoom <= tileRange.getZoomStop(); zoom++) {
237+
return currentBlobIterator.hasNext();
238+
}
234239

235-
String zoomPrefix = coordsPrefix + "/" + zoom;
240+
@Override
241+
public List<BlobItem> next() {
242+
if (!hasNext()) {
243+
throw new NoSuchElementException();
244+
}
236245

237-
if (!client.prefixExists(zoomPrefix)) {
238-
// empty level, skipping
239-
continue;
246+
List<BlobItem> batch = new ArrayList<>(DeleteManager.PAGE_SIZE);
247+
while (currentBlobIterator.hasNext() && batch.size() < DeleteManager.PAGE_SIZE) {
248+
batch.add(currentBlobIterator.next());
249+
}
250+
return batch;
240251
}
252+
};
241253

242-
long[] rangeBoundsAtZoom = tileRange.rangeBounds(zoom);
254+
return StreamSupport.stream(iterable.spliterator(), false);
255+
}
243256

244-
client.listBlobs(zoomPrefix)
245-
.filter(tb -> isTileBlobInBounds(tb, rangeBoundsAtZoom))
246-
.forEach(blobsToDelete::add);
257+
private void deleteTileBlobs(List<BlobItem> blobs, TileRange tileRange) {
258+
try {
259+
if (listeners.isEmpty()) {
260+
// if there are no listeners, don't bother requesting every tile
261+
// metadata to notify the listeners
262+
List<String> keysToDelete =
263+
blobs.stream().map(BlobItem::getName).collect(Collectors.toList());
264+
265+
if (!shutDown) {
266+
deleteManager.deleteParallel(keysToDelete);
267+
}
268+
} else {
269+
// if we need to gather info, we'll end up just calling "delete" on each tile
270+
// this is run here instead of inside the delete manager as we need high level info
271+
// about tiles, e.g., TileObject, to inform the listeners
272+
List<Callable<?>> tilesDeletions = blobs.stream()
273+
.map(blobItem -> {
274+
TileObject tile = createTileObject(blobItem, tileRange);
275+
tile.setParametersId(tileRange.getParametersId());
276+
277+
// PMD is being picky with this lambda inlined
278+
Callable<Boolean> deleteCallable = () -> delete(tile);
279+
return deleteCallable;
280+
})
281+
.collect(Collectors.toList());
282+
283+
executeParallelDeletions(tilesDeletions);
284+
}
285+
} catch (StorageException e) {
286+
throw new RuntimeException(e);
247287
}
248-
249-
return blobsToDelete;
250288
}
251289

252290
private boolean isTileBlobInBounds(BlobItem tileBlob, long[] bounds) {

0 commit comments

Comments
 (0)