Skip to content

Conversation

@axl8713
Copy link
Contributor

@axl8713 axl8713 commented Nov 20, 2025

These changes improve the handling of bulk deletions of cached tiles in Azure Blob storage.

Previously, truncating a TileRange involved blindly issuing one deletion request for every possible tile in the range, potentially flooding the blob store with thousands of unnecessary requests for tiles that did not exist.

With these changes, GWC first checks which tiles actually exist in the blob store within the specified range, and then issues a delete request only for those tiles.

Moreover, to avoid making one API call per deletion, the Azure BlobBatchClient
is now used to batch delete operations, reducing overhead and simplifying the code.

PS: Azure Batch Delete is documented as supporting up to 256 operations per batch, although this limit is not entirely clear or consistently referenced across all sources:

Each batch request supports a maximum of 256 subrequests.

As a precaution, the page size has therefore been reduced from 1000 to 256 to ensure full compatibility with the platform’s batching constraints.

@axl8713 axl8713 requested a review from aaime November 25, 2025 10:40
@axl8713 axl8713 force-pushed the azure_tile_better_truncate branch from e8fef4f to fd48c02 Compare November 25, 2025 11:54
@axl8713 axl8713 requested a review from afabiani November 25, 2025 11:55
Copy link
Contributor

@afabiani afabiani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential concerns are quite minor:

  1. Log injection / spam
    All log messages are parameterized via String.formatted, so you’re not concatenating untrusted text into log format specifiers. Unless user-controlled data can become part of prefix or container name (unlikely in GeoWebCache’s Azure store config), log injection isn’t a realistic threat.

  2. DOS via huge delete requests
    Massive delete operations can always cause heavy load, but that was already true of the previous implementation. The patch doesn’t open a new path for an attacker to cause more damage than they could before (assuming the same APIs let them trigger a large truncate).

.map(n -> container.getBlobClient(n).getBlobUrl())
.collect(Collectors.toList());

return batch.deleteBlobs(blobsUrls, DeleteSnapshotsOptionType.INCLUDE).stream()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PrefixBulkDelete may exceed the batch limit. Nothing here enforces itemNames.size() <= PAGE_SIZE. If Azure’s page size is greater than 256, you will send a batch with more than 256 deletes, which violates the batch constraints that motivated PAGE_SIZE = 256.

Recommendation (important):
Either set the page size explicitly:

ListBlobsOptions options = new ListBlobsOptions()
    .setPrefix(prefix)
    .setDetails(new BlobListDetails().setRetrieveDeletedBlobs(true))
    .setMaxResults(DeleteManager.PAGE_SIZE);

Or, more robustly, partition items inside the loop:

List<String> items = ...
for (int i = 0; i < items.size(); i += PAGE_SIZE) {
    count += deleteItems(container, batch,
        items.subList(i, Math.min(i + PAGE_SIZE, items.size())));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being a private method that does what it says, I don't know if we need to repeat the check here.

@aaime
Copy link
Member

aaime commented Nov 25, 2025

@axl8713 the new streaming approach seems to work, however it's a bit convoluted... you start from a stream, turn it into an iterator, batch it, create a stream of lists that is consumed later. Seems pretty complicated.
Why not keep it a single stream until the end (filtered and mapped as needed of course), and then decide what to do with it when you know if you have a listener or not, if you don't consume 256 items from the stream and send it over, otherwise feed it into the parallel deleter. Looping over the zoom levels at the top would also simplify the code quite a bit.

@axl8713 axl8713 force-pushed the azure_tile_better_truncate branch from f1021c9 to c15b7e4 Compare November 28, 2025 16:58
@axl8713
Copy link
Contributor Author

axl8713 commented Nov 28, 2025

Thanks, @aaime. I was overthinking it.

Could you please check my new version to see if it is simpler?

@axl8713 axl8713 requested a review from afabiani December 1, 2025 14:36
Copy link
Member

@aaime aaime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@aaime aaime merged commit 3cf6a77 into GeoWebCache:main Dec 1, 2025
11 of 12 checks passed
@geoserver-bot
Copy link
Collaborator

The backport to 1.27.x failed:

The process '/usr/bin/git' failed with exit code 1
stderr
error: could not apply 7201edafd... Revised AzureBlobStore deletions.
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"

stdout
Auto-merging geowebcache/azureblob/pom.xml
Auto-merging geowebcache/azureblob/src/main/java/org/geowebcache/azure/AzureClient.java
Auto-merging geowebcache/azureblob/src/main/java/org/geowebcache/azure/DeleteManager.java
CONFLICT (content): Merge conflict in geowebcache/azureblob/src/main/java/org/geowebcache/azure/DeleteManager.java

To backport manually, run these commands in your terminal:

# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-1.27.x 1.27.x
# Navigate to the new working tree
cd .worktrees/backport-1.27.x
# Create a new branch
git switch --create backport-1451-to-1.27.x
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick 7201edafdd2b82b17ae3d0657a1a6aebb1d040d7,690258c87df9c9f223e39cee8af639325a12c31a,c15b7e4cd8f52349d86337455b9d5d78b9cb78ee,c91d5877ecfeda9d890fc5f60ddb823ad5ae4e82
# Push it to GitHub
git push --set-upstream origin backport-1451-to-1.27.x
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-1.27.x

Then, create a pull request where the base branch is 1.27.x and the compare/head branch is backport-1451-to-1.27.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants