2.6 KiB
2.6 KiB
UNDF: UNDF-2026-000000385
elasticsearch-004: IndexGraveyard.containsIndex O(n²) List scan in DanglingIndicesState loop
Classification
- CWE: CWE-407 (Inefficient Algorithmic Complexity)
- Severity: MEDIUM
- Path: Node startup and periodic dangling-index detection
Location
server/src/main/java/org/elasticsearch/cluster/metadata/IndexGraveyard.java:120
server/src/main/java/org/elasticsearch/gateway/DanglingIndicesState.java:67
Defect
// IndexGraveyard.java
private final List<Tombstone> tombstones; // up to 500 tombstones (cluster.indices.tombstones.size)
public boolean containsIndex(final Index index) {
for (Tombstone tombstone : tombstones) { // O(T) — linear scan
if (tombstone.getIndex().equals(index)) {
return true;
}
}
return false;
}
// DanglingIndicesState.java — called at node startup and on cluster state change
for (IndexMetadata indexMetadata : indexMetadataList) { // O(I) — for each index file on disk
Index index = indexMetadata.getIndex();
if (graveyard.containsIndex(index) == false) { // O(T) — linear scan per index
danglingIndices.put(index, stripAliases(indexMetadata));
}
}
Total complexity: O(I × T) where I = index files on disk, T = tombstones (default max 500).
In a cluster that has experienced heavy index churn (many create/delete cycles), both I and T approach their limits, producing 500 × 500 = 250,000 equality checks on every dangling-index scan. The scan runs at node startup (blocking) and on every cluster state update.
Fix
Pre-build a HashSet<Index> from the tombstones once, then do O(1) membership tests:
// IndexGraveyard.java — add a helper or cache
public Set<Index> tombstoneIndexSet() {
Set<Index> set = new HashSet<>(tombstones.size() * 2);
for (Tombstone t : tombstones) {
set.add(t.getIndex());
}
return set;
}
// DanglingIndicesState.java
Set<Index> graveyardSet = graveyard.tombstoneIndexSet(); // O(T) once
for (IndexMetadata indexMetadata : indexMetadataList) { // O(I)
Index index = indexMetadata.getIndex();
if (graveyardSet.contains(index) == false) { // O(1)
danglingIndices.put(index, stripAliases(indexMetadata));
}
}
Result: O(I + T) — linear rather than quadratic.
Overhead Measurement
| I (indices) | T (tombstones) | Slow ops | Fast ops | Ratio |
|---|---|---|---|---|
| 100 | 100 | 10,000 | 200 | 50x |
| 500 | 500 | 250,000 | 1,000 | 250x |
| 1000 | 500 | 500,000 | 1,500 | 333x |