diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 68aadf577a0af4ae5acc96f5db5cbef0064cd067..a3213a59beda95c29444be3838de352e32ea1830 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -206,11 +206,6 @@ def self.last_deployment_group_for_environment(env)
     end
   end
 
-  def self.distinct_on_environment
-    order('environment_id, deployments.id DESC')
-      .select('DISTINCT ON (environment_id) deployments.*')
-  end
-
   def self.find_successful_deployment!(iid)
     success.find_by!(iid: iid)
   end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 02bdbd86cd13c7daf648703d326cd0d82c050570..1950431446b5b7ffc66398db3f351b601e2157d9 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -26,12 +26,11 @@ class Environment < ApplicationRecord
   has_many :self_managed_prometheus_alert_events, inverse_of: :environment
   has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :environment
 
+  # NOTE: If you preload multiple last deployments of environments, use Preloaders::Environments::DeploymentPreloader.
   has_one :last_deployment, -> { success.ordered }, class_name: 'Deployment', inverse_of: :environment
-  has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment'
-  has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus', disable_joins: true
-  has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline', disable_joins: true
+  has_one :last_visible_deployment, -> { visible.order(id: :desc) }, inverse_of: :environment, class_name: 'Deployment'
 
-  has_one :upcoming_deployment, -> { upcoming.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment
+  has_one :upcoming_deployment, -> { upcoming.order(id: :desc) }, class_name: 'Deployment', inverse_of: :environment
   has_one :latest_opened_most_severe_alert, -> { order_severity_with_open_prometheus_alert }, class_name: 'AlertManagement::Alert', inverse_of: :environment
 
   before_validation :generate_slug, if: ->(env) { env.slug.blank? }
@@ -216,28 +215,11 @@ def legacy_last_deployment_group
       deployable_id: last_deployment_pipeline.latest_builds.pluck(:id))
   end
 
-  # NOTE: Below assocation overrides is a workaround for issue https://gitlab.com/gitlab-org/gitlab/-/issues/339908
-  # It helps to avoid cross joins with the CI database.
-  # Caveat: It also overrides and losses the default AR caching mechanism.
-  # Read - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68870#note_677227727
-
-  # NOTE: Association Preloads does not use the overriden definitions below.
-  # Association Preloads when preloading uses the original definitions from the relationships above.
-  # https://github.com/rails/rails/blob/75ac626c4e21129d8296d4206a1960563cc3d4aa/activerecord/lib/active_record/associations/preloader.rb#L158
-  # But after preloading, when they are called it is using the overriden methods below.
-  # So we are checking for `association_cached?(:association_name)` in the overridden methods and calling `super` which inturn fetches the preloaded values.
-
-  # Overriding association
   def last_visible_deployable
-    return super if association_cached?(:last_visible_deployable)
-
     last_visible_deployment&.deployable
   end
 
-  # Overriding association
   def last_visible_pipeline
-    return super if association_cached?(:last_visible_pipeline)
-
     last_visible_deployable&.pipeline
   end
 
diff --git a/ee/app/serializers/dashboard_environments_serializer.rb b/ee/app/serializers/dashboard_environments_serializer.rb
index b101af4965548a18ac9351e2e1c2765e4c035eaf..843d92582d01a7b91f62532cbbe290abfe8ea749 100644
--- a/ee/app/serializers/dashboard_environments_serializer.rb
+++ b/ee/app/serializers/dashboard_environments_serializer.rb
@@ -18,24 +18,43 @@ def batch_load(projects)
     ActiveRecord::Associations::Preloader.new.preload(projects, [
       :route,
       environments_for_dashboard: [
-        last_visible_pipeline: [
-          :user,
-          project: [:route, :group, :project_feature, namespace: :route]
-        ],
-        last_visible_deployment: [
-          deployable: [
-            :metadata,
-            :pipeline,
-            project: [:project_feature, :group, :route, namespace: :route]
-          ],
-          project: [:route, namespace: :route]
-        ],
         project: [:project_feature, :group, namespace: :route]
       ],
       namespace: [:route, :owner]
     ])
 
+    environments = projects.map(&:environments_for_dashboard).flatten
+
+    Preloaders::Environments::DeploymentPreloader.new(environments)
+      .execute_with_union(:last_visible_deployment, deployment_associations)
+
     projects
   end
   # rubocop: enable CodeReuse/ActiveRecord
+
+  def deployment_associations
+    {
+      deployable: {
+        metadata: nil,
+        pipeline: {
+          user: nil,
+          project: project_associations
+        },
+        project: project_associations
+      },
+      project: {
+        route: nil,
+        namespace: :route
+      }
+    }
+  end
+
+  def project_associations
+    {
+      project_feature: nil,
+      group: nil,
+      route: nil,
+      namespace: :route
+    }
+  end
 end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 573e69799246020dc5e15fd7908def73fdb5f8e9..3f4372dafd0ec0f846d85550d66d560e199c9488 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -1025,178 +1025,29 @@
   describe '#last_visible_deployable' do
     subject { environment.last_visible_deployable }
 
-    context 'does not join across databases' do
-      let(:pipeline_a) { create(:ci_pipeline, project: project) }
-      let(:pipeline_b) { create(:ci_pipeline, project: project) }
-      let(:ci_build_a) { create(:ci_build, project: project, pipeline: pipeline_a) }
-      let(:ci_build_b) { create(:ci_build, project: project, pipeline: pipeline_b) }
-
-      before do
-        create(:deployment, :success, project: project, environment: environment, deployable: ci_build_a)
-        create(:deployment, :failed, project: project, environment: environment, deployable: ci_build_b)
-      end
-
-      it 'for direct call' do
-        with_cross_joins_prevented do
-          expect(subject.id).to eq(ci_build_b.id)
-        end
-      end
-
-      it 'for preload' do
-        environment.reload
-
-        with_cross_joins_prevented do
-          ActiveRecord::Associations::Preloader.new.preload(environment, [last_visible_deployable: []])
-          expect(subject.id).to eq(ci_build_b.id)
-        end
-      end
+    let!(:deployment) do
+      create(:deployment, :success, project: project, environment: environment, deployable: deployable)
     end
 
-    context 'call after preload' do
-      it 'fetches from association cache' do
-        pipeline = create(:ci_pipeline, project: project)
-        ci_build = create(:ci_build, project: project, pipeline: pipeline)
-        create(:deployment, :failed, project: project, environment: environment, deployable: ci_build)
-
-        environment.reload
-        ActiveRecord::Associations::Preloader.new.preload(environment, [last_visible_deployable: []])
+    let!(:deployable) { create(:ci_build, :success, project: project) }
 
-        query_count = ActiveRecord::QueryRecorder.new do
-          expect(subject.id).to eq(ci_build.id)
-        end.count
-
-        expect(query_count).to eq(0)
-      end
+    it 'fetches the deployable through the last visible deployment' do
+      is_expected.to eq(deployable)
     end
   end
 
   describe '#last_visible_pipeline' do
-    let(:user) { create(:user) }
-    let_it_be(:project) { create(:project, :repository) }
-
-    let(:environment) { create(:environment, project: project) }
-    let(:commit) { project.commit }
-
-    let(:success_pipeline) do
-      create(:ci_pipeline, :success, project: project, user: user, sha: commit.sha)
-    end
-
-    let(:failed_pipeline) do
-      create(:ci_pipeline, :failed, project: project, user: user, sha: commit.sha)
-    end
+    subject { environment.last_visible_pipeline }
 
-    it 'uses the last deployment even if it failed' do
-      pipeline = create(:ci_pipeline, project: project, user: user, sha: commit.sha)
-      ci_build = create(:ci_build, project: project, pipeline: pipeline)
-      create(:deployment, :failed, project: project, environment: environment, deployable: ci_build, sha: commit.sha)
-
-      last_pipeline = environment.last_visible_pipeline
-
-      expect(last_pipeline).to eq(pipeline)
-    end
-
-    it 'returns nil if there is no deployment' do
-      create(:ci_build, project: project, pipeline: success_pipeline)
-
-      expect(environment.last_visible_pipeline).to be_nil
+    let!(:deployment) do
+      create(:deployment, :success, project: project, environment: environment, deployable: deployable)
     end
 
-    it 'does not return an invisible pipeline' do
-      failed_pipeline = create(:ci_pipeline, project: project, user: user, sha: commit.sha)
-      ci_build_a = create(:ci_build, project: project, pipeline: failed_pipeline)
-      create(:deployment, :failed, project: project, environment: environment, deployable: ci_build_a, sha: commit.sha)
-      pipeline = create(:ci_pipeline, project: project, user: user, sha: commit.sha)
-      ci_build_b = create(:ci_build, project: project, pipeline: pipeline)
-      create(:deployment, :created, project: project, environment: environment, deployable: ci_build_b, sha: commit.sha)
+    let!(:deployable) { create(:ci_build, :success, project: project, pipeline: pipeline) }
+    let!(:pipeline) { create(:ci_pipeline, :success, project: project) }
 
-      last_pipeline = environment.last_visible_pipeline
-
-      expect(last_pipeline).to eq(failed_pipeline)
-    end
-
-    context 'does not join across databases' do
-      let(:pipeline_a) { create(:ci_pipeline, project: project) }
-      let(:pipeline_b) { create(:ci_pipeline, project: project) }
-      let(:ci_build_a) { create(:ci_build, project: project, pipeline: pipeline_a) }
-      let(:ci_build_b) { create(:ci_build, project: project, pipeline: pipeline_b) }
-
-      before do
-        create(:deployment, :success, project: project, environment: environment, deployable: ci_build_a)
-        create(:deployment, :failed, project: project, environment: environment, deployable: ci_build_b)
-      end
-
-      subject { environment.last_visible_pipeline }
-
-      it 'for direct call' do
-        with_cross_joins_prevented do
-          expect(subject.id).to eq(pipeline_b.id)
-        end
-      end
-
-      it 'for preload' do
-        environment.reload
-
-        with_cross_joins_prevented do
-          ActiveRecord::Associations::Preloader.new.preload(environment, [last_visible_pipeline: []])
-          expect(subject.id).to eq(pipeline_b.id)
-        end
-      end
-    end
-
-    context 'for the environment' do
-      it 'returns the last pipeline' do
-        pipeline = create(:ci_pipeline, project: project, user: user, sha: commit.sha)
-        ci_build = create(:ci_build, project: project, pipeline: pipeline)
-        create(:deployment, :success, project: project, environment: environment, deployable: ci_build, sha: commit.sha)
-
-        last_pipeline = environment.last_visible_pipeline
-
-        expect(last_pipeline).to eq(pipeline)
-      end
-
-      context 'with multiple deployments' do
-        it 'returns the last pipeline' do
-          pipeline_a = create(:ci_pipeline, project: project, user: user)
-          pipeline_b = create(:ci_pipeline, project: project, user: user)
-          ci_build_a = create(:ci_build, project: project, pipeline: pipeline_a)
-          ci_build_b = create(:ci_build, project: project, pipeline: pipeline_b)
-          create(:deployment, :success, project: project, environment: environment, deployable: ci_build_a)
-          create(:deployment, :success, project: project, environment: environment, deployable: ci_build_b)
-
-          last_pipeline = environment.last_visible_pipeline
-
-          expect(last_pipeline).to eq(pipeline_b)
-        end
-      end
-
-      context 'with multiple pipelines' do
-        it 'returns the last pipeline' do
-          create(:ci_build, project: project, pipeline: success_pipeline)
-          ci_build_b = create(:ci_build, project: project, pipeline: failed_pipeline)
-          create(:deployment, :failed, project: project, environment: environment, deployable: ci_build_b, sha: commit.sha)
-
-          last_pipeline = environment.last_visible_pipeline
-
-          expect(last_pipeline).to eq(failed_pipeline)
-        end
-      end
-    end
-
-    context 'call after preload' do
-      it 'fetches from association cache' do
-        pipeline = create(:ci_pipeline, project: project)
-        ci_build = create(:ci_build, project: project, pipeline: pipeline)
-        create(:deployment, :failed, project: project, environment: environment, deployable: ci_build)
-
-        environment.reload
-        ActiveRecord::Associations::Preloader.new.preload(environment, [last_visible_pipeline: []])
-
-        query_count = ActiveRecord::QueryRecorder.new do
-          expect(environment.last_visible_pipeline.id).to eq(pipeline.id)
-        end.count
-
-        expect(query_count).to eq(0)
-      end
+    it 'fetches the pipeline through the last visible deployment' do
+      is_expected.to eq(pipeline)
     end
   end