Merge pull request #32786 from pedronastasi/fix-O-n2-complexity
Fix O(n²) performance bottleneck in buildEntitySearch traverse()
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Fixed O(n²) performance bottleneck in `buildEntitySearch` `traverse()` by replacing `Array.some()` linear scan with a `Set` for O(1) duplicate path key detection.
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { DEFAULT_NAMESPACE, Entity } from '@backstage/catalog-model';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import { buildEntitySearch, mapToRows, traverse } from './buildEntitySearch';
|
||||
|
||||
describe('buildEntitySearch', () => {
|
||||
@@ -50,6 +51,23 @@ describe('buildEntitySearch', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles large arrays without quadratic performance degradation', () => {
|
||||
// Generate an array with 500 unique string items to verify that the
|
||||
// Set-based dedup scales linearly rather than quadratically.
|
||||
// With the previous Array.some() approach this would cause ~125,000
|
||||
// comparisons; with Set.has() it's ~500 lookups.
|
||||
const items = Array.from({ length: 500 }, (_, i) => `tag-${i}`);
|
||||
const input = { tags: items };
|
||||
|
||||
const start = performance.now();
|
||||
const output = traverse(input);
|
||||
const elapsed = performance.now() - start;
|
||||
|
||||
expect(output).toHaveLength(1000);
|
||||
// Should complete well under 100ms with O(n); O(n²) would be noticeably slower
|
||||
expect(elapsed).toBeLessThan(100);
|
||||
});
|
||||
|
||||
it('skips over special keys', () => {
|
||||
const input = {
|
||||
status: { x: 1 },
|
||||
|
||||
@@ -71,6 +71,11 @@ type Kv = {
|
||||
// "h.j": "l"
|
||||
export function traverse(root: unknown): Kv[] {
|
||||
const output: Kv[] = [];
|
||||
// Use a Set for O(1) case-insensitive duplicate detection of synthetic
|
||||
// boolean path keys (e.g. "metadata.tags.java"), instead of the previous
|
||||
// O(n) Array.some() linear scan which caused O(n²) overall complexity
|
||||
// and severe event loop blocking for entities with large arrays.
|
||||
const seenPathKeys = new Set<string>();
|
||||
|
||||
function visit(path: string, current: unknown) {
|
||||
if (SPECIAL_KEYS.includes(path)) {
|
||||
@@ -111,13 +116,9 @@ export function traverse(root: unknown): Kv[] {
|
||||
visit(path, item);
|
||||
if (typeof item === 'string') {
|
||||
const pathKey = `${path}.${item}`;
|
||||
if (
|
||||
!output.some(
|
||||
kv =>
|
||||
kv.key.toLocaleLowerCase('en-US') ===
|
||||
pathKey.toLocaleLowerCase('en-US'),
|
||||
)
|
||||
) {
|
||||
const lowerKey = pathKey.toLocaleLowerCase('en-US');
|
||||
if (!seenPathKeys.has(lowerKey)) {
|
||||
seenPathKeys.add(lowerKey);
|
||||
output.push({ key: pathKey, value: true });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user