Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
M
Metabase
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Engineering Digital Service
Metabase
Commits
bb3194ba
Unverified
Commit
bb3194ba
authored
10 months ago
by
Alexander Solovyov
Committed by
GitHub
10 months ago
Browse files
Options
Downloads
Patches
Plain Diff
move cache business logic to model from api (#41677)
parent
03db4e38
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
src/metabase/api/cache.clj
+24
-89
24 additions, 89 deletions
src/metabase/api/cache.clj
src/metabase/models/cache_config.clj
+93
-0
93 additions, 0 deletions
src/metabase/models/cache_config.clj
test/metabase/api/cache_test.clj
+4
-1
4 additions, 1 deletion
test/metabase/api/cache_test.clj
with
121 additions
and
90 deletions
src/metabase/api/cache.clj
+
24
−
89
View file @
bb3194ba
...
...
@@ -2,16 +2,11 @@
(
:require
[
clojure.walk
:as
walk
]
[
compojure.core
:refer
[
GET
]]
[
java-time.api
:as
t
]
[
metabase.api.common
:as
api
]
[
metabase.api.common.validation
:as
validation
]
[
metabase.db.query
:as
mdb.query
]
[
metabase.events
:as
events
]
[
metabase.models
:refer
[
CacheConfig
Card
]]
[
metabase.models.cache-config
:as
cache-config
]
[
metabase.public-settings.premium-features
:as
premium-features
:refer
[
defenterprise
]]
[
metabase.util
:as
u
]
[
metabase.util.i18n
:refer
[
tru
]]
[
metabase.util.i18n
:refer
[
tru
trun
]]
[
metabase.util.malli.schema
:as
ms
]
[
toucan2.core
:as
t2
]))
...
...
@@ -49,7 +44,6 @@
[]
(
drop-internal-fields
(
CacheStrategy
)))
(
def
^
:private
CachingModel
[
:enum
"root"
"database"
"dashboard"
"question"
])
(
defn-
assert-valid-models
[
model
ids
premium?
]
(
cond
...
...
@@ -68,98 +62,40 @@
"question"
:model/Card
)
:id
[
:in
ids
]))))
(
defn-
audit-caching-change!
[
id
prev
new
]
(
events/publish-event!
:event/cache-config-update
{
:user-id
api/*current-user-id*
:model
:model/CacheConfig
:model-id
id
:details
{
:model
(
or
(
:model
prev
)
(
:model
new
))
:model-id
(
or
(
:model_id
prev
)
(
:model_id
new
))
:old-value
(
dissoc
prev
:model
:model_id
)
:new-value
(
dissoc
new
:model
:model_id
)}}))
(
api/defendpoint
GET
"/"
"Return cache configuration."
[
:as
{{
:strs
[
model
collection
]
:or
{
model
"root"
}}
:query-params
}]
{
model
(
ms/QueryVectorOf
CachingModel
)
{
model
(
ms/QueryVectorOf
cache-config/
CachingModel
)
;; note that `nil` in `collection` means all configurations not scoped to any particular collection
collection
[
:maybe
ms/PositiveInt
]}
(
validation/check-has-application-permission
:setting
)
(
let
[
items
(
if
(
premium-features/enable-cache-granular-controls?
)
(
t2/select
:model/CacheConfig
:model
[
:in
model
]
{
:left-join
[
:report_card
[
:and
[
:=
:model
[
:inline
"question"
]]
[
:=
:model_id
:report_card.id
]
[
:=
:report_card.collection_id
collection
]]
:report_dashboard
[
:and
[
:=
:model
[
:inline
"dashboard"
]]
[
:=
:model_id
:report_dashboard.id
]
[
:=
:report_dashboard.collection_id
collection
]]]
:where
[
:case
[
:=
:model
[
:inline
"question"
]]
[
:!=
:report_card.id
nil
]
[
:=
:model
[
:inline
"dashboard"
]]
[
:!=
:report_dashboard.id
nil
]
:else
true
]})
(
t2/select
:model/CacheConfig
:model
"root"
))]
{
:data
(
mapv
cache-config/row->config
items
)}))
(
when
(
and
(
not
(
premium-features/enable-cache-granular-controls?
))
(
not=
model
[
"root"
]))
(
throw
(
premium-features/ee-feature-error
(
tru
"Granular Caching"
))))
{
:data
(
cache-config/get-list
model
collection
)})
(
api/defendpoint
PUT
"/"
"Store cache configuration."
[
:as
{{
:keys
[
model
model_id
strategy
]
:as
config
}
:body
}]
{
model
CachingModel
{
model
cache-config/
CachingModel
model_id
ms/IntGreaterThanOrEqualToZero
strategy
(
CacheStrategyAPI
)}
(
validation/check-has-application-permission
:setting
)
(
assert-valid-models
model
[
model_id
]
(
premium-features/enable-cache-granular-controls?
))
(
t2/with-transaction
[
_tx
]
(
let
[
data
(
cache-config/config->row
config
)
current
(
t2/select-one
:model/CacheConfig
:model
model
:model_id
model_id
{
:for
:update
})]
{
:id
(
u/prog1
(
mdb.query/update-or-insert!
:model/CacheConfig
{
:model
model
:model_id
model_id
}
(
constantly
data
))
(
audit-caching-change!
<>
current
data
))})))
{
:id
(
cache-config/store!
api/*current-user-id*
config
)})
(
api/defendpoint
DELETE
"/"
"Delete cache configuration."
"Delete cache configuration
s
."
[
:as
{{
:keys
[
model
model_id
]}
:body
}]
{
model
CachingModel
{
model
cache-config/
CachingModel
model_id
(
ms/QueryVectorOf
ms/IntGreaterThanOrEqualToZero
)}
(
validation/check-has-application-permission
:setting
)
(
assert-valid-models
model
model_id
(
premium-features/enable-cache-granular-controls?
))
(
when-let
[
current
(
seq
(
t2/select
:model/CacheConfig
:model
model
:model_id
[
:in
model_id
]))]
(
t2/delete!
:model/CacheConfig
:model
model
:model_id
[
:in
model_id
])
(
doseq
[
item
current
]
(
audit-caching-change!
(
:id
item
)
(
select-keys
item
[
:strategy
:config
:model
:model_id
])
nil
)))
(
cache-config/delete!
api/*current-user-id*
model
model_id
)
nil
)
(
defn-
invalidate-cache-configs
[
database
dashboard
question
]
(
let
[
conditions
(
for
[[
k
vs
]
[[
:database
database
]
[
:dashboard
dashboard
]
[
:question
question
]]
v
vs
]
[
:and
[
:=
:model
(
name
k
)]
[
:=
:model_id
v
]])]
(
if
(
empty?
conditions
)
0
;; using JVM date rather than DB time since it's what are used in cache tasks
(
t2/query-one
{
:update
(
t2/table-name
CacheConfig
)
:set
{
:invalidated_at
(
t/offset-date-time
)}
:where
(
into
[
:or
]
conditions
)}))))
(
defn-
invalidate-cards
[
database
dashboard
question
]
(
let
[
card-ids
(
concat
question
(
when
(
seq
database
)
(
t2/select-fn-vec
:id
[
:model/Card
:id
]
:database_id
[
:in
database
]))
(
when
(
seq
dashboard
)
(
t2/select-fn-vec
:card_id
[
:model/DashboardCard
:card_id
]
:dashboard_id
[
:in
dashboard
])))]
(
if
(
empty?
card-ids
)
0
(
t2/update!
Card
:id
[
:in
card-ids
]
{
:cache_invalidated_at
(
t/offset-date-time
)}))))
(
api/defendpoint
POST
"/invalidate"
"Invalidate cache entries.
...
...
@@ -178,19 +114,18 @@
(
when-not
(
premium-features/enable-cache-granular-controls?
)
(
throw
(
premium-features/ee-feature-error
(
tru
"Granular Caching"
))))
(
let
[
cnt
(
if
(
=
include
:overrides
)
(
invalidate-cards
database
dashboard
question
)
(
invalidate-cache-configs
database
dashboard
question
))]
(
case
[(
=
include
:overrides
)
(
pos?
cnt
)]
[
true
false
]
{
:status
404
:body
{
:message
(
tru
"Could not find any cached questions for the given database, dashboard, or questions ids."
)}}
[
true
true
]
{
:status
200
:body
{
:message
(
tru
"Invalidated cache for {0} question(s)."
cnt
)
:count
cnt
}}
[
false
false
]
{
:status
404
:body
{
:message
(
tru
"Could not find a cache configuration to invalidate."
)}}
[
false
true
]
{
:status
200
:body
{
:message
(
tru
"Invalidated {0} cache configuration(s)."
cnt
)
:count
cnt
}})))
(
let
[
cnt
(
cache-config/invalidate!
{
:databases
database
:dashboards
dashboard
:questions
question
:with-overrides?
(
=
include
:overrides
)})]
{
:status
(
if
(
=
cnt
-1
)
404
200
)
:body
{
:count
cnt
:message
(
case
[(
=
include
:overrides
)
(
if
(
pos?
cnt
)
1
cnt
)]
[
true
-1
]
(
tru
"Could not find a question for the criteria you've specified."
)
[
true
0
]
(
tru
"No cached results to invalidate."
)
[
true
1
]
(
trun
"Invalidated a cached result."
"Invalidated {0} cached results."
cnt
)
[
false
-1
]
(
tru
"Nothing to invalidate."
)
[
false
0
]
(
tru
"No cache configuration to invalidate."
)
[
false
1
]
(
trun
"Invalidated cache configuration."
"Invalidated {0} cache configurations."
cnt
))}}))
(
api/define-routes
)
This diff is collapsed.
Click to expand it.
src/metabase/models/cache_config.clj
+
93
−
0
View file @
bb3194ba
...
...
@@ -3,10 +3,15 @@
(
:require
[
java-time.api
:as
t
]
[
medley.core
:as
m
]
[
metabase.db.query
:as
mdb.query
]
[
metabase.events
:as
events
]
[
metabase.models.interface
:as
mi
]
[
metabase.util
:as
u
]
[
methodical.core
:as
methodical
]
[
toucan2.core
:as
t2
]))
(
def
CachingModel
"Caching is configurable for those models"
[
:enum
"root"
"database"
"dashboard"
"question"
])
(
def
CacheConfig
"Cache configuration"
:model/CacheConfig
)
(
doto
:model/CacheConfig
...
...
@@ -20,6 +25,17 @@
:config
mi/transform-json
:state
mi/transform-json
})
(
defn-
audit-caching-change!
[
user-id
id
prev
new
]
(
events/publish-event!
:event/cache-config-update
{
:user-id
user-id
:model
:model/CacheConfig
:model-id
id
:details
{
:model
(
or
(
:model
prev
)
(
:model
new
))
:model-id
(
or
(
:model_id
prev
)
(
:model_id
new
))
:old-value
(
dissoc
prev
:model
:model_id
)
:new-value
(
dissoc
new
:model
:model_id
)}}))
;;; API
(
defn
root-strategy
...
...
@@ -50,3 +66,80 @@
:model_id
model_id
:strategy
(
:type
strategy
)
:config
(
dissoc
strategy
:type
)})
(
defn
get-list
"Get a list of cache configurations for given `models` and a `collection`."
[
models
collection
]
(
->>
(
t2/select
:model/CacheConfig
:model
[
:in
models
]
{
:left-join
[
:report_card
[
:and
[
:=
:model
[
:inline
"question"
]]
[
:=
:model_id
:report_card.id
]
[
:=
:report_card.collection_id
collection
]]
:report_dashboard
[
:and
[
:=
:model
[
:inline
"dashboard"
]]
[
:=
:model_id
:report_dashboard.id
]
[
:=
:report_dashboard.collection_id
collection
]]]
:where
[
:case
[
:=
:model
[
:inline
"question"
]]
[
:!=
:report_card.id
nil
]
[
:=
:model
[
:inline
"dashboard"
]]
[
:!=
:report_dashboard.id
nil
]
:else
true
]})
(
mapv
row->config
)))
(
defn
store!
"Store cache configuration in DB."
[
user-id
{
:keys
[
model
model_id
]
:as
config
}]
(
t2/with-transaction
[
_tx
]
(
let
[
data
(
config->row
config
)
current
(
t2/select-one
:model/CacheConfig
:model
model
:model_id
model_id
{
:for
:update
})]
(
u/prog1
(
mdb.query/update-or-insert!
:model/CacheConfig
{
:model
model
:model_id
model_id
}
(
constantly
data
))
(
audit-caching-change!
user-id
<>
current
data
)))))
(
defn
delete!
"Delete cache configuration (possibly multiple), identified by a `model` and a vector of `model-ids`."
[
user-id
model
model-ids
]
(
when-let
[
current
(
seq
(
t2/select
:model/CacheConfig
:model
model
:model_id
[
:in
model-ids
]))]
(
t2/delete!
:model/CacheConfig
:model
model
:model_id
[
:in
model-ids
])
(
doseq
[
item
current
]
(
audit-caching-change!
user-id
(
:id
item
)
(
select-keys
item
[
:strategy
:config
:model
:model_id
])
nil
))))
;;; Invalidation
(
defn-
invalidate-cards
[
databases
dashboards
questions
]
(
let
[
card-ids
(
concat
questions
(
when
(
seq
databases
)
(
t2/select-fn-vec
:id
[
:model/Card
:id
]
:database_id
[
:in
databases
]))
(
when
(
seq
dashboards
)
(
t2/select-fn-vec
:card_id
[
:model/DashboardCard
:card_id
]
:dashboard_id
[
:in
dashboards
])))]
(
if
(
empty?
card-ids
)
-1
(
t2/update!
:model/Card
:id
[
:in
card-ids
]
{
:cache_invalidated_at
(
t/offset-date-time
)}))))
(
defn-
invalidate-cache-configs
[
databases
dashboards
questions
]
(
let
[
conditions
(
for
[[
k
vs
]
[[
:database
databases
]
[
:dashboard
dashboards
]
[
:question
questions
]]
v
vs
]
[
:and
[
:=
:model
(
name
k
)]
[
:=
:model_id
v
]])]
(
if
(
empty?
conditions
)
-1
;; using JVM date rather than DB time since it's what are used in cache tasks
(
t2/query-one
{
:update
(
t2/table-name
CacheConfig
)
:set
{
:invalidated_at
(
t/offset-date-time
)}
:where
(
into
[
:or
]
conditions
)}))))
(
defn
invalidate!
"Invalidate cache configuration. Accepts lists of ids for different types of models. If `with-overrides?` is passed,
then invalidates cache on each individual card suitable for criteria."
[{
:keys
[
databases
dashboards
questions
with-overrides?
]}]
(
if
with-overrides?
(
invalidate-cards
databases
dashboards
questions
)
(
invalidate-cache-configs
databases
dashboards
questions
)))
This diff is collapsed.
Click to expand it.
test/metabase/api/cache_test.clj
+
4
−
1
View file @
bb3194ba
...
...
@@ -15,7 +15,10 @@
(
mt/user-http-request
:crowberto
:put
402
"cache/"
{
:model
"question"
:model_id
1
:strategy
{
:type
"nocache"
}}))))
:strategy
{
:type
"nocache"
}})))
(
is
(
=
"Granular Caching is a paid feature not currently available to your instance. Please upgrade to use it. Learn more at metabase.com/upgrade/"
(
mt/user-http-request
:crowberto
:get
402
"cache/"
:model
"question"
))))
(
testing
"Can operate on root settings though"
(
is
(
mt/user-http-request
:crowberto
:put
200
"cache/"
{
:model
"root"
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment