Code for creating the connection pool for the application DB and setting it as the default Toucan connection. | (ns metabase.db.connection-pool-setup (:require [java-time.api :as t] [metabase.config :as config] [metabase.connection-pool :as connection-pool] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms]) (:import (com.mchange.v2.c3p0 ConnectionCustomizer PoolBackedDataSource))) |
(set! *warn-on-reflection* true) | |
(def ^:private latest-activity (atom nil)) | |
(def ^:private ^java.time.Duration recent-window-duration (t/seconds 15)) | |
(defn- recent-activity?* [activity duration] (when activity (t/after? activity (t/minus (t/offset-date-time) duration)))) | |
Returns true if there has been recent activity. Define recent activity as an application db connection checked in, checked out, or acquired within [[recent-window-duration]]. Check-in means a query succeeded and the db connection is no longer needed. | (defn recent-activity? [] (recent-activity?* @latest-activity recent-window-duration)) |
(defrecord DbActivityTracker [] ConnectionCustomizer (onAcquire [_ _connection _identity-token] (reset! latest-activity (t/offset-date-time))) (onCheckIn [_ _connection _identity-token] (reset! latest-activity (t/offset-date-time))) (onCheckOut [_ _connection _identity-token] (reset! latest-activity (t/offset-date-time))) (onDestroy [_ _connection _identity-token])) | |
c3p0 allows for hooking into lifecycles with its interface ConnectionCustomizer. https://www.mchange.com/projects/c3p0/apidocs/com/mchange/v2/c3p0/ConnectionCustomizer.html. But Clojure defined code is in memory in a dynamic class loader not available to c3p0's use of Class/forName. Luckily it looks up the instances in a cache which I pre-seed with out impl here. Issue for better access here: https://github.com/swaldman/c3p0/issues/166 | (defn- register-customizer! [^Class klass] (let [field (doto (.getDeclaredField com.mchange.v2.c3p0.C3P0Registry "classNamesToConnectionCustomizers") (.setAccessible true))] (.put ^java.util.HashMap (.get field com.mchange.v2.c3p0.C3P0Registry) (.getName klass) (.newInstance klass)))) |
(register-customizer! DbActivityTracker) | |
Options for c3p0 connection pool for the application DB. These are set in code instead of a properties file because we use separate options for data warehouse DBs. See https://www.mchange.com/projects/c3p0/#configuringconnectiontesting for an overview of the options used below (jump to the 'Simple advice on Connection testing' section.) | (def ^:private application-db-connection-pool-props (merge {"idleConnectionTestPeriod" 60 "connectionCustomizerClassName" (.getName DbActivityTracker)} ;; only merge in `max-pool-size` if it's actually set, this way it doesn't override any things that may have been ;; set in `c3p0.properties` (when-let [max-pool-size (config/config-int :mb-application-db-max-connection-pool-size)] {"maxPoolSize" max-pool-size}))) |
(mu/defn connection-pool-data-source :- (ms/InstanceOfClass PoolBackedDataSource) "Create a connection pool [[javax.sql.DataSource]] from an unpooled [[javax.sql.DataSource]] `data-source`. If `data-source` is already pooled, this will return `data-source` as-is." ^PoolBackedDataSource [db-type :- :keyword ^PoolBackedDataSource data-source :- (ms/InstanceOfClass javax.sql.DataSource)] (if (instance? PoolBackedDataSource data-source) data-source (let [ds-name (format "metabase-%s-app-db" (name db-type)) pool-props (assoc application-db-connection-pool-props "dataSourceName" ds-name)] (com.mchange.v2.c3p0.DataSources/pooledDataSource data-source (connection-pool/map->properties pool-props))))) | |