Fix GLQL ES performance regression: optimize `project_ids` pre-query in Work Items ES finder
## Problem to solve
GLQL views on large namespaces (e.g. `gitlab-org` with ~7,000 projects) take ~10 seconds to load
when Advanced Search is enabled via `glql_es_integration`. The regression was introduced in commit
`3a84ec8d` (2026-02-27, "Remove FF `search_project_list_lookup`"), which changed the `project_ids`
method in `ee/lib/ee/search/advanced_finders/work_items_finder.rb` from a lightweight path-based
lookup to a heavy join-based query using `ProjectsFinder`:
```ruby
# Current (slow) — introduced by 3a84ec8d
def project_ids
projects = ::ProjectsFinder.new(current_user: current_user)
.execute
.for_group_and_its_subgroups(resource_parent)
.with_route
.include_topics
.without_order
projects.pluck_primary_key
end
```
This creates two compounding performance problems for large groups:
1. **Expensive PG pre-query**: `ProjectsFinder` with `for_group_and_its_subgroups` + `with_route` +
`include_topics` joins is significantly slower than the previous `inside_path_preloaded` ILIKE-based
lookup, especially for groups with thousands of projects.
2. **Large ES `terms` filter**: The resulting project IDs (e.g. 7,034 for `gitlab-org`) are sent as
an ES `terms` filter on every GLQL query, making the ES query itself expensive.
The file already has a comment acknowledging this is known technical debt:
```ruby
# NOTE: project_ids should be removed when the traversal_ids
# optimization is implemented for confidentiality filters
# https://gitlab.com/gitlab-org/gitlab/-/issues/558781
```
**Mitigation applied:** `glql_es_integration` was disabled on GitLab.com on 2026-03-05, routing
GLQL queries to PostgreSQL (~0.3s). This needs to be re-enabled once the fix is in place.
Postmortem: https://gitlab.com/gitlab-org/gitlab/-/work_items/592405#note_3134535593
## Proposal
Two complementary fixes, in order of urgency:
### Fix 1 — Add caching to `project_ids` (short-term, days)
Add Rails cache with a short TTL (e.g. 5 minutes) to `project_ids`, keyed by group ID and user ID.
This amortizes the PG cost across multiple concurrent GLQL requests for the same group, which is the
common case (many users viewing the same wiki page):
```ruby
def project_ids
Rails.cache.fetch(['glql_es_project_ids', resource_parent.id, current_user.id], expires_in: 5.minutes) do
::ProjectsFinder.new(current_user: current_user)
.execute
.for_group_and_its_subgroups(resource_parent)
.with_route
.include_topics
.without_order
.pluck_primary_key
end
end
```
### Fix 2 — Implement `traversal_ids` optimization (long-term, per #558781)
The correct architectural fix is to eliminate the `project_ids` pre-query entirely by using ES
`traversal_ids` range filtering to scope queries to a namespace subtree — a single efficient ES
range filter instead of thousands of explicit project IDs. This is already tracked in
https://gitlab.com/gitlab-org/gitlab/-/issues/558781 (now closed for confidentiality filters).
The work items ES finder should be updated to use the same `traversal_ids` approach, removing the
`project_ids` method and its callers entirely once the index supports it.
## Further details
- **Regression commit:** `3a84ec8d` — "Remove FF `search_project_list_lookup`" (2026-02-27)
- **Affected file:** `ee/lib/ee/search/advanced_finders/work_items_finder.rb`, `project_ids` method (~line 215)
- **Related traversal_ids work:** https://gitlab.com/gitlab-org/gitlab/-/issues/558781
- **Incident:** https://gitlab.com/gitlab-org/gitlab/-/work_items/592405
## Links / references
- Incident (postmortem in comments): https://gitlab.com/gitlab-org/gitlab/-/work_items/592405
- `traversal_ids` optimization: https://gitlab.com/gitlab-org/gitlab/-/issues/558781
- Admin setting to control `glql_es_integration`: https://gitlab.com/gitlab-org/gitlab/-/issues/592415
issue