1.9 KiB
1.9 KiB
UNDF: UNDF-2026-000000385
elasticsearch-001: MMRResultDiversification O(n²) selectedDocRanks.contains
Classification
- CWE: CWE-407 (Inefficient Algorithmic Complexity)
- Severity: HIGH
- Path: Hot query-time ranking path — every MMR diversified search request
Location
server/src/main/java/org/elasticsearch/search/diversification/mmr/MMRResultDiversification.java:63
Defect
List<Integer> selectedDocRanks = new ArrayList<>();
// ...
for (int x = 0; x < topDocsSize && ...; x++) {
for (RankDoc doc : docs) {
int docRank = doc.rank;
if (selectedDocRanks.contains(docRank)) { // O(n) ArrayList scan
continue;
}
// ...
}
selectedDocRanks.add(thisMaxMMRDocRank);
}
The outer loop runs up to topDocsSize iterations; the inner loop runs docs.length
iterations; inside the inner loop selectedDocRanks.contains(docRank) performs an
O(selectedDocRanks.size()) linear scan.
Total complexity: O(topDocsSize × docs × selectedDocRanks) = O(n³) in the worst case, reducing to O(n²) for typical result windows.
For a 1000-doc result window with 100 selected docs, this is ~100,000 list-scans per query instead of ~100,000 O(1) set lookups.
Fix
Pre-build a HashSet<Integer> that is kept in sync with selectedDocRanks:
List<Integer> selectedDocRanks = new ArrayList<>();
Set<Integer> selectedDocRankSet = new HashSet<>();
// when adding:
selectedDocRanks.add(thisMaxMMRDocRank);
selectedDocRankSet.add(thisMaxMMRDocRank);
// in the guard:
if (selectedDocRankSet.contains(docRank)) { // O(1)
continue;
}
Complexity
| Metric | Before | After |
|---|---|---|
| contains() | O(n) | O(1) |
| Full loop | O(n²)–O(n³) | O(n²) |
| At n=1000 | ~500,000 comparisons | ~1,000 |
Affected Versions
All versions that include MMRResultDiversification (introduced with semantic MMR ranking feature).