diff --git a/.changeset/fix-facets-perf-regression.md b/.changeset/fix-facets-perf-regression.md new file mode 100644 index 0000000000..71f293271a --- /dev/null +++ b/.changeset/fix-facets-perf-regression.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed a performance regression in the `/entity-facets` endpoint when filters or permission conditions are applied, by routing the EXISTS-based filter through `final_entities` instead of correlating against the much larger `search` table. diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 7fda0ac0f1..830120e486 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -690,13 +690,24 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { .groupBy(['search.key', 'search.original_value']); if (request.filter || request.query) { + // Build a subquery that finds matching entity IDs via + // final_entities, so that the EXISTS-based filters correlate + // against one-row-per-entity rather than the much larger search + // table. This keeps the facets aggregation fast even with many + // filter clauses or permission conditions. + const entityIdSubquery = this.database('final_entities') + .select('final_entities.entity_id') + .whereNotNull('final_entities.final_entity'); + applyEntityFilterToQuery({ filter: request.filter, query: request.query, - targetQuery: query, - onEntityIdField: 'search.entity_id', + targetQuery: entityIdSubquery, + onEntityIdField: 'final_entities.entity_id', knex: this.database, }); + + query.whereIn('search.entity_id', entityIdSubquery); } const rows = await query;