From 2d6d12c8d6ee5e33a92215c138074d1ea35fa84f Mon Sep 17 00:00:00 2001
From: Braden Shepherdson <braden@metabase.com>
Date: Thu, 1 Feb 2024 16:03:27 -0500
Subject: [PATCH] [MLv2] Cache JS -> CLJS metadata conversion (#38375)

This previous got rebuilt from scratch over and over, since different
queries being handed the same JS `Metadata` instance were returning
completely separate `MetadataProvider`s.

The FE replaces the `Metadata` instance as new metadata arrives from the
BE, so the `metabase.lib.cache/side-channel-cache` which is attached to
the input object is implicitly invalidated when the old `Metadata` is
thrown away.
---
 src/metabase/lib/cache.cljc       | 33 ++++++++++++++++---------------
 src/metabase/lib/js/metadata.cljs | 13 ++++++++++--
 2 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/src/metabase/lib/cache.cljc b/src/metabase/lib/cache.cljc
index ec573c0ec60..391e98486bf 100644
--- a/src/metabase/lib/cache.cljc
+++ b/src/metabase/lib/cache.cljc
@@ -10,19 +10,20 @@
 
   If there is not already a key `subkey` in the map, calls `(f x)` and caches the value at `subkey`.
   If there is a value at `subkey`, it is returned directly."
-  [subkey x f]
-  (comment subkey) ; Avoids lint warning for half-unused `subkey`.
-  #?(:clj  (f x)
-     :cljs (if (or (object? x) (map? x))
-             (do
-               (when-not (.-__mbcache ^js x)
-                 (set! (.-__mbcache ^js x) (atom {})))
-               (if-let [cache (.-__mbcache ^js x)]
-                 (if-let [cached (get @cache subkey)]
-                   cached
-                   ;; Cache miss - generate the value and cache it.
-                   (let [value (f x)]
-                     (swap! cache assoc subkey value)
-                     value))
-                 (f x)))
-             (f x))))
+  ([subkey x f] (side-channel-cache subkey x f false))
+  ([subkey x f force?]
+   (comment subkey, force?) ; Avoids lint warning for half-unused inputs.
+   #?(:clj  (f x)
+      :cljs (if (or force? (object? x) (map? x))
+              (do
+                (when-not (.-__mbcache ^js x)
+                  (set! (.-__mbcache ^js x) (atom {})))
+                (if-let [cache (.-__mbcache ^js x)]
+                  (if-let [cached (get @cache subkey)]
+                    cached
+                    ;; Cache miss - generate the value and cache it.
+                    (let [value (f x)]
+                      (swap! cache assoc subkey value)
+                      value))
+                  (f x)))
+              (f x)))))
diff --git a/src/metabase/lib/js/metadata.cljs b/src/metabase/lib/js/metadata.cljs
index e5f1a2897a7..1f83f68c0c1 100644
--- a/src/metabase/lib/js/metadata.cljs
+++ b/src/metabase/lib/js/metadata.cljs
@@ -6,6 +6,7 @@
    [goog]
    [goog.object :as gobject]
    [medley.core :as m]
+   [metabase.lib.cache :as lib.cache]
    [metabase.lib.metadata.protocols :as lib.metadata.protocols]
    [metabase.lib.util :as lib.util]
    [metabase.util :as u]
@@ -24,6 +25,7 @@
 ;;;
 ;;; where keys are a map of String ID => metadata
 
+;; TODO: This is always wrapped with `keyword` in its usage so that may as well be memoized too.
 (def ^:private ^{:arglists '([k])} memoized-kebab-key
   "Even tho [[u/->kebab-case-en]] has LRU memoization, plain memoization is significantly faster, and since the keys
   we're parsing here are bounded it's fine to memoize this stuff forever."
@@ -506,8 +508,8 @@
     (object-get "settings")
     (object-get (name setting-key))))
 
-(defn metadata-provider
-  "Use a `metabase-lib/metadata/Metadata` as a [[metabase.lib.metadata.protocols/MetadataProvider]]."
+(defn- metadata-provider*
+  "Inner implementation for [[metadata-provider]], which wraps this with a cache."
   [database-id unparsed-metadata]
   (let [metadata (parse-metadata unparsed-metadata)]
     (log/debug "Created metadata provider for metadata")
@@ -535,6 +537,13 @@
              form))
          metadata)))))
 
+(defn metadata-provider
+  "Use a `metabase-lib/metadata/Metadata` as a [[metabase.lib.metadata.protocols/MetadataProvider]]."
+  [database-id unparsed-metadata]
+  (lib.cache/side-channel-cache (str database-id) unparsed-metadata
+                                (partial metadata-provider* database-id)
+                                true #_force?))
+
 (def parse-column
   "Parses a JS column provided by the FE into a :metadata/column value for use in MLv2."
   (parse-object-fn :field))
-- 
GitLab