Remove the unused join_field from the main Advanced Search index
Summary
The main Advanced Search index mapping declares a join_field (type join) with relation project: [issue, merge_request, milestone, note, blob, wiki_blob, commit], but no search reads it anymore. Project documents have also moved to their own index, so the join has no parent documents in the main index. This removes the dead read path, stops writing the field, and strips it from stored documents, so the mapping can drop it at the next main-index reindex.
Scope: main index only. This issue removes join_field from the main combined Advanced Search index (gitlab-production, mapping in ee/lib/elastic/latest/config.rb). It is the only index mapping that declares join_field, so the separate indices (work items, merge requests, notes, projects, commits, wikis, users, etc.) are not changed and don't need a reindex. This issue covers the code changes and stripping the field from stored documents, none of which needs a reindex. Dropping the field from the mapping happens in the main-index reindex in #600600, which this issue blocks.
Current state
- Mapping:
ee/lib/elastic/latest/config.rb. - Dead read path: the only
has_parentquery is inElastic::Latest::ApplicationClassProxy#project_ids_filter. It's only reached through#authorization_filter, which has no callers since the git class proxy moved to shared filters in eaf8adc0 ("Remove search_query_authorization_refactor feature flag"). - Active searches all skip the join:
Search::Elastic::QueryBuilder#base_optionssetsno_join_project: truefor every query-builder search, including milestones.- Commits are in their own index (
Elastic::Latest::CommitConfig.index_name); commit search setsno_join_project: true. - Blob search sets it from
GitClassProxy#disable_project_joins_for_blob?, which returnstrueunconditionally. - Note search sets
no_join_project: true.
- No
has_childqueries exist. - Project documents are in a separate index (
Project.use_separate_indices?is true), routed byn_<root_ancestor_id>. - Writers from Rails:
ApplicationInstanceProxy#generic_attributesaddsjoin_fieldwheneveres_parentis present. Today that means milestones only: project milestones (parentproject_<id>) and, since !257456 (merged), group milestones (parentn_<root_ancestor_id>). Merge requests and notes strip it (super.except('join_field')). Snippets have noes_parent. - Deletes don't use it:
ElasticDeleteProjectWorker#remove_children_documentsdeletes byproject_id/rid/commit.rid/target_project_idterms with project routing. gitlab-elasticsearch-indexer(Go, separate repo) writesjoin_fieldon every blob document (internal/mode/advanced/indexer/indexer.go). Routing doesn't depend on it: the indexer setsproject_<id>routing separately (routingForininternal/mode/advanced/elastic/client.go).
Tasks
- Remove the dead join path in
ApplicationClassProxy:#authorization_filter,#should_use_project_ids_filter?,#project_ids_filter,#project_ids_queryand#pick_projects_by_membershiphave no callers outside that chain. Then remove theno_join_projectplumbing that only existed to switch the join off, and update the comment inee/lib/gitlab/elastic/search_results.rbthat points toproject_ids_query.- !259063 (merged). Removes the whole unused chain, plus helpers only it used.
no_join_projectstays, because it also controls the doc type filter that snippet search needs.
- !259063 (merged). Removes the whole unused chain, plus helpers only it used.
- Stop writing
join_fieldfrom Rails: drop it fromApplicationInstanceProxy#generic_attributes, then remove theexcept('join_field')overrides in the merge request and note proxies. - Stop the Go indexer writing
join_fieldfor blobs, and update its test mapping (defaultIndexMappingininternal/mode/advanced/elastic/index.go).- gitlab-elasticsearch-indexer!947 (merged). Then bump
GITLAB_ELASTICSEARCH_INDEXER_VERSIONin gitlab-org/gitlab after the indexer release.
- gitlab-elasticsearch-indexer!947 (merged). Then bump
- Strip
join_fieldfrom stored documents with aMigrationRemoveFieldsHelpermigration (update_by_querywithctx._source.remove), after both writers have stopped. Local testing showed this works while the join mapping still exists and keeps routing (see the 2026-10-01 comment on #600600).- !261846. A standalone migration rather than
MigrationRemoveFieldsHelper, because the helper handles one document type andjoin_fieldis on both blob and milestone documents. Merge afterGITLAB_ELASTICSEARCH_INDEXER_VERSIONis bumped to 5.14.18 (gitlab-elasticsearch-indexer!960 (closed)), so blobs don't get the field back.
- !261846. A standalone migration rather than
- Confirm the
existscount forjoin_fieldis 0 on staging and production, so #600600 can drop it from the mapping.
Notes
- Existing documents keep the field until they're rewritten. That's harmless while the mapping still has it, but they must be stripped before #600600 drops it from the mapping: the mapping is
dynamic: 'strict'and the reindex copies_sourceas-is, so any document that still hasjoin_fieldfails its slice and the reindex aborts on the document count check. - Came up in #428589 (group milestones in Advanced Search).