diff --git a/Gemfile b/Gemfile
index 886434a89faf446e23983068d0bf33697ef40f50..c2c176e12b2f27ad39ebd661f552efae9b11e75c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -496,7 +496,7 @@ gem 'flipper', '~> 0.21.0'
 gem 'flipper-active_record', '~> 0.21.0'
 gem 'flipper-active_support_cache_store', '~> 0.21.0'
 gem 'unleash', '~> 0.1.5'
-gem 'gitlab-experiment', '~> 0.5.4'
+gem 'gitlab-experiment', '~> 0.6.1'
 
 # Structured logging
 gem 'lograge', '~> 0.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index ac113252f85b41404657ec3ce3384695b4a4c50b..cb9e2342ad388fce438bb31d045bf973115a4b1f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -464,7 +464,7 @@ GEM
       numerizer (~> 0.2)
     gitlab-dangerfiles (2.1.2)
       danger-gitlab
-    gitlab-experiment (0.5.4)
+    gitlab-experiment (0.6.1)
       activesupport (>= 3.0)
       request_store (>= 1.0)
       scientist (~> 1.6, >= 1.6.0)
@@ -1487,7 +1487,7 @@ DEPENDENCIES
   github-markup (~> 1.7.0)
   gitlab-chronic (~> 0.10.5)
   gitlab-dangerfiles (~> 2.1.2)
-  gitlab-experiment (~> 0.5.4)
+  gitlab-experiment (~> 0.6.1)
   gitlab-fog-azure-rm (~> 1.1.1)
   gitlab-fog-google (~> 1.13)
   gitlab-labkit (~> 0.18.0)
diff --git a/app/experiments/application_experiment.rb b/app/experiments/application_experiment.rb
index 53ea8ea2d3ab78998bdad1c976d2b70d06c58214..c777fdc63a1bc2be6a95dcb52b8a844765f495ae 100644
--- a/app/experiments/application_experiment.rb
+++ b/app/experiments/application_experiment.rb
@@ -10,22 +10,28 @@ def enabled?
   end
 
   def publish(_result = nil)
-    return unless should_track? # don't track events for excluded contexts
-
-    record_experiment if @record # record the subject in the database if the context contains a namespace, group, project, actor or user
-
-    track(:assignment) # track that we've assigned a variant for this context
+    super
 
-    push_to_client
+    publish_to_client if should_track? # publish the experiment data to the client
+    publish_to_database if @record # publish the experiment context to the database
   end
 
-  # push the experiment data to the client
-  def push_to_client
+  def publish_to_client
     Gon.push({ experiment: { name => signature } }, true)
   rescue NoMethodError
     # means we're not in the request cycle, and can't add to Gon. Log a warning maybe?
   end
 
+  def publish_to_database
+    # if the context contains a namespace, group, project, user, or actor
+    value = context.value
+    subject = value[:namespace] || value[:group] || value[:project] || value[:user] || value[:actor]
+    return unless ExperimentSubject.valid_subject?(subject)
+
+    variant = :experimental if @variant_name != :control
+    Experiment.add_subject(name, variant: variant || :control, subject: subject)
+  end
+
   def track(action, **event_args)
     return unless should_track? # don't track events for excluded contexts
 
@@ -41,14 +47,23 @@ def record!
     @record = true
   end
 
-  def exclude!
-    @excluded = true
-  end
-
   def control_behavior
     # define a default nil control behavior so we can omit it when not needed
   end
 
+  # TODO: remove
+  # This is deprecated logic as of v0.6.0 and should eventually be removed, but
+  # needs to stay intact for actively running experiments. The new strategy
+  # utilizes Digest::SHA2, a secret seed, and generates a 64-byte string.
+  def key_for(source, seed = name)
+    source = source.keys + source.values if source.is_a?(Hash)
+
+    ingredients = Array(source).map { |v| identify(v) }
+    ingredients.unshift(seed)
+
+    Digest::MD5.hexdigest(ingredients.join('|'))
+  end
+
   private
 
   def feature_flag_name
@@ -58,13 +73,4 @@ def feature_flag_name
   def experiment_group?
     Feature.enabled?(feature_flag_name, self, type: :experiment, default_enabled: :yaml)
   end
-
-  def record_experiment
-    subject = context.value[:namespace] || context.value[:group] || context.value[:project] || context.value[:user] || context.value[:actor]
-    return unless ExperimentSubject.valid_subject?(subject)
-
-    variant = :experimental if @variant_name != :control
-
-    Experiment.add_subject(name, variant: variant || :control, subject: subject)
-  end
 end
diff --git a/config/feature_flags/development/gitlab_experiment_middleware.yml b/config/feature_flags/development/gitlab_experiment_middleware.yml
new file mode 100644
index 0000000000000000000000000000000000000000..76f8a36812d64694a910eaf844a188d2f4a2f30a
--- /dev/null
+++ b/config/feature_flags/development/gitlab_experiment_middleware.yml
@@ -0,0 +1,8 @@
+---
+name: gitlab_experiment_middleware
+introduced_by_url:
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323643
+milestone: '14.1'
+type: development
+group: group::adoption
+default_enabled: false
diff --git a/config/initializers/gitlab_experiment.rb b/config/initializers/gitlab_experiment.rb
index 055979bb66bdeb761ee202d68dd3d9cd9574fbc7..39131e1126086972b184a97b12c9daab7c263b78 100644
--- a/config/initializers/gitlab_experiment.rb
+++ b/config/initializers/gitlab_experiment.rb
@@ -2,16 +2,22 @@
 
 Gitlab::Experiment.configure do |config|
   config.base_class = 'ApplicationExperiment'
+  config.mount_at = '/-/experiment'
   config.cache = Gitlab::Experiment::Cache::RedisHashStore.new(
     pool: ->(&block) { Gitlab::Redis::SharedState.with { |redis| block.call(redis) } }
   )
+end
+
+# TODO: This shim should be removed after the feature flag is rolled out, as
+#   it only exists to facilitate the feature flag control of the behavior.
+module Gitlab::Experiment::MiddlewareWithFeatureFlags
+  attr_reader :app
 
-  # TODO: This will be deprecated as of v0.6.0, but needs to stay intact for
-  #  actively running experiments until a versioning concept is put in place to
-  #  enable migrating into the new SHA2 strategy.
-  config.context_hash_strategy = lambda do |source, seed|
-    source = source.keys + source.values if source.is_a?(Hash)
-    data = Array(source).map { |v| (v.respond_to?(:to_global_id) ? v.to_global_id : v).to_s }
-    Digest::MD5.hexdigest(data.unshift(seed).join('|'))
+  def call(env)
+    return app.call(env) unless Feature.enabled?(:gitlab_experiment_middleware)
+
+    super
   end
 end
+
+Gitlab::Experiment::Middleware.prepend(Gitlab::Experiment::MiddlewareWithFeatureFlags)
diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb
index 2d2b911749b73316d7224cb9665109a59ff12a28..25e91dfdedd625cab907ec21bcbb6ebd8e7d579c 100644
--- a/spec/experiments/application_experiment_spec.rb
+++ b/spec/experiments/application_experiment_spec.rb
@@ -3,11 +3,10 @@
 require 'spec_helper'
 
 RSpec.describe ApplicationExperiment, :experiment do
-  subject { described_class.new('namespaced/stub') }
+  subject { described_class.new('namespaced/stub', **context) }
 
-  let(:feature_definition) do
-    { name: 'namespaced_stub', type: 'experiment', group: 'group::adoption', default_enabled: false }
-  end
+  let(:context) { {} }
+  let(:feature_definition) { { name: 'namespaced_stub', type: 'experiment', default_enabled: false } }
 
   around do |example|
     Feature::Definition.definitions[:namespaced_stub] = Feature::Definition.new('namespaced_stub.yml', feature_definition)
@@ -25,7 +24,7 @@
     expect { experiment('namespaced/stub') { } }.not_to raise_error
   end
 
-  describe "enabled" do
+  describe "#enabled?" do
     before do
       allow(subject).to receive(:enabled?).and_call_original
 
@@ -57,103 +56,100 @@
     end
   end
 
-  describe "publishing results" do
-    it "doesn't record, track or push data to the client if we shouldn't track", :snowplow do
+  describe "#publish" do
+    it "doesn't track or publish to the client or database if we can't track", :snowplow do
       allow(subject).to receive(:should_track?).and_return(false)
-      subject.record!
 
-      expect(subject).not_to receive(:record_experiment)
-      expect(subject).not_to receive(:track)
-      expect(Gon).not_to receive(:push)
+      expect(subject).not_to receive(:publish_to_client)
+      expect(subject).not_to receive(:publish_to_database)
 
-      subject.publish(:action)
+      subject.publish
 
       expect_no_snowplow_event
     end
 
-    describe 'recording the experiment' do
-      it 'does not record the experiment if we do not tell it to' do
-        expect(subject).not_to receive(:record_experiment)
-
-        subject.publish
-      end
-
-      it 'records the experiment if we tell it to' do
-        subject.record!
-
-        expect(subject).to receive(:record_experiment)
-
-        subject.publish
-      end
-    end
-
     it "tracks the assignment" do
       expect(subject).to receive(:track).with(:assignment)
 
       subject.publish
     end
 
-    it "pushes the experiment knowledge into the client using Gon" do
-      expect(Gon).to receive(:push).with({ experiment: { 'namespaced/stub' => subject.signature } }, true)
+    it "publishes the to the client" do
+      expect(subject).to receive(:publish_to_client)
 
       subject.publish
     end
 
-    it "handles when Gon raises exceptions (like when it can't be pushed into)" do
-      expect(Gon).to receive(:push).and_raise(NoMethodError)
+    it "publishes to the database if we've opted for that" do
+      subject.record!
+
+      expect(subject).to receive(:publish_to_database)
 
-      expect { subject.publish }.not_to raise_error
+      subject.publish
     end
-  end
 
-  it "can exclude from within the block" do
-    expect(described_class.new('namespaced/stub') { |e| e.exclude! }).to be_excluded
-  end
+    describe "#publish_to_client" do
+      it "adds the data into Gon" do
+        signature = { key: '86208ac54ca798e11f127e8b23ec396a', variant: 'control' }
+        expect(Gon).to receive(:push).with({ experiment: { 'namespaced/stub' => hash_including(signature) } }, true)
 
-  describe 'recording the experiment subject' do
-    using RSpec::Parameterized::TableSyntax
+        subject.publish_to_client
+      end
 
-    subject { described_class.new('namespaced/stub', nil, **context) }
+      it "handles when Gon raises exceptions (like when it can't be pushed into)" do
+        expect(Gon).to receive(:push).and_raise(NoMethodError)
 
-    before do
-      subject.record!
+        expect { subject.publish_to_client }.not_to raise_error
+      end
     end
 
-    context 'when providing a compatible context' do
-      where(:context_key, :object_type) do
-        :namespace | :namespace
-        :group     | :namespace
-        :project   | :project
-        :user      | :user
-        :actor     | :user
+    describe "#publish_to_database" do
+      using RSpec::Parameterized::TableSyntax
+      let(:context) { { context_key => context_value }}
+
+      before do
+        subject.record!
       end
 
-      with_them do
-        let(:context) { { context_key => build(object_type) }}
+      context "when there's a usable subject" do
+        where(:context_key, :context_value, :object_type) do
+          :namespace | build(:namespace) | :namespace
+          :group     | build(:namespace) | :namespace
+          :project   | build(:project)   | :project
+          :user      | build(:user)      | :user
+          :actor     | build(:user)      | :user
+        end
 
-        it 'records the experiment and the experiment subject from the context' do
-          expect { subject.publish }.to change(Experiment, :count).by(1)
+        with_them do
+          it "creates an experiment and experiment subject record" do
+            expect { subject.publish_to_database }.to change(Experiment, :count).by(1)
 
-          expect(Experiment.last.name).to eq('namespaced/stub')
-          expect(ExperimentSubject.last.send(object_type)).to eq(context[context_key])
+            expect(Experiment.last.name).to eq('namespaced/stub')
+            expect(ExperimentSubject.last.send(object_type)).to eq(context[context_key])
+          end
         end
       end
-    end
 
-    context 'when providing an incompatible or no context' do
-      where(context_hash: [{ foo: :bar }, {}])
+      context "when there's not a usable subject" do
+        where(:context_key, :context_value) do
+          :namespace | nil
+          :foo       | :bar
+        end
 
-      with_them do
-        let(:context) { context_hash }
+        with_them do
+          it "doesn't create an experiment record" do
+            expect { subject.publish_to_database }.not_to change(Experiment, :count)
+          end
 
-        it 'does not record the experiment' do
-          expect { subject.publish }.not_to change(Experiment, :count)
+          it "doesn't create an experiment subject record" do
+            expect { subject.publish_to_database }.not_to change(ExperimentSubject, :count)
+          end
         end
       end
     end
   end
 
-  describe "tracking events", :snowplow do
+  describe "#track", :snowplow do
     it "doesn't track if we shouldn't track" do
       allow(subject).to receive(:should_track?).and_return(false)
 
@@ -185,7 +181,13 @@
     end
   end
 
-  describe "variant resolution" do
+  describe "#key_for" do
+    it "generates MD5 hashes" do
+      expect(subject.key_for(foo: :bar)).to eq('6f9ac12afdb9b58c2f19a136d09f9153')
+    end
+  end
+
+  context "when resolving variants" do
     it "uses the default value as specified in the yaml" do
       expect(Feature).to receive(:enabled?).with('namespaced_stub', subject, type: :experiment, default_enabled: :yaml)