diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index e9d6cfe00b28a030c2416aa88fd3e47f5eb845db..a144bf70a86240cc3bbc024bae6f48d0c3279b02 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -216,3 +216,13 @@ bundle exec rake routes
 
 Since these take some time to create, it's often helpful to save the output to
 a file for quick reference.
+
+## Show obsolete `ignored_columns`
+
+To see a list of all obsolete `ignored_columns` run:
+
+```
+bundle exec rake db:obsolete_ignored_columns
+```
+
+Feel free to remove their definitions from their `ignored_columns` definitions.
diff --git a/lib/gitlab/database/obsolete_ignored_columns.rb b/lib/gitlab/database/obsolete_ignored_columns.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6266b6a4b65577663596146e815ce6122919dbf5
--- /dev/null
+++ b/lib/gitlab/database/obsolete_ignored_columns.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Database
+    # Checks which `ignored_columns` can be safely removed by scanning
+    # the current schema for all `ApplicationRecord` descendants.
+    class ObsoleteIgnoredColumns
+      def initialize(base = ApplicationRecord)
+        @base = base
+      end
+
+      def execute
+        @base.descendants.map do |klass|
+          next if klass.abstract_class?
+
+          safe_to_remove = ignored_columns_safe_to_remove_for(klass)
+          next if safe_to_remove.empty?
+
+          [klass.name, safe_to_remove]
+        end.compact.sort_by(&:first)
+      end
+
+      private
+
+      def ignored_columns_safe_to_remove_for(klass)
+        ignored = klass.ignored_columns.map(&:to_s)
+
+        return [] if ignored.empty?
+
+        schema = klass.connection.schema_cache.columns_hash(klass.table_name)
+        existing = schema.values.map(&:name)
+
+        used = ignored & existing
+        ignored - used
+      end
+    end
+  end
+end
diff --git a/lib/tasks/db_obsolete_ignored_columns.rake b/lib/tasks/db_obsolete_ignored_columns.rake
new file mode 100644
index 0000000000000000000000000000000000000000..184e407f28cdb6da804961cd9bb989069136f545
--- /dev/null
+++ b/lib/tasks/db_obsolete_ignored_columns.rake
@@ -0,0 +1,21 @@
+desc 'Show a list of obsolete `ignored_columns`'
+task 'db:obsolete_ignored_columns' => :environment do
+  list = Gitlab::Database::ObsoleteIgnoredColumns.new.execute
+
+  if list.empty?
+    puts 'No obsolete `ignored_columns` found.'
+  else
+    puts 'The following `ignored_columns` are obsolete and can be removed:'
+
+    list.each do |name, ignored_columns|
+      puts "- #{name}: #{ignored_columns.join(', ')}"
+    end
+
+    puts <<~TEXT
+
+      WARNING: Removing columns is tricky because running GitLab processes may still be using the columns.
+
+      See also https://docs.gitlab.com/ee/development/what_requires_downtime.html#dropping-columns
+    TEXT
+  end
+end
diff --git a/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb b/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6d38f7f1b95b58da083702d35f2caafa423b3bcc
--- /dev/null
+++ b/spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Database::ObsoleteIgnoredColumns do
+  module Testing
+    class MyBase < ApplicationRecord
+    end
+
+    class SomeAbstract < MyBase
+      self.abstract_class = true
+
+      self.table_name = 'projects'
+
+      self.ignored_columns += %i[unused]
+    end
+
+    class B < MyBase
+      self.table_name = 'issues'
+
+      self.ignored_columns += %i[id other]
+    end
+
+    class A < SomeAbstract
+      self.ignored_columns += %i[id also_unused]
+    end
+
+    class C < MyBase
+      self.table_name = 'users'
+    end
+  end
+
+  subject { described_class.new(Testing::MyBase) }
+
+  describe '#execute' do
+    it 'returns a list of class names and columns pairs' do
+      expect(subject.execute).to eq([
+        ['Testing::A', %w(unused also_unused)],
+        ['Testing::B', %w(other)]
+      ])
+    end
+  end
+end