From b657f297f82b92877b9749d3520dc87aeef16d05 Mon Sep 17 00:00:00 2001 From: Gaurav Bafna Date: Fri, 24 Apr 2026 16:50:53 +0530 Subject: [PATCH] Fix O(n^2) removeAll in remote translog metadata cleanup ArrayList.removeAll(ArrayList) is O(n*m) due to linear contains() checks. Wrap the argument in HashSet for O(1) lookups, reducing the complexity to O(n). This was causing 500ms CPU spikes on the remote_purge thread when metadata file counts grew large. Signed-off-by: Gaurav Bafna --- .../RemoteFsTimestampAwareTranslog.java | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/opensearch/index/translog/RemoteFsTimestampAwareTranslog.java b/server/src/main/java/org/opensearch/index/translog/RemoteFsTimestampAwareTranslog.java index 1832d1e7d035a..32d79439b004e 100644 --- a/server/src/main/java/org/opensearch/index/translog/RemoteFsTimestampAwareTranslog.java +++ b/server/src/main/java/org/opensearch/index/translog/RemoteFsTimestampAwareTranslog.java @@ -202,12 +202,22 @@ public void onResponse(List blobMetadata) { return; } - logger.debug(() -> "metadataFilesToBeDeleted = " + metadataFilesToBeDeleted); + logger.debug( + () -> "metadataFilesToBeDeleted count = " + + metadataFilesToBeDeleted.size() + + ", metadataFilesToBeDeleted = " + + metadataFilesToBeDeleted + ); // For all the files that we are keeping, fetch min and max generations List metadataFilesNotToBeDeleted = new ArrayList<>(metadataFiles); - metadataFilesNotToBeDeleted.removeAll(metadataFilesToBeDeleted); + metadataFilesNotToBeDeleted.removeAll(new HashSet<>(metadataFilesToBeDeleted)); - logger.debug(() -> "metadataFilesNotToBeDeleted = " + metadataFilesNotToBeDeleted); + logger.debug( + () -> "metadataFilesNotToBeDeleted count = " + + metadataFilesNotToBeDeleted.size() + + ", metadataFilesNotToBeDeleted = " + + metadataFilesNotToBeDeleted + ); Set generationsToBeDeleted = getGenerationsToBeDeleted( metadataFilesNotToBeDeleted, @@ -373,7 +383,7 @@ protected static List getMetadataFilesToBeDeleted( long maxGeneration = TranslogTransferMetadata.getMaxGenerationFromFileName(md); return maxGeneration == -1 || maxGeneration >= minGenerationToKeepInRemote; }).collect(Collectors.toList()); - metadataFilesToBeDeleted.removeAll(metadataFilesContainingMinGenerationToKeep); + metadataFilesToBeDeleted.removeAll(new HashSet<>(metadataFilesContainingMinGenerationToKeep)); logger.trace( "metadataFilesContainingMinGenerationToKeep.size = {}, metadataFilesToBeDeleted based on minGenerationToKeep filtering = {}, minGenerationToKeep = {}", @@ -572,12 +582,22 @@ public void onResponse(List blobMetadata) { staticLogger.debug("No metadata files to delete"); return; } - staticLogger.debug(() -> "metadataFilesToBeDeleted = " + metadataFilesToBeDeleted); + staticLogger.debug( + () -> "metadataFilesToBeDeleted count = " + + metadataFilesToBeDeleted.size() + + ", metadataFilesToBeDeleted = " + + metadataFilesToBeDeleted + ); // For all the files that we are keeping, fetch min and max generations List metadataFilesNotToBeDeleted = new ArrayList<>(metadataFiles); - metadataFilesNotToBeDeleted.removeAll(metadataFilesToBeDeleted); - staticLogger.debug(() -> "metadataFilesNotToBeDeleted = " + metadataFilesNotToBeDeleted); + metadataFilesNotToBeDeleted.removeAll(new HashSet<>(metadataFilesToBeDeleted)); + staticLogger.debug( + () -> "metadataFilesNotToBeDeleted count = " + + metadataFilesNotToBeDeleted.size() + + ", metadataFilesNotToBeDeleted = " + + metadataFilesNotToBeDeleted + ); // Delete stale metadata files translogTransferManager.deleteMetadataFilesAsync(metadataFilesToBeDeleted, () -> {});