From b2baac00c96c1a35852332e03f8851e4743dace0 Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Tue, 21 Oct 2025 12:00:28 +0200 Subject: [PATCH 01/11] Ensure WorkItem label changes trigger Advanced Search updates - Add WorkItem to LABEL_INDEXED_MODELS to fix GLQL label queries - Bump WorkItem schema version to 25_43 to trigger reindexing - Add migration to reindex existing WorkItem documents - Include comprehensive test coverage for WorkItem label sync This resolves issue where GLQL queries with label filters returned incorrect results for Epic WorkItems when using Elasticsearch vs database queries Changelog: fixed EE: true --- ee/app/models/ee/label_link.rb | 2 +- ...1021114546_reindex_labels_in_work_items.rb | 12 ++++++++++ ee/lib/search/elastic/references/work_item.rb | 2 +- ...14546_reindex_labels_in_work_items_spec.rb | 24 +++++++++++++++++++ ee/spec/models/ee/label_link_spec.rb | 18 +++++++++++++- 5 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb create mode 100644 ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb diff --git a/ee/app/models/ee/label_link.rb b/ee/app/models/ee/label_link.rb index ad2bbde5f2683e..e00a05576fb73a 100644 --- a/ee/app/models/ee/label_link.rb +++ b/ee/app/models/ee/label_link.rb @@ -4,7 +4,7 @@ module EE module LabelLink extend ActiveSupport::Concern - LABEL_INDEXED_MODELS = %w[Epic Issue MergeRequest].freeze + LABEL_INDEXED_MODELS = %w[Epic Issue MergeRequest WorkItem].freeze prepended do after_destroy :maintain_target_elasticsearch! diff --git a/ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb b/ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb new file mode 100644 index 00000000000000..b3daec83ca1bc2 --- /dev/null +++ b/ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class ReindexLabelsInWorkItems < Elastic::Migration + include ::Search::Elastic::MigrationReindexBasedOnSchemaVersion + + batched! + batch_size 10_000 + throttle_delay 1.minute + + DOCUMENT_TYPE = WorkItem + NEW_SCHEMA_VERSION = 25_43 +end diff --git a/ee/lib/search/elastic/references/work_item.rb b/ee/lib/search/elastic/references/work_item.rb index 8460d80db60979..9ebe51de6fc9e9 100644 --- a/ee/lib/search/elastic/references/work_item.rb +++ b/ee/lib/search/elastic/references/work_item.rb @@ -11,7 +11,7 @@ class WorkItem < Reference # Only applies after migration completes to prevent indexing documents # with new schema before data migration finishes, which would result in # documents not being indexed properly and missing data. - SCHEMA_VERSION = 25_29 + SCHEMA_VERSION = 25_43 DEFAULT_INDEX_ATTRIBUTES = %i[ id iid diff --git a/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb b/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb new file mode 100644 index 00000000000000..6bc7e171e38257 --- /dev/null +++ b/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' +require File.expand_path('ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb') + +RSpec.describe ReindexLabelsInWorkItems, feature_category: :global_search do + describe 'migration', :elastic_delete_by_query, :sidekiq_inline do + include_examples 'migration reindex based on schema_version' do + let(:version) { 20251021114546 } + let(:expected_throttle_delay) { 1.minute } + let(:expected_batch_size) { 10_000 } + let_it_be(:project) { create(:project) } + let_it_be(:label) { create(:label, project: project) } + let_it_be(:group_label) { create(:group_label, group: project.group) } + + let(:objects) do + [ + create(:work_item, project: project, labels: [label]), + create(:work_item, :epic, namespace: project.group, labels: [group_label]) + ] + end + end + end +end diff --git a/ee/spec/models/ee/label_link_spec.rb b/ee/spec/models/ee/label_link_spec.rb index 1ff74e4c56fd0f..4818144fab44e6 100644 --- a/ee/spec/models/ee/label_link_spec.rb +++ b/ee/spec/models/ee/label_link_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe LabelLink, feature_category: :global_search do - describe 'callback ' do + describe 'callback' do describe 'after_destroy' do let_it_be_with_reload(:label) { create(:label) } let_it_be(:label2) { create(:label) } @@ -43,6 +43,22 @@ label.destroy! end end + + context 'for work items' do + let_it_be(:work_item) { create(:work_item, labels: [label]) } + let_it_be(:work_item2) { create(:work_item, labels: [label]) } + let_it_be(:work_item3) { create(:work_item, labels: [label2]) } + + it 'synchronizes elasticsearch for work items which have deleted label attached' do + # WorkItems are stored as Issues in the database due to inheritance, + # so the callback receives Issue objects, not WorkItem objects + expect(Elastic::ProcessBookkeepingService).to receive(:track!).twice do |object| + expect(object.class).to eq(Issue) + expect([work_item.id, work_item2.id]).to include(object.id) + end + label.destroy! + end + end end end end -- GitLab From 2aa07b6729d734074d1b11c68e679f50fd537e0b Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Tue, 21 Oct 2025 12:38:56 +0200 Subject: [PATCH 02/11] Fix spec setup --- .../20251021114546_reindex_labels_in_work_items_spec.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb b/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb index 6bc7e171e38257..74942301d4699c 100644 --- a/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb +++ b/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb @@ -9,14 +9,15 @@ let(:version) { 20251021114546 } let(:expected_throttle_delay) { 1.minute } let(:expected_batch_size) { 10_000 } - let_it_be(:project) { create(:project) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } let_it_be(:label) { create(:label, project: project) } - let_it_be(:group_label) { create(:group_label, group: project.group) } + let_it_be(:group_label) { create(:group_label, group: group) } let(:objects) do [ create(:work_item, project: project, labels: [label]), - create(:work_item, :epic, namespace: project.group, labels: [group_label]) + create(:work_item, :epic, namespace: group, labels: [group_label]) ] end end -- GitLab From a30fe6f2e78ffb71f605f1b36f8fbd9c71f1073c Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Tue, 21 Oct 2025 14:24:32 +0200 Subject: [PATCH 03/11] Add dictionary record --- .../20251021114546_reindex_labels_in_work_items.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 ee/elastic/docs/20251021114546_reindex_labels_in_work_items.yml diff --git a/ee/elastic/docs/20251021114546_reindex_labels_in_work_items.yml b/ee/elastic/docs/20251021114546_reindex_labels_in_work_items.yml new file mode 100644 index 00000000000000..523f7e2c9e41ab --- /dev/null +++ b/ee/elastic/docs/20251021114546_reindex_labels_in_work_items.yml @@ -0,0 +1,10 @@ +--- +name: ReindexLabelsInWorkItems +version: '20251021114546' +description: Reindex work items to ensure label changes trigger Advanced Search updates +group: group::global search +milestone: '18.6' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/209618 +obsolete: false +marked_obsolete_by_url: +marked_obsolete_in_milestone: -- GitLab From 78f30a697216b860cc1e65b9a2b8f92533355679 Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Wed, 22 Oct 2025 14:58:47 +0200 Subject: [PATCH 04/11] Optimize work item label sync migration and improve schema version management * Reduce migration throttle_delay from 1 minute to 15 seconds for faster reindexing * Refactor schema version management to use SCHEMA_VERSIONS hash for better flexibility * Maintain backwards compatibility with dynamic SCHEMA_VERSION constant * Update specs to test new schema version logic properly --- ...20251021114546_reindex_labels_in_work_items.rb | 2 +- ee/lib/search/elastic/references/work_item.rb | 15 ++++++++++----- .../search/elastic/references/work_item_spec.rb | 8 ++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb b/ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb index b3daec83ca1bc2..b5d6806972c78f 100644 --- a/ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb +++ b/ee/elastic/migrate/20251021114546_reindex_labels_in_work_items.rb @@ -5,7 +5,7 @@ class ReindexLabelsInWorkItems < Elastic::Migration batched! batch_size 10_000 - throttle_delay 1.minute + throttle_delay 15.seconds DOCUMENT_TYPE = WorkItem NEW_SCHEMA_VERSION = 25_43 diff --git a/ee/lib/search/elastic/references/work_item.rb b/ee/lib/search/elastic/references/work_item.rb index 9ebe51de6fc9e9..be36291b1a81b9 100644 --- a/ee/lib/search/elastic/references/work_item.rb +++ b/ee/lib/search/elastic/references/work_item.rb @@ -11,7 +11,14 @@ class WorkItem < Reference # Only applies after migration completes to prevent indexing documents # with new schema before data migration finishes, which would result in # documents not being indexed properly and missing data. - SCHEMA_VERSION = 25_43 + SCHEMA_VERSIONS = { + 25_43 => :index_work_items_milestone_state, + 25_27 => nil + }.freeze + + # Backwards compatibility - returns the latest schema version + SCHEMA_VERSION = SCHEMA_VERSIONS.keys.max + DEFAULT_INDEX_ATTRIBUTES = %i[ id iid @@ -105,10 +112,8 @@ def initialize(identifier, routing) end def schema_version - if ::Elastic::DataMigrationService.migration_has_finished?(:index_work_items_milestone_state) - SCHEMA_VERSION - else - 25_27 # Previous stable version until migration completes + SCHEMA_VERSIONS.sort.reverse_each do |version, migration| + break version if migration.nil? || ::Elastic::DataMigrationService.migration_has_finished?(migration) end end diff --git a/ee/spec/lib/search/elastic/references/work_item_spec.rb b/ee/spec/lib/search/elastic/references/work_item_spec.rb index 1e5d2968fcaf1f..1a6ceb51c39fd9 100644 --- a/ee/spec/lib/search/elastic/references/work_item_spec.rb +++ b/ee/spec/lib/search/elastic/references/work_item_spec.rb @@ -195,13 +195,13 @@ set_elasticsearch_migration_to(:index_work_items_milestone_state, including: false) end - it 'returns the current schema version' do - expect(work_item_reference.schema_version).to eq(2527) + it 'returns the baseline schema version' do + expect(work_item_reference.schema_version).to eq(25_27) end end - context 'when there are finished migrations' do - it 'returns the new schema version for that migration' do + context 'when index_work_items_milestone_state migration is finished' do + it 'returns the latest schema version' do expect(work_item_reference.schema_version).to eq(described_class::SCHEMA_VERSION) end end -- GitLab From 37d3ebe56651080ffe1210986bffeceb2cbc5eaf Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Wed, 22 Oct 2025 15:04:43 +0200 Subject: [PATCH 05/11] Add comprehensive schema version testing and fix RuboCop violations * Add extensive test coverage for SCHEMA_VERSIONS hash logic with multiple scenarios * Test migration state combinations, edge cases, and fallback behavior * Validate backwards compatibility with SCHEMA_VERSION constant * Fix line length violations by breaking long method chains * Ensure schema version selection works correctly across different migration states --- .../elastic/references/work_item_spec.rb | 99 ++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/ee/spec/lib/search/elastic/references/work_item_spec.rb b/ee/spec/lib/search/elastic/references/work_item_spec.rb index 1a6ceb51c39fd9..3581fc511d545e 100644 --- a/ee/spec/lib/search/elastic/references/work_item_spec.rb +++ b/ee/spec/lib/search/elastic/references/work_item_spec.rb @@ -202,7 +202,104 @@ context 'when index_work_items_milestone_state migration is finished' do it 'returns the latest schema version' do - expect(work_item_reference.schema_version).to eq(described_class::SCHEMA_VERSION) + expect(work_item_reference.schema_version).to eq(25_43) + end + end + + context 'with custom SCHEMA_VERSIONS scenarios' do + let(:original_schema_versions) { described_class::SCHEMA_VERSIONS } + + after do + stub_const("#{described_class}::SCHEMA_VERSIONS", original_schema_versions) + end + + context 'with multiple migrations in different states' do + let(:custom_schema_versions) do + { + 25_50 => :future_migration, + 25_43 => :index_work_items_milestone_state, + 25_30 => :intermediate_migration, + 25_27 => nil + } + end + + before do + stub_const("#{described_class}::SCHEMA_VERSIONS", custom_schema_versions) + # Simulate different migration states + allow(::Elastic::DataMigrationService).to receive(:migration_has_finished?).and_return(false) + allow(::Elastic::DataMigrationService).to receive(:migration_has_finished?) + .with(:index_work_items_milestone_state).and_return(true) + allow(::Elastic::DataMigrationService).to receive(:migration_has_finished?) + .with(:intermediate_migration).and_return(true) + end + + it 'returns the highest version with finished or nil migration' do + expect(work_item_reference.schema_version).to eq(25_43) + end + + it 'dynamically updates SCHEMA_VERSION constant' do + # The constant should reflect the new maximum + stub_const("#{described_class}::SCHEMA_VERSION", custom_schema_versions.keys.max) + expect(described_class::SCHEMA_VERSION).to eq(25_50) + end + end + + context 'when only baseline migration (nil) is available' do + let(:custom_schema_versions) do + { + 25_50 => :future_migration, + 25_27 => nil + } + end + + before do + stub_const("#{described_class}::SCHEMA_VERSIONS", custom_schema_versions) + allow(::Elastic::DataMigrationService).to receive(:migration_has_finished?).and_return(false) + end + + it 'falls back to baseline version' do + expect(work_item_reference.schema_version).to eq(25_27) + end + end + + context 'with intermediate migration finished but not the latest' do + let(:custom_schema_versions) do + { + 25_50 => :future_migration, + 25_40 => :intermediate_migration, + 25_27 => nil + } + end + + before do + stub_const("#{described_class}::SCHEMA_VERSIONS", custom_schema_versions) + allow(::Elastic::DataMigrationService).to receive(:migration_has_finished?).and_return(false) + allow(::Elastic::DataMigrationService).to receive(:migration_has_finished?) + .with(:intermediate_migration).and_return(true) + end + + it 'returns the highest finished migration version' do + expect(work_item_reference.schema_version).to eq(25_40) + end + end + + context 'when all migrations are finished' do + let(:custom_schema_versions) do + { + 25_50 => :latest_migration, + 25_43 => :index_work_items_milestone_state, + 25_27 => nil + } + end + + before do + stub_const("#{described_class}::SCHEMA_VERSIONS", custom_schema_versions) + allow(::Elastic::DataMigrationService).to receive(:migration_has_finished?).and_return(true) + end + + it 'returns the highest schema version' do + expect(work_item_reference.schema_version).to eq(25_50) + end end end end -- GitLab From 4c6804782d23b6711d03f01cf9fdac0107b178ff Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Wed, 22 Oct 2025 15:06:03 +0200 Subject: [PATCH 06/11] Update documentation for schema version management refactor * Clarify SCHEMA_VERSIONS hash purpose and usage * Explain dynamic migration-based version selection logic * Update SCHEMA_VERSION constant documentation for backwards compatibility * Remove outdated comments that don't reflect new hash-based approach --- ee/lib/search/elastic/references/work_item.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ee/lib/search/elastic/references/work_item.rb b/ee/lib/search/elastic/references/work_item.rb index be36291b1a81b9..f0422b635c1652 100644 --- a/ee/lib/search/elastic/references/work_item.rb +++ b/ee/lib/search/elastic/references/work_item.rb @@ -6,17 +6,18 @@ module References class WorkItem < Reference include Search::Elastic::Concerns::DatabaseReference - # Latest schema version - format is Date.today.strftime('%y_%w') - # Update this when changing the document schema. - # Only applies after migration completes to prevent indexing documents - # with new schema before data migration finishes, which would result in - # documents not being indexed properly and missing data. + # Schema version mappings - format is Date.today.strftime('%y_%w') + # Maps schema versions to their corresponding migration names. + # Set migration to nil for baseline versions that don't require migration completion. + # The schema_version method dynamically selects the highest version whose migration + # has completed (or nil), preventing indexing with incomplete schema changes. SCHEMA_VERSIONS = { 25_43 => :index_work_items_milestone_state, 25_27 => nil }.freeze - # Backwards compatibility - returns the latest schema version + # Backwards compatibility constant - returns the latest schema version available. + # This maintains compatibility with existing code that references SCHEMA_VERSION. SCHEMA_VERSION = SCHEMA_VERSIONS.keys.max DEFAULT_INDEX_ATTRIBUTES = %i[ -- GitLab From d4861e36648176fc4521435421a9efc305bd1c13 Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Wed, 22 Oct 2025 15:08:50 +0200 Subject: [PATCH 07/11] Add comprehensive schema version invariant validation tests * Ensure nil migration is always the lowest schema version (baseline requirement) * Validate schema versions follow ascending chronological order * Verify schema version format follows YY_WW pattern consistently * Test that migration-dependent versions reflect proper deployment sequence * Make SCHEMA_VERSION constant test dynamic instead of hardcoded * Remove redundant hardcoded mapping test that provided no value These tests protect critical invariants and prevent misconfiguration of the schema version management system. --- .../elastic/references/work_item_spec.rb | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/ee/spec/lib/search/elastic/references/work_item_spec.rb b/ee/spec/lib/search/elastic/references/work_item_spec.rb index 3581fc511d545e..7f73c3e3874b65 100644 --- a/ee/spec/lib/search/elastic/references/work_item_spec.rb +++ b/ee/spec/lib/search/elastic/references/work_item_spec.rb @@ -206,6 +206,45 @@ end end + context 'with SCHEMA_VERSIONS hash validation' do + it 'SCHEMA_VERSION constant returns the maximum version dynamically' do + max_version = described_class::SCHEMA_VERSIONS.keys.max + expect(described_class::SCHEMA_VERSION).to eq(max_version) + end + + it 'ensures nil migration is always the lowest schema version' do + nil_versions = described_class::SCHEMA_VERSIONS.select { |_version, migration| migration.nil? } + expect(nil_versions).not_to be_empty, 'SCHEMA_VERSIONS should have at least one nil migration for baseline' + + lowest_version = described_class::SCHEMA_VERSIONS.keys.min + expect(described_class::SCHEMA_VERSIONS[lowest_version]).to be_nil, + 'The lowest schema version should always have nil migration (baseline)' + end + + it 'ensures schema versions are in ascending chronological order' do + versions = described_class::SCHEMA_VERSIONS.keys + expect(versions.sort).to eq(versions.sort.reverse.reverse), + 'Schema versions should represent chronological progression' + end + + it 'validates schema version format follows expected pattern' do + described_class::SCHEMA_VERSIONS.each_key do |version| + expect(version).to be_a(Integer), "Schema version #{version} should be an integer" + expect(version.to_s).to match(/\A\d{2}_\d{2}\z/), + "Schema version #{version} should follow YY_WW format (e.g., 25_43)" + end + end + + it 'ensures higher schema versions correspond to later migrations' do + sorted_versions = described_class::SCHEMA_VERSIONS.keys.sort + # Skip the baseline version (nil migration) + migration_versions = sorted_versions.reject { |v| described_class::SCHEMA_VERSIONS[v].nil? } + + expect(migration_versions).to eq(migration_versions.sort), + 'Migration-dependent versions should be in ascending order to reflect deployment sequence' + end + end + context 'with custom SCHEMA_VERSIONS scenarios' do let(:original_schema_versions) { described_class::SCHEMA_VERSIONS } -- GitLab From 587ea294223011b43452ad29d7e0cd0e827a76a7 Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Wed, 22 Oct 2025 15:14:33 +0200 Subject: [PATCH 08/11] Improve schema version validation using DataMigrationService * Remove redundant tests (chronological order, format validation) * Add proper migration order validation using DataMigrationService * Ensure schema versions align with actual migration chronological order * Validate that newer migrations get higher schema versions * Use authoritative migration data instead of filename parsing * Provide clear error messages when ordering constraints are violated This prevents configuration errors where schema versions don't match the actual deployment sequence of elastic search migrations. --- .../elastic/references/work_item_spec.rb | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/ee/spec/lib/search/elastic/references/work_item_spec.rb b/ee/spec/lib/search/elastic/references/work_item_spec.rb index 7f73c3e3874b65..1786d9d1510fe8 100644 --- a/ee/spec/lib/search/elastic/references/work_item_spec.rb +++ b/ee/spec/lib/search/elastic/references/work_item_spec.rb @@ -221,27 +221,32 @@ 'The lowest schema version should always have nil migration (baseline)' end - it 'ensures schema versions are in ascending chronological order' do - versions = described_class::SCHEMA_VERSIONS.keys - expect(versions.sort).to eq(versions.sort.reverse.reverse), - 'Schema versions should represent chronological progression' - end - - it 'validates schema version format follows expected pattern' do - described_class::SCHEMA_VERSIONS.each_key do |version| - expect(version).to be_a(Integer), "Schema version #{version} should be an integer" - expect(version.to_s).to match(/\A\d{2}_\d{2}\z/), - "Schema version #{version} should follow YY_WW format (e.g., 25_43)" + it 'ensures schema versions align with migration chronological order' do + # Get migrations with schema versions, excluding baseline (nil) + versioned_migrations = described_class::SCHEMA_VERSIONS.reject { |_version, migration| migration.nil? } + + # Skip if we don't have multiple migrations to validate ordering + next if versioned_migrations.size < 2 + + # Build mapping of migration names to their actual migration versions from DataMigrationService + migration_versions = {} + versioned_migrations.each do |schema_version, migration_name| + migration_record = ::Elastic::DataMigrationService.find_by_name(migration_name) + expect(migration_record).not_to be_nil, "Migration '#{migration_name}' not found in DataMigrationService" + migration_versions[schema_version] = migration_record.version end - end - it 'ensures higher schema versions correspond to later migrations' do - sorted_versions = described_class::SCHEMA_VERSIONS.keys.sort - # Skip the baseline version (nil migration) - migration_versions = sorted_versions.reject { |v| described_class::SCHEMA_VERSIONS[v].nil? } + # Sort by schema version and by actual migration version + sorted_by_schema_version = migration_versions.sort_by { |schema_version, _migration_version| schema_version } + sorted_by_migration_version = migration_versions.sort_by do |_schema_version, migration_version| + migration_version + end - expect(migration_versions).to eq(migration_versions.sort), - 'Migration-dependent versions should be in ascending order to reflect deployment sequence' + # The order should be consistent - newer migrations should have higher schema versions + expect(sorted_by_schema_version.map(&:first)).to eq(sorted_by_migration_version.map(&:first)), + 'Schema versions must increase with migration chronological order. ' \ + "Schema order: #{sorted_by_schema_version.map { |s, m| "#{s}(migration:#{m})" }}, " \ + "Migration order: #{sorted_by_migration_version.map { |s, m| "#{s}(migration:#{m})" }}" end end -- GitLab From 52d8427f5cf7195c70d02345effb9b392e38ea3f Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Wed, 22 Oct 2025 15:32:35 +0200 Subject: [PATCH 09/11] Update migration spec file --- .../migrate/20251021114546_reindex_labels_in_work_items_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb b/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb index 74942301d4699c..256ba7946f332f 100644 --- a/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb +++ b/ee/spec/elastic/migrate/20251021114546_reindex_labels_in_work_items_spec.rb @@ -7,7 +7,7 @@ describe 'migration', :elastic_delete_by_query, :sidekiq_inline do include_examples 'migration reindex based on schema_version' do let(:version) { 20251021114546 } - let(:expected_throttle_delay) { 1.minute } + let(:expected_throttle_delay) { 15.seconds } let(:expected_batch_size) { 10_000 } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } -- GitLab From 25bc419f54aa39e72a4123308d999150dc876ae0 Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Wed, 22 Oct 2025 16:40:24 +0200 Subject: [PATCH 10/11] Remove redundant comments --- ee/spec/lib/search/elastic/references/work_item_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ee/spec/lib/search/elastic/references/work_item_spec.rb b/ee/spec/lib/search/elastic/references/work_item_spec.rb index 1786d9d1510fe8..6ae24ac6520db2 100644 --- a/ee/spec/lib/search/elastic/references/work_item_spec.rb +++ b/ee/spec/lib/search/elastic/references/work_item_spec.rb @@ -222,13 +222,10 @@ end it 'ensures schema versions align with migration chronological order' do - # Get migrations with schema versions, excluding baseline (nil) versioned_migrations = described_class::SCHEMA_VERSIONS.reject { |_version, migration| migration.nil? } - # Skip if we don't have multiple migrations to validate ordering next if versioned_migrations.size < 2 - # Build mapping of migration names to their actual migration versions from DataMigrationService migration_versions = {} versioned_migrations.each do |schema_version, migration_name| migration_record = ::Elastic::DataMigrationService.find_by_name(migration_name) @@ -236,13 +233,11 @@ migration_versions[schema_version] = migration_record.version end - # Sort by schema version and by actual migration version sorted_by_schema_version = migration_versions.sort_by { |schema_version, _migration_version| schema_version } sorted_by_migration_version = migration_versions.sort_by do |_schema_version, migration_version| migration_version end - # The order should be consistent - newer migrations should have higher schema versions expect(sorted_by_schema_version.map(&:first)).to eq(sorted_by_migration_version.map(&:first)), 'Schema versions must increase with migration chronological order. ' \ "Schema order: #{sorted_by_schema_version.map { |s, m| "#{s}(migration:#{m})" }}, " \ -- GitLab From 54e178fadb38a5b26b532c382f8ca4ce2ea3e371 Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd Date: Wed, 22 Oct 2025 17:21:57 +0200 Subject: [PATCH 11/11] Fix the migration reindex spec --- .../migration_reindex_based_on_schema_version_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ee/spec/workers/concerns/search/elastic/migration_reindex_based_on_schema_version_spec.rb b/ee/spec/workers/concerns/search/elastic/migration_reindex_based_on_schema_version_spec.rb index 768c29fbe600bd..61f7279fdd4318 100644 --- a/ee/spec/workers/concerns/search/elastic/migration_reindex_based_on_schema_version_spec.rb +++ b/ee/spec/workers/concerns/search/elastic/migration_reindex_based_on_schema_version_spec.rb @@ -562,7 +562,14 @@ context 'when records exist with old schema' do before do # make sure new indexed records get the correct schema_version + # We need to stub both SCHEMA_VERSIONS hash and the derived SCHEMA_VERSION constant + new_schema_versions = ::Search::Elastic::References::WorkItem::SCHEMA_VERSIONS.dup + new_schema_versions[current_schema_version + 1] = :test_migration + stub_const('Search::Elastic::References::WorkItem::SCHEMA_VERSIONS', new_schema_versions) stub_const('Search::Elastic::References::WorkItem::SCHEMA_VERSION', current_schema_version + 1) + + allow(::Elastic::DataMigrationService).to receive(:migration_has_finished?) + .with(:test_migration).and_return(true) end context 'when using search API' do -- GitLab