From 6668b4ee53f0d1ff78b8157899c2c6eeaa56e425 Mon Sep 17 00:00:00 2001 From: Robert Roland <rob@metabase.com> Date: Fri, 12 Mar 2021 11:27:24 -0800 Subject: [PATCH] Clean up sync scheduling (#15043) It's possible for the scheduler to get in a weird state if the sync fails while it executes. This change makes it *only* recreate a job/task if the schedule has changed or if it is missing. Previously, clearing the state at every start had bad effects if the JVM had terminated during the sync. Adds a vector of Exception classes that signal a "fatal" exception during sync for a specific database. If these exceptions occur, the sync for that database stops and will pick up next time. This will have to be expanded per driver, but I don't see a way around that, as each driver will have its own, unique way of failing. metabase/metabase#14817 --- .circleci/config.yml | 2 +- .../test/__runner__/test_db_fixture.db.mv.db | Bin 167936 -> 348160 bytes resources/quartz.properties | 12 +--- src/metabase/models/database.clj | 2 +- src/metabase/models/task_history.clj | 10 ++- .../sync/sync_metadata/sync_timezone.clj | 25 +++----- src/metabase/sync/util.clj | 38 ++++++++++-- src/metabase/task.clj | 17 +++--- src/metabase/task/sync_databases.clj | 50 ++++++++++----- src/metabase/util.clj | 7 ++- .../sync/sync_metadata/sync_timezone_test.clj | 24 -------- test/metabase/sync/util_test.clj | 57 ++++++++++++++++++ test/metabase/task/sync_databases_test.clj | 14 ++--- 13 files changed, 167 insertions(+), 91 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e675b609733..b2c90abaace 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1333,7 +1333,7 @@ workflows: <<: *Matrix - fe-tests-cypress: - name: fe-tests-cypres-mysql-8-<< matrix.edition >> + name: fe-tests-cypress-mysql-8-<< matrix.edition >> requires: - build-uberjar-<< matrix.edition >> e: fe-mysql-8 diff --git a/frontend/test/__runner__/test_db_fixture.db.mv.db b/frontend/test/__runner__/test_db_fixture.db.mv.db index 8455bd0ecc0bbbe87bd1671aa67e6192e04203a1..1fcd479290e1bb9d3078fb0e824e4b173d7d5270 100644 GIT binary patch delta 74122 zcmbq+31AdO_V-lHR1%Jage&1r069aL?&-Mzfe-=&LI@!NK{WQv^gt8{M?erY+;S+_ zsEs$e-m55};)VCM>U!YuUv<58cfC>6_4fO{s?IS28uuGQzpn0ARqwreRrTssb=9if ztyk^tv}zr@mT%wbO{q5WBlBvbXAINB`SYv=!!$F0zPWIirbhsm57XkB*PFk<T3$EI z@Z~Qw=U7XIX+AA~acyngFud}5{gFr{rsdbw%(sR`^++h<3q`&8%dEvqYHAk@GZK4; z2J2h9T&|#NkK4^$F8sOtSrWQ0d`ZGqBic#We$Pvr*vf&HxQU6kxOj@5zQr=!11Zib zmn)i~;Jp<R-F3@$30WSVQ_3rzRq)OiB%V9bUWPdzT)xvB(-SCs`v&#sh6{o6kSlFl zih|c~PAoT1Y18qVo$|fAW?~{OGO?G(%{{`6pKyCToZ%;dhkkAkPppUx78AuJQJT0d zlAXVriFHhDW@0N7JDIqMiA$Kcf{EQsTubyFa>v{rmuMr}iuC;>{8cQ$f(zTQsCeJ) zc8N@pl~@)0Phw(tVO!T>(ZS7KB1d#_CGv)U(2>2G3KCqRtLP?rh@P%2kEd&5@94pa zU$^v4oIYY_V*ZKmwqh5x8bK-bP5c;HvH!zp2bSTQ-MTBJE{Z5dQR3GRx+h**?rrOG zxl>zRK`%;N`wQ!T;cfP$x1$Wf!kWaYJ9no+R>RuPcNZk~9O{z+;yryPO38T2%Mx!N z>KdR&w`WS>^}XvU1)tw6QAGbT8SX!l%IhIl|4S;>^VJ&@qh30_9#O>=4(g*D)vNbZ z>M89&8s4Xrsw9k`Re7Y`qLK>u4#^|~�nJb#ImUsZ&)tsf&Pz_^FSdFJYSJX$1>F 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%0Ay|LQQck6L2c@x{&0cP8x+1fH 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 apEUW6&zIfx^JQQ!xN!{E9-l9>jQ;^r?`p;X delta 12 TcmZozAlk5itD%K)3DX1sADjf6 diff --git a/resources/quartz.properties b/resources/quartz.properties index a39a1c48e87..853c7b4f0bc 100644 --- a/resources/quartz.properties +++ b/resources/quartz.properties @@ -4,7 +4,7 @@ org.quartz.scheduler.instanceId = AUTO org.quartz.threadPool.threadCount = 10 # Don't phone home -org.quartz.scheduler.skipUpdateCheck: true +org.quartz.scheduler.skipUpdateCheck = true # Use the JDBC backend so we can cluster when running multiple instances! # See http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigJDBCJobStoreClustering @@ -27,16 +27,6 @@ org.quartz.jobStore.isClustered = true # than not at all for such things) org.quartz.jobStore.misfireThreshold=900000 -# By default, Quartz will fire triggers up to a minute late without considering them to be misfired; if it cannot fire -# anything within that period for one reason or another (such as all threads in the thread pool being tied up), the -# trigger is considered misfired. Threshold is in milliseconds. -# -# Default threshould is one minute (60,000) -# We'll bump it up to 15 minutes (900,000) because the sorts of things we're scheduling aren't extremely time-sensitive, -# for example Pulses and Sync can be sent out more than a minute late without issue. (In fact, 15 minutes late is better -# than not at all for such things) -org.quartz.jobStore.misfireThreshold=900000 - # Useful for debugging when Quartz jobs run and when they misfire #org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin #org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger \{1\}.\{0\} fired job \{6\}.\{5\} at: \{4, date, HH:mm:ss MM/dd/yyyy} diff --git a/src/metabase/models/database.clj b/src/metabase/models/database.clj index b6bb0c31cc7..994c79f7d74 100644 --- a/src/metabase/models/database.clj +++ b/src/metabase/models/database.clj @@ -25,7 +25,7 @@ (try ;; this is done this way to avoid circular dependencies (classloader/require 'metabase.task.sync-databases) - ((resolve 'metabase.task.sync-databases/schedule-tasks-for-db!) database) + ((resolve 'metabase.task.sync-databases/check-and-schedule-tasks-for-db!) database) (catch Throwable e (log/error e (trs "Error scheduling tasks for DB"))))) diff --git a/src/metabase/models/task_history.clj b/src/metabase/models/task_history.clj index f146b1f15cc..4af8f2fb68a 100644 --- a/src/metabase/models/task_history.clj +++ b/src/metabase/models/task_history.clj @@ -1,5 +1,6 @@ (ns metabase.models.task-history - (:require [clojure.tools.logging :as log] + (:require [cheshire.generate :refer [add-encoder encode-map]] + [clojure.tools.logging :as log] [java-time :as t] [metabase.models.interface :as i] [metabase.util :as u] @@ -94,3 +95,10 @@ {:style/indent 1} [info & body] `(do-with-task-history ~info (fn [] ~@body))) + +;; TaskHistory can contain an exception for logging purposes, so use the built-in +;; serialization of a `Throwable->map` to make this something that can be JSON encoded. +(add-encoder + Throwable + (fn [throwable json-generator] + (encode-map (Throwable->map throwable) json-generator))) diff --git a/src/metabase/sync/sync_metadata/sync_timezone.clj b/src/metabase/sync/sync_metadata/sync_timezone.clj index d01613bd175..098aa7cbf31 100644 --- a/src/metabase/sync/sync_metadata/sync_timezone.clj +++ b/src/metabase/sync/sync_metadata/sync_timezone.clj @@ -1,6 +1,5 @@ (ns metabase.sync.sync-metadata.sync-timezone - (:require [clojure.tools.logging :as log] - [metabase.driver :as driver] + (:require [metabase.driver :as driver] [metabase.driver.util :as driver.u] [metabase.models.database :refer [Database]] [metabase.sync.interface :as i] @@ -13,18 +12,12 @@ (-> dt .getChronology .getZone .getID)) (s/defn sync-timezone! - "Query `database` for it' current time to determine its timezone. The results of this function are used by the sync - process to update the timezone if it's different. - - Catches and logs Exceptions if querying for current timezone fails. Returns timezone as `{:timezone-id <timezone>}` - upon success, `nil` if query failed." + "Query `database` for its current time to determine its timezone. The results of this function are used by the sync + process to update the timezone if it's different." [database :- i/DatabaseInstance] - (try - (let [driver (driver.u/database->driver database) - zone-id (or (driver/db-default-timezone driver database) - (some-> (driver/current-db-time driver database) extract-time-zone))] - (when-not (= zone-id (:timezone database)) - (db/update! Database (:id database) {:timezone zone-id})) - {:timezone-id zone-id}) - (catch Exception e - (log/warn e "Error syncing database timezone")))) + (let [driver (driver.u/database->driver database) + zone-id (or (driver/db-default-timezone driver database) + (some-> (driver/current-db-time driver database) extract-time-zone))] + (when-not (= zone-id (:timezone database)) + (db/update! Database (:id database) {:timezone zone-id})) + {:timezone-id zone-id})) diff --git a/src/metabase/sync/util.clj b/src/metabase/sync/util.clj index f1766bf59d7..283675f394f 100644 --- a/src/metabase/sync/util.clj +++ b/src/metabase/sync/util.clj @@ -135,6 +135,11 @@ (driver/sync-in-context (driver.u/database->driver database) database f))) +(def ^:private exception-classes-not-to-retry + ;;TODO: future, expand this to `driver` level, where the drivers themselves can add to the + ;; list of exception classes (like, driver-specific exceptions) + [java.net.ConnectException java.net.NoRouteToHostException java.net.UnknownHostException + com.mchange.v2.resourcepool.CannotAcquireResourceException]) (defn do-with-error-handling "Internal implementation of `with-error-handling`; use that instead of calling this directly." @@ -144,13 +149,16 @@ ([message f] (try (f) - (catch Throwable e - (log/error e message) - e)))) + (catch Throwable t + (log/warn t message) + t)))) (defmacro with-error-handling "Execute `body` in a way that catches and logs any Exceptions thrown, and returns `nil` if they do so. Pass a - `message` to help provide information about what failed for the log message." + `message` to help provide information about what failed for the log message. + + The exception classes in `exception-classes-not-to-retry` are a list of classes tested against exceptions thrown. + If there is a match found, the sync is aborted as that error is not considered recoverable for this sync run." {:style/indent 1} [message & body] `(do-with-error-handling ~message (fn [] ~@body))) @@ -339,7 +347,11 @@ results (with-start-and-finish-debug-logging (trs "step ''{0}'' for {1}" step-name (name-for-logging database)) - #(sync-fn database)) + (fn [& args] + (try + (apply sync-fn database args) + (catch Throwable t + {:throwable t})))) end-time (t/zoned-date-time)] [step-name (assoc results :start-time start-time @@ -424,7 +436,21 @@ database :- i/DatabaseInstance sync-steps :- [StepDefinition]] (let [start-time (t/zoned-date-time) - step-metadata (mapv #(run-step-with-metadata database %) sync-steps) + step-metadata (loop [[step-defn & rest-defns] sync-steps + result []] + (let [[step-name r] (run-step-with-metadata database step-defn) + new-result (conj result [step-name r])] + (if (contains? r :throwable) + (let [caught-exception (:throwable r) + exception-classes (u/full-exception-chain caught-exception) + abandon? (some true? (for [ex exception-classes + test-ex exception-classes-not-to-retry] + (= (.. ^Object ex getClass getName) (.. ^Class test-ex getName))))] + (cond abandon? new-result + (not (seq rest-defns)) new-result + :else (recur rest-defns new-result))) + (cond (not (seq rest-defns)) new-result + :else (recur rest-defns new-result))))) end-time (t/zoned-date-time) sync-metadata {:start-time start-time :end-time end-time diff --git a/src/metabase/task.clj b/src/metabase/task.clj index d63bd5e2f35..3dd6d2cb594 100644 --- a/src/metabase/task.clj +++ b/src/metabase/task.clj @@ -271,14 +271,15 @@ (task/job-info \"metabase.task.sync-and-analyze.job\")" [job-key] - (let [job-key (->job-key job-key)] - (try - (assoc (job-detail->info (qs/get-job (scheduler) job-key)) - :triggers (for [trigger (sort-by #(-> ^Trigger % .getKey .getName) - (qs/get-triggers-of-job (scheduler) job-key))] - (trigger->info trigger))) - (catch Throwable e - (log/warn e (trs "Error fetching details for Job: {0}" (.getName job-key))))))) + (when-let [scheduler (scheduler)] + (let [job-key (->job-key job-key)] + (try + (assoc (job-detail->info (qs/get-job scheduler job-key)) + :triggers (for [trigger (sort-by #(-> ^Trigger % .getKey .getName) + (qs/get-triggers-of-job scheduler job-key))] + (trigger->info trigger))) + (catch Throwable e + (log/warn e (trs "Error fetching details for Job: {0}" (.getName job-key)))))))) (defn- jobs-info [] (->> (some-> (scheduler) (.getJobKeys nil)) diff --git a/src/metabase/task/sync_databases.clj b/src/metabase/task/sync_databases.clj index 9cf1698bfa6..c8f5a41c9fd 100644 --- a/src/metabase/task/sync_databases.clj +++ b/src/metabase/task/sync_databases.clj @@ -199,20 +199,41 @@ ;; See https://www.nurkiewicz.com/2012/04/quartz-scheduler-misfire-instructions.html for more info (cron/with-misfire-handling-instruction-do-nothing))))) -(s/defn ^:private schedule-tasks-for-db! - "Schedule a new Quartz job for `database` and `task-info`." +(s/defn ^:private check-and-schedule-tasks-for-db! + "Schedule a new Quartz job for `database` and `task-info` if it doesn't already exist or is incorrect." [database :- DatabaseInstance] - (let [sync-trigger (trigger database sync-analyze-task-info) - fv-trigger (trigger database field-values-task-info)] - ;; unschedule any tasks that might already be scheduled - (unschedule-tasks-for-db! database) - (log/debug - (u/format-color 'green "Scheduling sync/analyze and field-values task for database %d: trigger: %s and trigger: %s" - (u/get-id database) (.getName (.getKey sync-trigger)) - (u/get-id database) (.getName (.getKey fv-trigger)))) - ;; now (re)schedule all the tasks - (task/add-trigger! sync-trigger) - (task/add-trigger! fv-trigger))) + (let [sync-job (task/job-info (job-key sync-analyze-task-info)) + fv-job (task/job-info (job-key field-values-task-info)) + + sync-trigger (trigger database sync-analyze-task-info) + fv-trigger (trigger database field-values-task-info) + + existing-sync-trigger (some (fn [trigger] (when (= (:key trigger) (.. sync-trigger getKey getName)) + trigger)) + (:triggers sync-job)) + existing-fv-trigger (some (fn [trigger] (when (= (:key trigger) (.. fv-trigger getKey getName)) + trigger)) + (:triggers fv-job))] + + (doseq [{:keys [existing-trigger existing-schedule ti trigger description]} + [{:existing-trigger existing-sync-trigger + :existing-schedule (:metadata_sync_schedule database) + :ti sync-analyze-task-info + :trigger sync-trigger + :description "sync/analyze"} + {:existing-trigger existing-fv-trigger + :existing-schedule (:cache_field_values_schedule database) + :ti field-values-task-info + :trigger fv-trigger + :description "field-values"}]] + (when (or (not existing-trigger) + (not= (:schedule existing-trigger) existing-schedule)) + (delete-task! database ti) + (log/info + (u/format-color 'green "Scheduling %s for database %d: trigger: %s" + description (u/get-id database) (.. ^org.quartz.Trigger trigger getKey getName))) + ;; now (re)schedule the task + (task/add-trigger! trigger))))) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -257,7 +278,6 @@ (job-init) (doseq [database (db/select Database)] (try - ;; TODO -- shouldn't all the triggers be scheduled already? - (schedule-tasks-for-db! (maybe-update-db-schedules database)) + (check-and-schedule-tasks-for-db! (maybe-update-db-schedules database)) (catch Throwable e (log/error e (trs "Failed to schedule tasks for Database {0}" (:id database))))))) diff --git a/src/metabase/util.clj b/src/metabase/util.clj index c1ad71a755e..a0f3b77921b 100644 --- a/src/metabase/util.clj +++ b/src/metabase/util.clj @@ -406,6 +406,11 @@ (^String [s max-length] (str/join (take max-length (slugify s))))) +(defn full-exception-chain + "Gather the full exception chain into a single vector." + [e] + (take-while some? (iterate #(.getCause ^Throwable %) e))) + (defn all-ex-data "Like `ex-data`, but merges `ex-data` from causes. If duplicate keys exist, the keys from the highest level are preferred. @@ -422,7 +427,7 @@ (fn [data e] (merge (ex-data e) data)) nil - (take-while some? (iterate #(.getCause ^Throwable %) e)))) + (full-exception-chain e))) (defn do-with-auto-retries "Execute `f`, a function that takes no arguments, and return the results. diff --git a/test/metabase/sync/sync_metadata/sync_timezone_test.clj b/test/metabase/sync/sync_metadata/sync_timezone_test.clj index b00b9cb5b3d..5f91b2277e7 100644 --- a/test/metabase/sync/sync_metadata/sync_timezone_test.clj +++ b/test/metabase/sync/sync_metadata/sync_timezone_test.clj @@ -37,27 +37,3 @@ (is (nil? tz-after-update))) (testing "Check that the value was set again after sync" (is (time/time-zone-for-id (db-timezone db))))))))) - -(deftest bad-change-test - (mt/test-drivers #{:postgres} - (testing "Test that if timezone is changed to something that fails, timezone is unaffected." - ;; Setting timezone to "Austrailia/Sydney" fails on some computers, especially the CI ones. In that case it fails as - ;; the dates on PostgreSQL return 'AEST' for the time zone name. The Exception is logged, but the timezone column - ;; should be left alone and processing should continue. - ;; - ;; TODO - Recently this call has started *succeeding* for me on Java 10/11 and Postgres 9.6. I've seen it sync as both - ;; "Australia/Hobart" and "Australia/Sydney". Since setting the timezone no longer always fails it's no longer a good - ;; test. We need to think of something else here. In the meantime, I'll go ahead and consider any of the three options - ;; valid answers. - (mt/dataset test-data - ;; use `with-temp-vals-in-db` to make sure the test data DB timezone gets reset to whatever it was before the test - ;; ran if we accidentally end up setting it in the `:after` part - (mt/with-temp-vals-in-db Database (mt/db) {:timezone (db-timezone (mt/db))} - (sync-tz/sync-timezone! (mt/db)) - (testing "before" - (is (= "UTC" - (db-timezone (mt/db))))) - (testing "after" - (mt/with-temporary-setting-values [report-timezone "Australia/Sydney"] - (sync-tz/sync-timezone! (mt/db)) - (is (contains? #{"Australia/Hobart" "Australia/Sydney" "UTC"} (db-timezone (mt/db))))))))))) diff --git a/test/metabase/sync/util_test.clj b/test/metabase/sync/util_test.clj index 9b34dcc7052..d3ecf4fd8ad 100644 --- a/test/metabase/sync/util_test.clj +++ b/test/metabase/sync/util_test.clj @@ -8,6 +8,7 @@ [metabase.models.task-history :refer [TaskHistory]] [metabase.sync :as sync] [metabase.sync.util :as sync-util :refer :all] + [metabase.test :as mt] [metabase.test.util :as tu] [toucan.db :as db] [toucan.util.test :as tt])) @@ -188,3 +189,59 @@ (testing "has-step-duration?" (is (= true (str/includes? results "4.0 s")))))))) + +(deftest error-handling-test + (testing "A ConnectException will cause sync to stop" + (mt/dataset sample-dataset + (let [expected (java.io.IOException. + "outer" + (java.net.ConnectException. + "inner, this one triggers the failure")) + actual (sync-util/sync-operation :sync-error-handling (mt/db) "sync error handling test" + (sync-util/run-sync-operation + "sync" + (mt/db) + [(sync-util/create-sync-step "failure-step" + (fn [_] + (throw expected))) + (sync-util/create-sync-step "should-not-run" + (fn [_] + {}))])) + [step-name result] (first (:steps actual))] + (is (= 1 (count (:steps actual)))) + (is (= "failure-step" step-name)) + (is (= {:throwable expected :log-summary-fn nil} + (dissoc result :start-time :end-time)))))) + + (doseq [ex [(java.io.IOException. + "outer, does not trigger" + (java.net.SocketException. "inner, this one does not trigger")) + (java.lang.IllegalArgumentException. "standalone, does not trigger") + (java.sql.SQLException. + "outer, does not trigger" + (java.sql.SQLException. + "inner, does not trigger" + (java.lang.IllegalArgumentException. + "third level, does not trigger")))]] + (testing "Other errors will not cause sync to stop" + (let [actual (sync-util/sync-operation :sync-error-handling (mt/db) "sync error handling test" + (sync-util/run-sync-operation + "sync" + (mt/db) + [(sync-util/create-sync-step "failure-step" + (fn [_] + (throw ex))) + (sync-util/create-sync-step "should-continue" + (fn [_] + {}))]))] + + ;; make sure we've ran two steps. the first one will have thrown an exception, + ;; but it wasn't an exception that can cause an abort. + (is (= 2 (count (:steps actual)))) + (let [[step-name result] (first (:steps actual))] + (is (= "failure-step" step-name)) + (is (= {:throwable ex :log-summary-fn nil} + (dissoc result :start-time :end-time)))) + (let [[step-name result] (second (:steps actual))] + (is (= "should-continue" step-name)) + (is (= {:log-summary-fn nil} (dissoc result :start-time :end-time)))))))) diff --git a/test/metabase/task/sync_databases_test.clj b/test/metabase/task/sync_databases_test.clj index 2813da9f0a3..ecdd7a50c47 100644 --- a/test/metabase/task/sync_databases_test.clj +++ b/test/metabase/task/sync_databases_test.clj @@ -46,7 +46,7 @@ (update :triggers (partial filter #(str/ends-with? (:key %) (str \. (u/get-id db-or-id))))) (dissoc :class))))) -(defmacro ^:private with-scheduler-setup [& body] +(defmacro with-scheduler-setup [& body] `(tu/with-temp-scheduler (#'sync-db/job-init) ~@body)) @@ -108,18 +108,18 @@ (deftest schedule-changes-only-expected-test (is (= [sync-job (assoc-in fv-job [:triggers 0 :cron-schedule] "0 15 10 ? * MON-FRI")] - (with-scheduler-setup - (mt/with-temp Database [database {:engine :postgres}] - (db/update! Database (u/get-id database) - :cache_field_values_schedule "0 15 10 ? * MON-FRI") - (current-tasks-for-db database))))) + (with-scheduler-setup + (mt/with-temp Database [database {:engine :postgres}] + (db/update! Database (u/get-id database) + :cache_field_values_schedule "0 15 10 ? * MON-FRI") + (current-tasks-for-db database))))) (is (= [(assoc-in sync-job [:triggers 0 :cron-schedule] "0 15 10 ? * MON-FRI") fv-job] (with-scheduler-setup (mt/with-temp Database [database {:engine :postgres}] (db/update! Database (u/get-id database) - :metadata_sync_schedule "0 15 10 ? * MON-FRI") + :metadata_sync_schedule "0 15 10 ? * MON-FRI") (current-tasks-for-db database)))))) (deftest validate-schedules-test -- GitLab