diff --git a/db/post_migrate/20240510134106_add_epics_issue_id_not_null_constraint.rb b/db/post_migrate/20240510134106_add_epics_issue_id_not_null_constraint.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f67a963d23e937e6c3386c3183be3a54a7111be4
--- /dev/null
+++ b/db/post_migrate/20240510134106_add_epics_issue_id_not_null_constraint.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddEpicsIssueIdNotNullConstraint < Gitlab::Database::Migration[2.2]
+  milestone '17.1'
+
+  disable_ddl_transaction!
+
+  def up
+    add_not_null_constraint :epics, :issue_id, validate: false
+  end
+
+  def down
+    remove_not_null_constraint :epics, :issue_id
+  end
+end
diff --git a/db/schema_migrations/20240510134106 b/db/schema_migrations/20240510134106
new file mode 100644
index 0000000000000000000000000000000000000000..82201469ab3771658fc0ca1665c5e5eab8f62309
--- /dev/null
+++ b/db/schema_migrations/20240510134106
@@ -0,0 +1 @@
+8ec5848ee1cd2e3e2f60a383136fcc53917f006e47d84a3ad6871630f83ddc12
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 4e71c36c79ade34439334750e0ea9e65c432b3ee..a4775291a1034c2cf28ed97904959c72fc8d42ab 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -21221,6 +21221,9 @@ ALTER TABLE workspaces
 ALTER TABLE vulnerability_scanners
     ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
 
+ALTER TABLE epics
+    ADD CONSTRAINT check_450724d1bb CHECK ((issue_id IS NOT NULL)) NOT VALID;
+
 ALTER TABLE sprints
     ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID;
 
diff --git a/ee/spec/lib/ee/gitlab/background_migration/backfill_epic_cache_counts_spec.rb b/ee/spec/lib/ee/gitlab/background_migration/backfill_epic_cache_counts_spec.rb
index cf0b2e932fe42451bfd11806e13a2c8f449a0466..2896c4cd22f06462b432855702f3328864419be4 100644
--- a/ee/spec/lib/ee/gitlab/background_migration/backfill_epic_cache_counts_spec.rb
+++ b/ee/spec/lib/ee/gitlab/background_migration/backfill_epic_cache_counts_spec.rb
@@ -2,7 +2,9 @@
 require 'spec_helper'
 
 # rubocop:disable RSpec/MultipleMemoizedHelpers
-RSpec.describe Gitlab::BackgroundMigration::BackfillEpicCacheCounts, :migration do
+# provide schema version before not null constraint is introduced on epics.issue_id
+RSpec.describe Gitlab::BackgroundMigration::BackfillEpicCacheCounts, :migration, schema: 20240508085441,
+  feature_category: :team_planning do
   let(:users) { table(:users) }
   let(:namespaces) { table(:namespaces) }
   let(:projects) { table(:projects) }
diff --git a/ee/spec/lib/ee/gitlab/background_migration/delete_invalid_epic_issues_spec.rb b/ee/spec/lib/ee/gitlab/background_migration/delete_invalid_epic_issues_spec.rb
index 04df69909d1dd86664c362c21625ecde3d733367..187cad0c8dc964e1d76772579eab665935886fe2 100644
--- a/ee/spec/lib/ee/gitlab/background_migration/delete_invalid_epic_issues_spec.rb
+++ b/ee/spec/lib/ee/gitlab/background_migration/delete_invalid_epic_issues_spec.rb
@@ -2,7 +2,9 @@
 
 require 'spec_helper'
 
-RSpec.describe Gitlab::BackgroundMigration::DeleteInvalidEpicIssues do
+# provide schema version before not null constraint is introduced on epics.issue_id
+RSpec.describe Gitlab::BackgroundMigration::DeleteInvalidEpicIssues, schema: 20240508085441,
+  feature_category: :team_planning do
   let!(:issue_base_type_enum) { 0 }
   let!(:issue_type_id) { table(:work_item_types).find_by(base_type: issue_base_type_enum).id }
 
diff --git a/ee/spec/services/ee/work_items/parent_links/reorder_service_spec.rb b/ee/spec/services/ee/work_items/parent_links/reorder_service_spec.rb
index 72251ad39da6a7620c6cabf307d45f170c9bc811..98a79c1b655ee71dcce4c6941c48ac21594f4edc 100644
--- a/ee/spec/services/ee/work_items/parent_links/reorder_service_spec.rb
+++ b/ee/spec/services/ee/work_items/parent_links/reorder_service_spec.rb
@@ -180,16 +180,6 @@
         it_behaves_like 'only changes work item'
       end
 
-      context 'when synced epic for the adjacent work item does not exist' do
-        let(:synced_moving_object) { nil }
-
-        before do
-          top_adjacent.synced_epic.update_columns(issue_id: nil)
-        end
-
-        it_behaves_like 'reorders the hierarchy'
-      end
-
       it_behaves_like 'when saving fails', Epic, expect_error_log: true, expect_error_message: true
       it_behaves_like 'when saving fails', WorkItems::ParentLink
     end
@@ -237,16 +227,6 @@
         it_behaves_like 'reorders the hierarchy'
       end
 
-      context 'when synced epic for the adjacent work item does not exist' do
-        let(:synced_moving_object) { nil }
-
-        before do
-          top_adjacent.synced_epic.update_columns(issue_id: nil)
-        end
-
-        it_behaves_like 'reorders the hierarchy'
-      end
-
       it_behaves_like 'when saving fails', EpicIssue, expect_error_log: true, expect_error_message: true
       it_behaves_like 'when saving fails', WorkItems::ParentLink
     end
diff --git a/spec/lib/gitlab/background_migration/backfill_epic_basic_fields_to_work_item_record_spec.rb b/spec/lib/gitlab/background_migration/backfill_epic_basic_fields_to_work_item_record_spec.rb
index 341312785a916386422259bd6bea41dfd8d04e0a..5c95c4dccc0147b9e1171b3b14da7c1c7d1611b5 100644
--- a/spec/lib/gitlab/background_migration/backfill_epic_basic_fields_to_work_item_record_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_epic_basic_fields_to_work_item_record_spec.rb
@@ -15,14 +15,6 @@
 
   before do
     (1..5).each do |idx|
-      table(:epics).create!(**epic_data(idx, namespace1))
-    end
-
-    (6..10).each do |idx|
-      table(:epics).create!(**epic_data(idx, namespace2))
-    end
-
-    (11..15).each do |idx|
       epic_data = epic_data(idx, namespace1)
       issue_id = issues.create!(
         title: "epic #{idx}", iid: idx, namespace_id: namespace1.id, work_item_type_id: epic_work_item_type_id
@@ -34,7 +26,7 @@
       table(:work_item_colors).create!(color_data) if idx % 3 == 0
     end
 
-    (16..20).each do |idx|
+    (6..10).each do |idx|
       issue_id = issues.create!(
         title: "epic #{idx}", iid: idx, namespace_id: namespace2.id, work_item_type_id: epic_work_item_type_id
       ).id
@@ -64,7 +56,7 @@
       migration.perform
 
       backfilled_epics = epics.where.not(issue_id: nil)
-      expect(backfilled_epics.count).to eq(20)
+      expect(backfilled_epics.count).to eq(10)
       expect(backfilled_epics.map(&:group_id).uniq).to match_array([namespace1.id, namespace2.id])
 
       epic_wis = table(:issues).where(work_item_type_id: epic_work_item_type_id)