-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DB caches #3320
DB caches #3320
Conversation
2c116a7
to
75b56ad
Compare
891b9d3
to
5dd4781
Compare
e8c679a
to
f5436bb
Compare
37a972a
to
c3d1689
Compare
Dependent cache reset should happen as soon as the cache is updated, but cache update algorithm is not strictly dependent on *when* it happens. add-watch seems like a convenient utility here.
35aa47e
to
7030002
Compare
@@ -104,51 +104,70 @@ | |||
(not (:expired item)) | |||
(not (:archived item)))) | |||
|
|||
(defn- plus-others [item-count] | |||
(when (> item-count 5) | |||
[:li (str "... plus " (- item-count 5) " more")])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Localization?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix this later. Let's get it merged!
(getx {1 {:resource-id 1} | ||
2 {:resource-id 2} | ||
3 {:resource-id 3 :formid 2} | ||
4 {:resource-id 4 :wfid 2} | ||
5 {:resource-id 5 :formid 2 :wfid 2} | ||
6 {:resource-id 5 :formid nil} | ||
7 {:resource-id 6 :enabled false} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe sometime later we can get to :resource/id
, :form/id
and :workflow/id
for all these.
test/clj/rems/db/test_users.clj
Outdated
(testing "user has different userid in userattrs" | ||
;; NB: raw db call due testing backwards compatible behavior | ||
(db/add-user! {:user "different-userid" :userattrs (json/generate-string {:userid "bad"})}) | ||
(cache/reset! rems.db.users/user-cache) | ||
(is (= {:userid "bad" | ||
:name nil | ||
:email nil} | ||
(rems.db.users/get-user "different-userid")))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should get rid of this behavior at some point. We could perhaps choose to have the master data in the blob, and the other column (e.g. user id) just be there for indexing / searching, but of secondary importance. Or just have the blob if it works fast enough (index and constraint functionally into blob)
:each | ||
reset-caches-fixture | ||
rollback-db-fixture) | ||
(def ^:private owner "owner") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we could just inline this.
(let [response (archive-catalogue-item! false)] | ||
(is (= {:success false | ||
:errors [{:type :t.administration.errors/dependencies-archived | ||
:forms [{:form/id form-id | ||
:form/external-title {:en "form" :fi "form" :sv "form"} | ||
:form/internal-name "catalogue-item-enabled-archived-test-form"}]}]} | ||
response))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose the assertion was simpler before so that the details of the response don't need to be tested here.
(is (= [] | ||
(mapv :id (get-catalogue-items)) | ||
(mapv :id (get-catalogue-items {:archived false})))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe split into two asserts was better on the error diff?
@@ -255,7 +256,7 @@ | |||
api-key user-id) | |||
(is (= {:success false | |||
:errors [{:type "t.administration.errors/in-use-by" | |||
:catalogue-items [{:id cat-id :localizations {}}]}]} | |||
:catalogue-items [{:catalogue-item/id cat-id :localizations {}}]}]} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is technically a change in the API? Perhaps worth a changelog entry.
Also remove tracking reset statistics, because they are not so interesting anymore.
Performance difference here is not very meaningful anymore, since dependency synchronization will always dominate overall execution time.
Cache dependencies can update at any given time. To ensure that caches work correctly, transactions test tries to run read/write operations concurrently, as fast as possible. Most of the time cache read/write operates in correct order, but certain race conditions existed (or may still exist). This change attempts to rectify those. Dependencies are loaded in separate threads, handled by a separate thread pool. Two count down latches are used: one to signal main loader that all sub-dependencies are ready, and one to indicate that sub-dependency thread can release cache locks. Each latch is awaited with timeout, so at maximum they can cause thread locking for duration of the timeout.
When an exception is thrown, for example in middleware, we didn't log anything in some cases. This makes it difficult to know in test what happened. Let's make sure we log the error message also when API-key is used (tests or external calls).
Re-frame and React seem to take a bit of time to update so we can't immediately check the results. We can sleep for a bit. Also let's switch the expected values to the right place.
- Try to simplify the locking strategy to make the code simpler and locking more safe. We want to allow a cache read to happen to stale data so we don't block reads. - Add also a simple performance test with synthetic scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to merge. Only localization issue really needs to be fixed before release. Others can be worked in the follow-up issues.
#2783
This change implements low-level caching for db entities. Motivation is greatly improved performance, especially for db entities that are often queried but rarely changed, due to the overhead in fetching data and serializing it. Most db entities have small memory footprint as well, so they can be cached entirely.
Most cache needs are very basic, but having a standard way of accessing the cache is probably beneficial, for example making sure that db entity cache is ready for use without explicit instructions in every db namespace. With this change nearly all db namespaces implement a common refreshable cache that uses clojure.core.cache.wrapped under hood. Interface is mostly similar, implementation takes care that cache is always up-to-date when accessed.
Renamed references to
rems.db.*
andrems.service.*
so that they are always fully qualified. Many places used ambiguous naming when referring to either db or service functions.Follow up
get-catalogue-items
coerce-from-db
andcoerce-to-db
:expired
) Remove catalogue item start and end columns #2286:form/title
Remove deprecated API #3047{:success true}
from service layer, rather throw on exception and let API decide return typecontext/*request*
in middlewarewrap-context
Possible follow ups
; only admins get enabled and disabled items
detect potential issues with caches(edit: transaction tests now implemented)while all tests should pass, there could still be hidden bugsOther noteworthy changes
raw db access
rems.db.catalogue
/rems.service.catalogue
rems.db.applications
/rems.service.application
rems.common.dependency
com.stuartsierra.dependency
(used in e.g. mondo, caches, dependencies)rems.db.attachments
/rems.service.attachment
rems.kaocha
rems.kaocha/cache-statistics-plugin
that prints table of cache CRUD statistics after successful test run. usable from CLI likelein kaocha integration --plugin rems.kaocha/cache-statistics-plugin
integration
andbrowser
test suites in CircleCI for tracking potential issues in cache usageexample table data from real test run
Implemented DB caches
dependent cache = actual cache but uses cache DAG to reset when main cache updates
for time being = actual cache implemented but not sure if want to keep):
rems.db.attachments
rems.db.attachments/by-application-id
dependent cacherems.db.attachments/license-attachments-cache
(for time being)rems.db.blacklist
rems.db.catalogue
rems.db.catalogue/catalogue-item-localizations-cache
(for time being)rems.db.form
rems.db.licenses
rems.db.licenses/license-localizations-cache
(for time being)rems.db.organization
rems.db.resource
rems.db.roles
rems.db.roles/users-by-role
dependent cacherems.db.user-mappings
rems.db.user-mappings/by-extidattribute
dependent cacherems.db.user-mappings/by-extidvalue
dependent cacherems.db.user-settings
(for time being)rems.db.users
rems.db.workflow
Not yet implemented:
rems.db.entitlements
rems.db.invitation
Existing cache implementations:
rems.db.api-key/api-key-memo
rems.db.events/event-cache
Checklist for author
Remove items that aren't applicable, check items that are done.
Reviewability
Backwards compatibility
Documentation
Testing
Follow-up