Add feature flag to skip related_ids query for large namespaces
What does this MR do and why?
Fixes namespace-wide searches failing when namespaces have more than 65,536 projects.
When searching in very large namespaces, the related_ids_for_notes query generates an Elasticsearch terms query containing all project IDs in the namespace. For namespaces with more than 65k projects, this exceeds Elasticsearch's index.max_terms_count limit, causing all searches (issues, merge requests, notes) to fail.
This MR adds a feature flag search_skip_related_ids that allows skipping the related_ids_for_notes query for affected namespaces. The guard is implemented in the base SearchResults class, so it applies to both group-level and project-level searches. The flag is checked against the root ancestor of the searched namespace, so enabling it for a top-level namespace automatically applies to all subgroups and projects within that hierarchy.
Trade-off: The related_ids_for_notes query boosts issues/MRs that have matching notes in search results. Disabling it means slightly lower search relevancy, but searches will work instead of failing completely.
References
- Related to ongoing TraversalIDs optimization work for search scopes
How to set up and validate locally
Prerequisites
- Ensure Elasticsearch is running in GDK
- Have a group with indexed projects
Testing the fix
-
Enable the feature flag for a group:
# In rails console group = Group.find_by_full_path('your-group') Feature.enable(:search_skip_related_ids, group) -
Perform searches at different levels:
- Group search:
http://localhost:3000/groups/your-group/-/search?search=test&scope=issues - Project search:
http://localhost:3000/your-group/your-project/-/search?search=test&scope=issues - Verify both complete successfully
- Group search:
-
Verify the query behavior for group searches:
# In rails console user = User.first group = Group.find_by_full_path('your-group') # With FF disabled (default) - calls Note.elastic_search Feature.disable(:search_skip_related_ids, group) results = Gitlab::Elastic::GroupSearchResults.new(user, 'test', [], group: group) results.send(:scope_options, :merge_requests)[:related_ids] # => Returns array of IDs (or empty if no matches) # With FF enabled - skips Note.elastic_search Feature.enable(:search_skip_related_ids, group) results = Gitlab::Elastic::GroupSearchResults.new(user, 'test', [], group: group) results.send(:scope_options, :merge_requests)[:related_ids] # => Returns [] -
Verify it works for project searches:
# Enable for top-level group group = Group.find_by_full_path('your-group') Feature.enable(:search_skip_related_ids, group) # Search in project - flag is inherited from root ancestor project = Project.find_by_full_path('your-group/your-project') results = Gitlab::Elastic::ProjectSearchResults.new(user, 'test', project: project) results.send(:skip_related_ids_for_large_namespace?) # => true -
Verify it works for subgroups:
# Enable for top-level group top_group = Group.find_by_full_path('your-group') Feature.enable(:search_skip_related_ids, top_group) # Search in subgroup - flag is inherited subgroup = Group.find_by_full_path('your-group/subgroup') results = Gitlab::Elastic::GroupSearchResults.new(user, 'test', [], group: subgroup) results.send(:skip_related_ids_for_large_namespace?) # => true
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.