diff --git a/app/events/projects/project_deleted_event.rb b/app/events/projects/project_deleted_event.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac58c5c6755d53231d9685b943d545912ad73075
--- /dev/null
+++ b/app/events/projects/project_deleted_event.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Projects
+  class ProjectDeletedEvent < ::Gitlab::EventStore::Event
+    def schema
+      {
+        'type' => 'object',
+        'properties' => {
+          'project_id' => { 'type' => 'integer' },
+          'namespace_id' => { 'type' => 'integer' }
+        },
+        'required' => %w[project_id namespace_id]
+      }
+    end
+  end
+end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 733a4b45cb2d9c06d4313bf5a48b590311e7ece6..95af5a6863ffe5f880621d0136e7f4628eda62ba 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -37,6 +37,8 @@ def execute
       system_hook_service.execute_hooks_for(project, :destroy)
       log_info("Project \"#{project.full_path}\" was deleted")
 
+      publish_project_deleted_event_for(project) if Feature.enabled?(:publish_project_deleted_event, default_enabled: :yaml)
+
       current_user.invalidate_personal_projects_count
 
       true
@@ -260,6 +262,12 @@ def raise_error(message)
     def flush_caches(project)
       Projects::ForksCountService.new(project).delete_cache
     end
+
+    def publish_project_deleted_event_for(project)
+      data = { project_id: project.id, namespace_id: project.namespace_id }
+      event = Projects::ProjectDeletedEvent.new(data: data)
+      Gitlab::EventStore.publish(event)
+    end
   end
 end
 
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 18646e5320b582c16128ebfe485f19f31c7744a6..f39aee14a69a4c0c4fb311e2c83798430edd0fdd 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -2569,6 +2569,15 @@
   :weight: 1
   :idempotent: true
   :tags: []
+- :name: namespaces_update_root_statistics
+  :worker_name: Namespaces::UpdateRootStatisticsWorker
+  :feature_category: :source_code_management
+  :has_external_dependencies:
+  :urgency: :low
+  :resource_boundary: :unknown
+  :weight: 1
+  :idempotent: true
+  :tags: []
 - :name: new_issue
   :worker_name: NewIssueWorker
   :feature_category: :team_planning
diff --git a/app/workers/namespaces/update_root_statistics_worker.rb b/app/workers/namespaces/update_root_statistics_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9fdf8e2506b42e8c72bc70be16208d9b4c3b5fc8
--- /dev/null
+++ b/app/workers/namespaces/update_root_statistics_worker.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Namespaces
+  class UpdateRootStatisticsWorker
+    include Gitlab::EventStore::Subscriber
+
+    data_consistency :always
+
+    idempotent!
+
+    feature_category :source_code_management
+
+    def handle_event(event)
+      ScheduleAggregationWorker.perform_async(event.data[:namespace_id])
+    end
+  end
+end
diff --git a/config/feature_flags/development/publish_project_deleted_event.yml b/config/feature_flags/development/publish_project_deleted_event.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1287ebe9f662a7719921465d41f94928c5bea474
--- /dev/null
+++ b/config/feature_flags/development/publish_project_deleted_event.yml
@@ -0,0 +1,8 @@
+---
+name: publish_project_deleted_event
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78862
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351073
+milestone: '14.8'
+type: development
+group: group::pipeline insights
+default_enabled: false
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index d52659d80e76636d7d4fc3c62a419db94624d69e..9c5294b6b2b80c69a6e32dd5c30110cacf33b5bb 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -295,6 +295,8 @@
   - 1
 - - namespaces_sync_namespace_name
   - 1
+- - namespaces_update_root_statistics
+  - 1
 - - new_epic
   - 2
 - - new_issue
diff --git a/lib/gitlab/event_store.rb b/lib/gitlab/event_store.rb
index 7dbbcdbb1a78d95b94e5b1f8bf6f1320ebd54500..e20ea1c736500177edc1e660b1a4717ed96f7d67 100644
--- a/lib/gitlab/event_store.rb
+++ b/lib/gitlab/event_store.rb
@@ -34,6 +34,7 @@ def self.configure!(store)
       # Add subscriptions here:
 
       store.subscribe ::MergeRequests::UpdateHeadPipelineWorker, to: ::Ci::PipelineCreatedEvent
+      store.subscribe ::Namespaces::UpdateRootStatisticsWorker, to: ::Projects::ProjectDeletedEvent
     end
     private_class_method :configure!
   end
diff --git a/spec/events/projects/project_deleted_event_spec.rb b/spec/events/projects/project_deleted_event_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd8cec7271b5e0be916ba94d4fc14886785a324a
--- /dev/null
+++ b/spec/events/projects/project_deleted_event_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ProjectDeletedEvent do
+  where(:data, :valid) do
+    [
+      [{ project_id: 1, namespace_id: 2 }, true],
+      [{ project_id: 1 }, false],
+      [{ namespace_id: 1 }, false],
+      [{ project_id: 'foo', namespace_id: 2 }, false],
+      [{ project_id: 1, namespace_id: 'foo' }, false],
+      [{ project_id: [], namespace_id: 2 }, false],
+      [{ project_id: 1, namespace_id: [] }, false],
+      [{ project_id: {}, namespace_id: 2 }, false],
+      [{ project_id: 1, namespace_id: {} }, false],
+      ['foo', false],
+      [123, false],
+      [[], false]
+    ]
+  end
+
+  with_them do
+    it 'validates data' do
+      constructor = -> { described_class.new(data: data) }
+
+      if valid
+        expect { constructor.call }.not_to raise_error
+      else
+        expect { constructor.call }.to raise_error(Gitlab::EventStore::InvalidEvent)
+      end
+    end
+  end
+end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index a6aa76c7e6ceab830d5dd4c5c2f8263d749f4e02..0beb5157ed6b6127006302a001b168abf575e5ab 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -18,17 +18,35 @@
   end
 
   shared_examples 'deleting the project' do
-    before do
-      # Run sidekiq immediately to check that renamed repository will be removed
+    it 'deletes the project', :sidekiq_inline do
       destroy_project(project, user, {})
-    end
 
-    it 'deletes the project', :sidekiq_inline do
       expect(Project.unscoped.all).not_to include(project)
 
       expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
       expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
     end
+
+    it 'publishes a ProjectDeleted event with project id and namespace id' do
+      expected_data = { project_id: project.id, namespace_id: project.namespace_id }
+      expect(Gitlab::EventStore)
+        .to receive(:publish)
+        .with(event_type(Projects::ProjectDeletedEvent).containing(expected_data))
+
+      destroy_project(project, user, {})
+    end
+
+    context 'when feature flag publish_project_deleted_event is disabled' do
+      before do
+        stub_feature_flags(publish_project_deleted_event: false)
+      end
+
+      it 'does not publish an event' do
+        expect(Gitlab::EventStore).not_to receive(:publish)
+
+        destroy_project(project, user, {})
+      end
+    end
   end
 
   shared_examples 'deleting the project with pipeline and build' do
diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb
new file mode 100644
index 0000000000000000000000000000000000000000..96a71ae3c22fc3e7bbfad4e148aea66b4536cde3
--- /dev/null
+++ b/spec/support/matchers/event_store.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :event_type do |event_class|
+  match do |actual|
+    actual.instance_of?(event_class) &&
+      actual.data == @expected_data
+  end
+
+  chain :containing do |expected_data|
+    @expected_data = expected_data
+  end
+end
diff --git a/spec/tooling/quality/test_level_spec.rb b/spec/tooling/quality/test_level_spec.rb
index 8a944a473d73545d0457f2b424b050107005f2c7..33d3a5b49b319d1b7c038ba36a10d366abf5d968 100644
--- a/spec/tooling/quality/test_level_spec.rb
+++ b/spec/tooling/quality/test_level_spec.rb
@@ -28,7 +28,7 @@
     context 'when level is unit' do
       it 'returns a pattern' do
         expect(subject.pattern(:unit))
-          .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
+          .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,events,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
       end
     end
 
@@ -110,7 +110,7 @@
     context 'when level is unit' do
       it 'returns a regexp' do
         expect(subject.regexp(:unit))
-          .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
+          .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|events|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
       end
     end
 
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index a21cf3ed5f65072467850f62cf3b49180042db17..1cd5d23d8fc90f239a10bf3e57ad09c56a666e1a 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -349,6 +349,7 @@
         'Namespaces::OnboardingPipelineCreatedWorker' => 3,
         'Namespaces::OnboardingProgressWorker' => 3,
         'Namespaces::OnboardingUserAddedWorker' => 3,
+        'Namespaces::RefreshRootStatisticsWorker' => 3,
         'Namespaces::RootStatisticsWorker' => 3,
         'Namespaces::ScheduleAggregationWorker' => 3,
         'NetworkPolicyMetricsWorker' => 3,
diff --git a/spec/workers/namespaces/update_root_statistics_worker_spec.rb b/spec/workers/namespaces/update_root_statistics_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a525904b7578748e4c3027aa13e4297bfe0f44af
--- /dev/null
+++ b/spec/workers/namespaces/update_root_statistics_worker_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Namespaces::UpdateRootStatisticsWorker do
+  let(:namespace_id) { 123 }
+
+  let(:event) do
+    Projects::ProjectDeletedEvent.new(data: { project_id: 1, namespace_id: namespace_id })
+  end
+
+  subject { consume_event(event) }
+
+  def consume_event(event)
+    described_class.new.perform(event.class.name, event.data)
+  end
+
+  it 'enqueues ScheduleAggregationWorker' do
+    expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async).with(namespace_id)
+
+    subject
+  end
+end
diff --git a/tooling/quality/test_level.rb b/tooling/quality/test_level.rb
index 50cbc69beb214b37de9da76c36869794bcdee3aa..624564ecd059967cb0ae22e67c36f5d942ffa323 100644
--- a/tooling/quality/test_level.rb
+++ b/tooling/quality/test_level.rb
@@ -24,6 +24,7 @@ class TestLevel
         elastic
         elastic_integration
         experiments
+        events
         factories
         finders
         frontend