This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-03-29
Channels
- # announcements (8)
- # babashka (41)
- # beginners (45)
- # calva (23)
- # cider (17)
- # cljdoc (2)
- # cljfx (9)
- # clojure (40)
- # clojure-bay-area (13)
- # clojure-czech (4)
- # clojure-europe (46)
- # clojure-germany (6)
- # clojure-nl (13)
- # clojure-serbia (3)
- # clojure-uk (9)
- # clojurescript (76)
- # conjure (7)
- # cursive (5)
- # data-science (6)
- # deps-new (7)
- # fulcro (41)
- # graalvm (2)
- # jobs (6)
- # lsp (10)
- # malli (1)
- # mid-cities-meetup (1)
- # off-topic (77)
- # polylith (40)
- # re-frame (18)
- # releases (1)
- # remote-jobs (9)
- # reveal (1)
- # rewrite-clj (1)
- # shadow-cljs (11)
- # tools-deps (3)
- # tree-sitter (3)
- # vim (8)
- # xtdb (45)
Some Monday morning reading for you folks: https://opencrux.com/blog/crux-strength-of-the-record.html 🙂 Feedback welcome: <mailto:[email protected]|[email protected]> or DM
@U01AVNG2XNF the link on https://opencrux.com/articles/what-is-crux.html to this article seems to be broken
@U7JKD4ENQ Thanks for pointing that out. I think that's a bug I've (repeatedly) encountered with the Antora build used for the docs. I'll see if I can get it fixed.
Fixed.
I'm trying to solve my open-db woes once and for all, this time with the help of java.lang.ref.Cleaner. I want to cache the closeable returned by open-db
, refer to it from a bunch of different threads, then when it's time for it to go, evict it from the cache, which should cause it to be GC'd, then I want to close the associated resources. But it seems like this particular API is troublesome, because it only runs the cleanup action after the reference has been GC'd, so I would have to call .close
on something that doesn't exist.. is there some way to close the associated rocks resource without holding the reference to the associated jvm object?
this bit of code I have seems to be broken as the cleanup lambda holds a reference to v
, the open-db
object
(let [cleaner (java.lang.ref.Cleaner/create)] (cache/make-cache {:maximumSize size :removalListener (cloffeine.common/reify-removal-listener (fn [k v _] (u/log ::evict-db-cache :key k) (.register cleaner v (fn [] (u/log ::close-db-cache :key k) (.close v)))))}))
this may be more of a java question (of which I know very little) than a crux api question
You're undoubtedly way ahead of me on this...but I guess storing the reference you get from open-db
in an atom doesn't cut it? 🙂 (and making sure to .close
it before swap!
/ reset!
)
the question is, when does it get closed? and who closes it? the way I have it, each http request knows the current tx-basis and that's the cache key, so one request will reuse the same open-db across multiple threads (and hundreds of calls), and it could even be shared across requests if the next one is on the same tx-basis
I think this works:
(defn- close-snapshot [snapshot] (.close ^java.io.Closeable snapshot)) (let [cleaner (java.lang.ref.Cleaner/create)] (cache/make-cache {:maximumSize size :removalListener (cloffeine.common/reify-removal-listener (fn [k v _] (u/log ::evict-db-cache :key k) (when-let [snapshot (:index-snapshot v)] (u/log ::snapshot :key k) (.register cleaner v (fn [] (u/log ::close-db-cache :key k) (close-snapshot snapshot))))))}))
ah I'm glad, good stuff 🙂 we'll discuss this after the standup today to make sure you're not missing any other tricks
I realise I never replied again on this! It's not something we were able to judge easily, so I can't really give you confidence/reassurance one way or the other. Did you decide to run with it anyway? Has it been stable?
ah yeah, no problem. Actually trying to work through some details at this very moment, but I think the logic is mostly ok. Just resolved a puzzle due to integrant halt order (I actually assoc the db-cache as part of the crux node itself, for a nicer API (i.e. never call crux/db, only pass around node) so I need to explicitly halt it as part of the crux node halt process, rather than rely on the default order integrant shuts it down in). the new snapshot check on shutdown is helping out a lot
may be a bit brittle, but I think the perf/ergonomic gains are likely worth it for my use case
ah, speak of the devil. just ran into a familiar
pure virtual method called terminate called without an active exception
fatal again, the kind that just leaves a system coredump (no jvm log)so far the only time I've seen this happen is when I have the app also running as an updater, which uses manifold.time/every
to schedule the updates, so maybe some weird stuff is going on there. I've seen weird things with manifold in general
I actually don't have a complete mental model for how the Rocks iterators would get shared between JVM threads...have you studied the parallelism in action at all? A single db-cache sounds pretty nice but maybe it introduces other limitations (asides from all this lifecycle complexity)
hmm.. I was hoping that crux handled the underlying iterators not being thread-safe actually. I was under the impression that seek() is called just once, to get to the point-in-time state
oh, I should mention that I noticed unclosed snapshots preventing shutdown even without open-db
and all the cache fanciness, when it was running as an updater. no crashes though. So that's why I think the behavior from manifold.time/every
may have something to do with it
Hey again, I caught up with James and Hakan about this briefly at the standup today. My understanding is still rather far from complete but the basic summary is that "db has not been explicitly designed to be thread safe" ...so it's not clear whether what you've attempted is fundamentally broken at some level. Can you write more about what you're trying to achieve? Or maybe I could setup a call for later in the month and you guys can talk it out?
no worries, the biggest win is just avoiding (crux/db) calls hundreds of times a request, so I think that gets me most of the way there without data races. open-db
probably isn't too important for perf and I don't care enough to even profile it, so no need to take up your attention
mostly I just wanted to push my luck, having set up the stuff to cache crux/db
calls already, and figured I might be able to cache open-db
for free. maybe it would still be a win with some synchronization, but that would take work to figure out
btw, it might be worth exploring caching repeated crux/db
calls within crux itself -- apparently datomic does this
not sure if your benchmarks hit the many n+1 queries use case, but that's a great selling point of datomic/crux
> it might be worth exploring caching repeated `crux/db` calls within crux itself -- apparently datomic does this
Yeah I guess that's why we have open-db
, because getting that kind of caching abstraction right was harder / more complex, but I don't understand the exact history. The original API for cache sharing was kv/new-snapshot
which you'd pass into the db
call.
You may well be right that there is something cleverer to be done - feel free to open an issue!
ah, yeah it would be harder than datomic cause of bitemporality. I just do this
(defn db [node] (if-let [basis (::basis node)] (if (:crux.tx/tx-id basis) (cache/get (::db-cache node) basis (fn [basis] (crux/open-db node basis))) (crux/db node basis)) (crux/db node)))
I guess the ts-devices / ts-weather benchmarks stress the n+1 mechanism https://github.com/juxt/crux/blob/master/crux-bench/src/crux/bench/ts_devices.clj#L219
no transducer? https://github.com/juxt/crux/blob/master/crux-bench/src/crux/bench/ts_devices.clj#L236
aha, good spot, think it'll make a difference? I'm guessing Crux I/O time dominates everything, but maybe that's an easy speedup
I might try playing around with it, honestly.. bit of a guilty pleasure to optimize criterium scores
something I notice about crux is that it's maybe the only codebase I've read that uses for
, which I think would be a bit slower since it builds a lazy seq
believe it or not he has at one point spiked an alternative version of the query engine that compiles the Datalog into a giant for