diff --git a/e2e/support/helpers/e2e-filter-helpers.js b/e2e/support/helpers/e2e-filter-helpers.js
index 4ad2b75ddc52f2b9c49ff1df44d671d707c62e08..2d34c01694865a6c3b3bbd59634369278d9465e0 100644
--- a/e2e/support/helpers/e2e-filter-helpers.js
+++ b/e2e/support/helpers/e2e-filter-helpers.js
@@ -81,3 +81,9 @@ export function setConnectedFieldSource(table, field) {
   popover().findByText(table).click();
   popover().findByText(field).click();
 }
+
+export function changeSynchronousBatchUpdateSetting(value) {
+  cy.request("PUT", "/api/setting/synchronous-batch-updates", {
+    value: value,
+  });
+}
diff --git a/e2e/test/scenarios/dashboard-filters/parameters.cy.spec.js b/e2e/test/scenarios/dashboard-filters/parameters.cy.spec.js
index 41bce4e39417b3474045022bb277052ee26ea8c5..21d53cb88350e7a9bc1b7f102634f88f83a9247e 100644
--- a/e2e/test/scenarios/dashboard-filters/parameters.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-filters/parameters.cy.spec.js
@@ -5,6 +5,7 @@ import {
   ORDERS_DASHBOARD_ID,
 } from "e2e/support/cypress_sample_instance_data";
 import {
+  changeSynchronousBatchUpdateSetting,
   disconnectDashboardFilter,
   editDashboard,
   filterWidget,
@@ -46,6 +47,12 @@ describe("scenarios > dashboard > parameters", () => {
   beforeEach(() => {
     restore();
     cy.signInAsAdmin();
+    changeSynchronousBatchUpdateSetting(true);
+  });
+
+  afterEach(() => {
+    cy.signInAsAdmin();
+    changeSynchronousBatchUpdateSetting(false);
   });
 
   it("one filter should search across multiple fields", () => {
diff --git a/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js b/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
index ca2ac776881e1353c295c0543e075154268ad57a..855249fba782ed6aa024ec64a679b8f6fcb83717 100644
--- a/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
@@ -1,6 +1,7 @@
 import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
 import {
   appBar,
+  changeSynchronousBatchUpdateSetting,
   clearFilterWidget,
   createNativeQuestion,
   createQuestion,
@@ -236,9 +237,16 @@ const getParameterMapping = card => ({
 describe("scenarios > dashboard > temporal unit parameters", () => {
   beforeEach(() => {
     restore();
+    cy.signInAsAdmin();
+    changeSynchronousBatchUpdateSetting(true);
     cy.signInAsNormalUser();
   });
 
+  afterEach(() => {
+    cy.signInAsAdmin();
+    changeSynchronousBatchUpdateSetting(false);
+  });
+
   describe("mapping targets", () => {
     it("should connect a parameter to a question and drill thru", () => {
       createQuestion(noBreakoutQuestionDetails);
diff --git a/e2e/test/scenarios/dashboard/tabs.cy.spec.js b/e2e/test/scenarios/dashboard/tabs.cy.spec.js
index 5f208ea284d88799ede44ef84c5393f6880524ab..8ae991d740b5096fb387f3e3b300ee2a2bf48189 100644
--- a/e2e/test/scenarios/dashboard/tabs.cy.spec.js
+++ b/e2e/test/scenarios/dashboard/tabs.cy.spec.js
@@ -11,6 +11,7 @@ import {
 import {
   addHeadingWhileEditing,
   addLinkWhileEditing,
+  changeSynchronousBatchUpdateSetting,
   createDashboardWithTabs,
   createNewTab,
   dashboardCards,
@@ -92,12 +93,6 @@ const TAB_2 = {
   name: "Tab 2",
 };
 
-const changeSynchronousBatchUpdateSetting = value => {
-  cy.request("PUT", "/api/setting/synchronous-batch-updates", {
-    value: value,
-  });
-};
-
 describe("scenarios > dashboard > tabs", () => {
   beforeEach(() => {
     restore();
diff --git a/e2e/test/scenarios/filters-reproductions/dashboard-filters-reproductions.cy.spec.js b/e2e/test/scenarios/filters-reproductions/dashboard-filters-reproductions.cy.spec.js
index bbd8d7faf4f40b63257c129b68496c4c02e7927f..61cf2f1e766394a5c12ea47fa736165420389737 100644
--- a/e2e/test/scenarios/filters-reproductions/dashboard-filters-reproductions.cy.spec.js
+++ b/e2e/test/scenarios/filters-reproductions/dashboard-filters-reproductions.cy.spec.js
@@ -11,6 +11,7 @@ import {
   addOrUpdateDashboardCard,
   appBar,
   assertQueryBuilderRowCount,
+  changeSynchronousBatchUpdateSetting,
   commandPalette,
   commandPaletteSearch,
   createDashboardWithTabs,
@@ -1459,6 +1460,12 @@ describe("issues 15279 and 24500", () => {
   beforeEach(() => {
     restore();
     cy.signInAsAdmin();
+    changeSynchronousBatchUpdateSetting(true);
+  });
+
+  afterEach(() => {
+    cy.signInAsAdmin();
+    changeSynchronousBatchUpdateSetting(false);
   });
 
   it("corrupted dashboard filter should still appear in the UI without breaking other filters (metabase#15279, metabase#24500)", () => {
diff --git a/src/metabase/models/user_parameter_value.clj b/src/metabase/models/user_parameter_value.clj
index 8675503eda591f2925e70325b86cc23a2d0b8e90..f4ec8ea2da6fb6c2df3f634e447a036bbb33c521 100644
--- a/src/metabase/models/user_parameter_value.clj
+++ b/src/metabase/models/user_parameter_value.clj
@@ -1,8 +1,11 @@
 (ns metabase.models.user-parameter-value
   (:require
    [cheshire.core :as json]
+   [medley.core :as m]
    [metabase.api.common :as api]
    [metabase.models.interface :as mi]
+   [metabase.util.grouper :as grouper]
+   [metabase.util.log :as log]
    [metabase.util.malli :as mu]
    [metabase.util.malli.schema :as ms]
    [methodical.core :as methodical]
@@ -30,31 +33,49 @@
   {:value {:in  mi/json-in
            :out json-out}})
 
-(mu/defn batched-upsert!
+(defn batched-upsert!
   "Delete param with nil value and upsert the rest."
+  [parameters]
+  (let [to-insert (remove #(and (nil? (:value %)) (nil? (:default %))) parameters)]
+    (t2/with-transaction [_conn]
+      (doseq [batch (partition-all 1000 parameters)]
+        (t2/delete! :model/UserParameterValue
+                    {:where (into [:or] (for [p batch]
+                                          [:and
+                                           [:= :user_id (:user_id p)]
+                                           [:= :dashboard_id (:dashboard_id p)]
+                                           [:= :parameter_id (:parameter_id p)]]))}))
+      (doseq [batch (partition-all 1000 to-insert)]
+        (t2/insert! :model/UserParameterValue
+                    (map #(select-keys % [:user_id :dashboard_id :parameter_id :value]) batch))))))
+
+(defonce ^:private user-parameter-value-queue
+  (delay (grouper/start!
+          (fn [inputs]
+            (try
+              (batched-upsert!
+               (->> (for [input     inputs
+                          parameter (:parameters input)]
+                      {:user_id      (:user-id input)
+                       :dashboard_id (:dashboard-id input)
+                       :parameter_id (:id parameter)
+                       :value        (:value parameter)
+                       :default      (:default parameter)})
+                    (m/index-by (juxt :user_id :dashboard_id :parameter_id))
+                    vals))
+              (catch Exception e
+                (log/error e "Error saving user parameters for a dashboard"))))
+          :capacity 5000
+          :interval 5000)))
+
+(mu/defn store!
+  "Asynchronously delete params with a nil `value` and upsert the rest."
   [user-id         :- ms/PositiveInt
    dashboard-id    :- ms/PositiveInt
    parameters      :- [:sequential :map]]
-  (let [;; delete param with nil value and no default
-        to-delete-pred (fn [{:keys [value default]}]
-                         (and (nil? value) (nil? default)))
-        to-delete      (filter to-delete-pred parameters)
-        to-upsert      (filter (complement to-delete-pred) parameters)]
-    (t2/with-transaction [_conn]
-      (doseq [{:keys [id value]} to-upsert]
-        (or (pos? (t2/update! :model/UserParameterValue {:user_id      user-id
-                                                         :dashboard_id dashboard-id
-                                                         :parameter_id id}
-                              {:value value}))
-            (t2/insert! :model/UserParameterValue {:user_id      user-id
-                                                   :dashboard_id dashboard-id
-                                                   :parameter_id id
-                                                   :value        value})))
-      (when (seq to-delete)
-        (t2/delete! :model/UserParameterValue
-                    :user_id user-id
-                    :dashboard_id dashboard-id
-                    :parameter_id [:in (map :id to-delete)])))))
+  (grouper/submit! @user-parameter-value-queue {:user-id      user-id
+                                                :dashboard-id dashboard-id
+                                                :parameters   parameters}))
 
 ;; hydration
 
diff --git a/src/metabase/query_processor/dashboard.clj b/src/metabase/query_processor/dashboard.clj
index 9250a675333a9a881311674792616bb0e361c1d6..d1c79ed5e44530998b05ef4619a0032e7b8c9f77 100644
--- a/src/metabase/query_processor/dashboard.clj
+++ b/src/metabase/query_processor/dashboard.clj
@@ -145,9 +145,7 @@
                                                request-param-id->param))]
     (when-let [user-id api/*current-user-id*]
       (when (seq request-params)
-        (user-parameter-value/batched-upsert!
-         user-id dashboard-id
-         request-params)))
+        (user-parameter-value/store! user-id dashboard-id request-params)))
     (log/tracef "Dashboard parameters:\n%s\nRequest parameters:\n%s\nMerged:\n%s"
                 (u/pprint-to-str (update-vals dashboard-param-id->param
                                               (fn [param]
diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj
index 0ce888982842d20a1d9e7f82252876c663cfe7b0..4c015c5f0ce49608f8f129356a217b4a253ba43d 100644
--- a/test/metabase/api/dashboard_test.clj
+++ b/test/metabase/api/dashboard_test.clj
@@ -391,58 +391,60 @@
                                (#'api.dashboard/get-dashboard dash-id))))))))
 
 (deftest last-used-parameter-value-test
-  (mt/dataset test-data
-    (mt/with-column-remappings [orders.user_id people.name]
-      (mt/as-admin
-        (t2.with-temp/with-temp
-          [Dashboard {dashboard-a-id :id} {:name       "Test Dashboard"
-                                           :creator_id (mt/user->id :crowberto)
-                                           :parameters [{:name    "Name", :slug "name", :id "a" :type :string/contains
-                                                         :default ["default_value"]}]}
-           Dashboard {dashboard-b-id :id} {:name       "Test Dashboard"
-                                           :creator_id (mt/user->id :crowberto)
-                                           :parameters [{:name "Name", :slug "name", :id "a" :type :string/contains}]}
-           Card {card-id :id} {:database_id   (mt/id)
-                               :query_type    :native
-                               :name          "test question"
-                               :creator_id    (mt/user->id :crowberto)
-                               :dataset_query {:type     :native
-                                               :native   {:query "SELECT COUNT(*) FROM people WHERE {{name}}"
-                                                          :template-tags
-                                                          {"name" {:name         "Name"
-                                                                   :display-name "name"
-                                                                   :type         :dimension
-                                                                   :dimension    [:field (mt/id :people :name) nil]
-                                                                   :widget-type  :string/contains}}}
-                                               :database (mt/id)}}
-           DashboardCard {dashcard-a-id :id} {:parameter_mappings [{:parameter_id "a", :card_id card-id, :target [:dimension [:template-tag "id"]]}]
-                                              :card_id            card-id
-                                              :dashboard_id       dashboard-a-id}
-           DashboardCard {dashcard-b-id :id} {:parameter_mappings [{:parameter_id "a", :card_id card-id, :target [:dimension [:template-tag "id"]]}]
-                                              :card_id            card-id
-                                              :dashboard_id       dashboard-b-id}]
-          (testing "User's set parameter is saved and sent back in the dashboard response, unique per dashboard."
-           ;; api request mimicking a user setting a parameter value
-            (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-a-id dashcard-a-id card-id)
-                                             {:parameters [{:id "a" :value ["initial value"]}]})))
-            (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-b-id dashcard-b-id card-id)
-                                             {:parameters [{:id "a" :value ["initial value"]}]})))
-            (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-a-id dashcard-a-id card-id)
-                                             {:parameters [{:id "a" :value ["new value"]}]})))
-            (is (= {:dashboard-a {:a ["new value"]}
-                    :dashboard-b {:a ["initial value"]}}
-                   {:dashboard-a (:last_used_param_values (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-a-id)))
-                    :dashboard-b (:last_used_param_values (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-b-id)))})))
-          (testing "If a User unsets a parameter's value, the default is NOT used."
-           ;; api request mimicking a user clearing parameter value, and no default exists
-            (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-a-id dashcard-a-id card-id)
-                                             {:parameters [{:id "a" :value nil}]})))
-            (is (= {}
-                   (:last_used_param_values (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-a-id)))))
-            (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-a-id dashcard-a-id card-id)
-                                             {:parameters [{:id "a" :value nil :default ["default value"]}]})))
-            (is (= {:a nil}
-                   (:last_used_param_values (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-a-id)))))))))))
+  (mt/test-helpers-set-global-values!
+    (mt/with-temporary-setting-values [synchronous-batch-updates true]
+      (mt/dataset test-data
+        (mt/with-column-remappings [orders.user_id people.name]
+          (mt/as-admin
+            (t2.with-temp/with-temp
+              [Dashboard {dashboard-a-id :id} {:name       "Test Dashboard"
+                                               :creator_id (mt/user->id :crowberto)
+                                               :parameters [{:name    "Name", :slug "name", :id "a" :type :string/contains
+                                                             :default ["default_value"]}]}
+               Dashboard {dashboard-b-id :id} {:name       "Test Dashboard"
+                                               :creator_id (mt/user->id :crowberto)
+                                               :parameters [{:name "Name", :slug "name", :id "a" :type :string/contains}]}
+               Card {card-id :id} {:database_id   (mt/id)
+                                   :query_type    :native
+                                   :name          "test question"
+                                   :creator_id    (mt/user->id :crowberto)
+                                   :dataset_query {:type     :native
+                                                   :native   {:query "SELECT COUNT(*) FROM people WHERE {{name}}"
+                                                              :template-tags
+                                                              {"name" {:name         "Name"
+                                                                       :display-name "name"
+                                                                       :type         :dimension
+                                                                       :dimension    [:field (mt/id :people :name) nil]
+                                                                       :widget-type  :string/contains}}}
+                                                   :database (mt/id)}}
+               DashboardCard {dashcard-a-id :id} {:parameter_mappings [{:parameter_id "a", :card_id card-id, :target [:dimension [:template-tag "id"]]}]
+                                                  :card_id            card-id
+                                                  :dashboard_id       dashboard-a-id}
+               DashboardCard {dashcard-b-id :id} {:parameter_mappings [{:parameter_id "a", :card_id card-id, :target [:dimension [:template-tag "id"]]}]
+                                                  :card_id            card-id
+                                                  :dashboard_id       dashboard-b-id}]
+              (testing "User's set parameter is saved and sent back in the dashboard response, unique per dashboard."
+                ;; api request mimicking a user setting a parameter value
+                (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-a-id dashcard-a-id card-id)
+                                                 {:parameters [{:id "a" :value ["initial value"]}]})))
+                (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-b-id dashcard-b-id card-id)
+                                                 {:parameters [{:id "a" :value ["initial value"]}]})))
+                (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-a-id dashcard-a-id card-id)
+                                                 {:parameters [{:id "a" :value ["new value"]}]})))
+                (is (= {:dashboard-a {:a ["new value"]}
+                        :dashboard-b {:a ["initial value"]}}
+                       {:dashboard-a (:last_used_param_values (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-a-id)))
+                        :dashboard-b (:last_used_param_values (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-b-id)))})))
+              (testing "If a User unsets a parameter's value, the default is NOT used."
+                ;; api request mimicking a user clearing parameter value, and no default exists
+                (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-a-id dashcard-a-id card-id)
+                                                 {:parameters [{:id "a" :value nil}]})))
+                (is (= {}
+                       (:last_used_param_values (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-a-id)))))
+                (is (some? (mt/user-http-request :rasta :post (format "dashboard/%d/dashcard/%s/card/%s/query" dashboard-a-id dashcard-a-id card-id)
+                                                 {:parameters [{:id "a" :value nil :default ["default value"]}]})))
+                (is (= {:a nil}
+                       (:last_used_param_values (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-a-id)))))))))))))
 
 (deftest fetch-dashboard-test
   (testing "GET /api/dashboard/:id"
diff --git a/test/metabase/models/user_parameter_value_test.clj b/test/metabase/models/user_parameter_value_test.clj
index 8ed7c19b9b5bcaadde7961e1a15cba53b9811dc6..226e65287726e950acd2c3268d17a5c26736f519 100644
--- a/test/metabase/models/user_parameter_value_test.clj
+++ b/test/metabase/models/user_parameter_value_test.clj
@@ -6,44 +6,53 @@
    [metabase.test :as mt]
    [toucan2.core :as t2]))
 
-(deftest user-parameter-value-batch-upsert-test
-  (mt/with-temp [:model/Dashboard {dashboard-id :id} {}]
-    (let [value!   (fn [parameters]
-                     (upv/batched-upsert! (mt/user->id :rasta) dashboard-id parameters))
-          value-fn (fn []
-                     (t2/select-fn->fn :parameter_id :value
-                                       :model/UserParameterValue
-                                       :user_id (mt/user->id :rasta) :dashboard_id dashboard-id))]
+(set! *warn-on-reflection* true)
 
-      (testing "insert upv if value is non-nil"
-        (value! [{:id "param1" :value 1}
-                 {:id "param2" :value "string"}
-                 {:id "param3" :value ["A" "B" "C"]}])
-        (is (= {"param1" 1
-                "param2" "string"
-                "param3" ["A" "B" "C"]}
-               (value-fn))))
+(deftest user-parameter-value-store-test
+  (mt/test-helpers-set-global-values!
+    (mt/with-temporary-setting-values [synchronous-batch-updates true]
+      (mt/with-temp [:model/Dashboard {dashboard-id :id} {}]
+        (let [store!      (fn [parameters]
+                            (upv/store! (mt/user->id :rasta) dashboard-id parameters))
+              retrieve-fn (fn []
+                            (t2/select-fn->fn :parameter_id :value
+                                              :model/UserParameterValue
+                                              :user_id (mt/user->id :rasta) :dashboard_id dashboard-id))]
+          (testing "insert upv if value is non-nil"
+            (store! [{:id "param1" :value 1}
+                     {:id "param2" :value "string"}
+                     {:id "param3" :value ["A" "B" "C"]}])
+            (is (= {"param1" 1
+                    "param2" "string"
+                    "param3" ["A" "B" "C"]}
+                   (retrieve-fn))))
 
-      (testing "delete if value is nil"
-        (value! [{:id "param1" :value "foo"} {:id "param2" :value nil}])
-        (is (= {"param1" "foo"
-                "param3" ["A" "B" "C"]}
-               (value-fn))))
+          (testing "delete if value is nil"
+            (store! [{:id "param1" :value "foo"} {:id "param2" :value nil}])
+            (is (= {"param1" "foo"
+                    "param3" ["A" "B" "C"]}
+                   (retrieve-fn))))
 
-      (testing "update existing param and insert new param"
-        (value! [{:id "param1", :value "new-value"} {:id "param2", :value "new-value"}])
-        (is (= {"param1" "new-value"
-                "param2" "new-value"
-                "param3" ["A" "B" "C"]}
-               (value-fn))))
+          (testing "last value wins"
+            (store! [{:id "param1" :value "bar"} {:id "param1" :value "baz"}])
+            (is (= {"param1" "baz"
+                    "param3" ["A" "B" "C"]}
+                   (retrieve-fn))))
 
-      (testing "insert nil if param has default value"
-        (value! [{:id "param4" :value nil :default "default"}])
-        (is (= {"param1" "new-value"
-                "param2" "new-value"
-                "param3" ["A" "B" "C"]
-                "param4" nil}
-               (value-fn)))))))
+          (testing "update existing param and insert new param"
+            (store! [{:id "param1", :value "new-value"} {:id "param2", :value "new-value"}])
+            (is (= {"param1" "new-value"
+                    "param2" "new-value"
+                    "param3" ["A" "B" "C"]}
+                   (retrieve-fn))))
+
+          (testing "insert nil if param has default value"
+            (store! [{:id "param4" :value nil :default "default"}])
+            (is (= {"param1" "new-value"
+                    "param2" "new-value"
+                    "param3" ["A" "B" "C"]
+                    "param4" nil}
+                   (retrieve-fn)))))))))
 
 (deftest hydrate-last-used-param-values-test
   (let [rasta-id (mt/user->id :rasta)