-
-
Notifications
You must be signed in to change notification settings - Fork 97
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
Simplify connection updates, add cost center. #658
Changes from all commits
57ed07a
1123b3f
a090a90
87a7451
7e8dd76
bf596b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
(ns ^:no-doc datahike.writer | ||
(:require [superv.async :refer [S thread-try <?-]] | ||
(:require [superv.async :refer [S thread-try <?- go-try]] | ||
[taoensso.timbre :as log] | ||
[datahike.core] | ||
[datahike.writing :as w] | ||
|
@@ -40,77 +40,86 @@ | |
[transaction-queue commit-queue | ||
(thread-try | ||
S | ||
(let [store (:store @(:wrapped-atom connection))] | ||
(do | ||
;; processing loop | ||
(go-loop [] | ||
(if-let [{:keys [op args callback] :as invocation} (<?- transaction-queue)] | ||
(do | ||
(when (> (count transaction-queue-buffer) (* 0.9 transaction-queue-size)) | ||
(log/warn "Transaction queue buffer more than 90% full, " | ||
(count transaction-queue-buffer) "of" transaction-queue-size " filled." | ||
"Reduce transaction frequency.")) | ||
(let [op-fn (write-fn-map op) | ||
res (try | ||
(apply op-fn connection args) | ||
(go-try S | ||
;; delay processing until the writer we are part of in connection is set | ||
(while (not (:writer @(:wrapped-atom connection))) | ||
(<! (timeout 10))) | ||
(loop [old @(:wrapped-atom connection)] | ||
(if-let [{:keys [op args callback] :as invocation} (<?- transaction-queue)] | ||
(do | ||
(when (> (count transaction-queue-buffer) (* 0.9 transaction-queue-size)) | ||
(log/warn "Transaction queue buffer more than 90% full, " | ||
(count transaction-queue-buffer) "of" transaction-queue-size " filled." | ||
"Reduce transaction frequency.")) | ||
(let [old (if-not (= (:max-tx old) (:max-tx @(:wrapped-atom connection))) | ||
(do | ||
(log/warn "DEPRECATED. Connection was changed outside of writer.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it happens that the connection was changed outside of the writer, what would a potential reason be? Could it be that the user of Datahike is using the library incorrectly in some way? If it should never happen in a correct program that we enter this line, maybe we should consider doing something more drastic here than showing a warning, but I am not sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DataScript puts the connection into a plain atom, which worked fine for Datahike until I implemented the distributed memory model last year. Now we have an explicit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I see. Thanks for the explanation :-) |
||
(assoc old :max-tx (:max-tx @(:wrapped-atom connection)))) | ||
old) | ||
|
||
op-fn (write-fn-map op) | ||
res (try | ||
(apply op-fn old args) | ||
;; Only catch ExceptionInfo here (intentionally rejected transactions). | ||
;; Any other exceptions should crash the writer and signal the supervisor. | ||
(catch Exception e | ||
(log/error "Error during invocation" invocation e args) | ||
(catch Exception e | ||
(log/error "Error during invocation" invocation e args) | ||
;; take a guess that a NPE was triggered by an invalid connection | ||
;; short circuit on errors | ||
(put! callback | ||
(if (= (type e) NullPointerException) | ||
(ex-info "Null pointer encountered in invocation. Connection may have been invalidated, e.g. through db deletion, and needs to be released everywhere." | ||
{:type :writer-error-during-invocation | ||
:invocation invocation | ||
:connection connection | ||
:error e}) | ||
e)) | ||
:error))] | ||
(when-not (= res :error) | ||
(when (> (count commit-queue-buffer) (/ commit-queue-size 2)) | ||
(log/warn "Commit queue buffer more than 50% full, " | ||
(count commit-queue-buffer) "of" commit-queue-size " filled." | ||
"Throttling transaction processing. Reduce transaction frequency and check your storage throughput.") | ||
(<! (timeout 50))) | ||
(put! commit-queue [res callback]))) | ||
(recur)) | ||
(do | ||
(close! commit-queue) | ||
(log/debug "Writer thread gracefully closed")))) | ||
(put! callback | ||
(if (= (type e) NullPointerException) | ||
(ex-info "Null pointer encountered in invocation. Connection may have been invalidated, e.g. through db deletion, and needs to be released everywhere." | ||
{:type :writer-error-during-invocation | ||
:invocation invocation | ||
:connection connection | ||
:error e}) | ||
e)) | ||
:error))] | ||
(if-not (= res :error) | ||
(do | ||
(when (> (count commit-queue-buffer) (/ commit-queue-size 2)) | ||
(log/warn "Commit queue buffer more than 50% full, " | ||
(count commit-queue-buffer) "of" commit-queue-size " filled." | ||
"Throttling transaction processing. Reduce transaction frequency and check your storage throughput.") | ||
(<! (timeout 50))) | ||
(put! commit-queue [res callback]) | ||
(recur (:db-after res))) | ||
(recur old)))) | ||
(do | ||
(close! commit-queue) | ||
(log/debug "Writer thread gracefully closed"))))) | ||
;; commit loop | ||
(go-loop [tx (<?- commit-queue)] | ||
(when tx | ||
(let [txs (atom [tx])] | ||
(go-try S | ||
(loop [tx (<?- commit-queue)] | ||
(when tx | ||
(let [txs (into [tx] (take-while some?) (repeatedly #(poll! commit-queue)))] | ||
;; empty channel of pending transactions | ||
(loop [tx (poll! commit-queue)] | ||
(when tx | ||
(swap! txs conj tx) | ||
(recur (poll! commit-queue)))) | ||
(log/trace "Batched transaction count: " (count @txs)) | ||
(log/trace "Batched transaction count: " (count txs)) | ||
;; commit latest tx to disk | ||
(let [db (:db-after (first (peek @txs)))] | ||
(try | ||
(let [start-ts (get-time-ms) | ||
{{:keys [datahike/commit-id datahike/parents]} :meta | ||
:as commit-db} (<?- (w/commit! db nil false)) | ||
commit-time (- (get-time-ms) start-ts)] | ||
(log/trace "Commit time (ms): " commit-time) | ||
(w/add-commit-meta! connection commit-db) | ||
(let [db (:db-after (first (peek txs)))] | ||
(try | ||
(let [start-ts (get-time-ms) | ||
{{:keys [datahike/commit-id]} :meta | ||
:as commit-db} (<?- (w/commit! db nil false)) | ||
commit-time (- (get-time-ms) start-ts)] | ||
(log/trace "Commit time (ms): " commit-time) | ||
(reset! connection commit-db) | ||
;; notify all processes that transaction is complete | ||
(doseq [[res callback] @txs] | ||
(let [res (-> res | ||
(assoc-in [:tx-meta :db/commitId] commit-id) | ||
(assoc :db-after commit-db))] | ||
(put! callback res)))) | ||
(catch Exception e | ||
(doseq [[_ callback] @txs] | ||
(put! callback e)) | ||
(log/error "Writer thread shutting down because of commit error " e) | ||
(close! commit-queue) | ||
(close! transaction-queue))) | ||
(<! (timeout commit-wait-time)) | ||
(recur (<?- commit-queue))))))))])) | ||
(doseq [[tx-report callback] txs] | ||
(let [tx-report (-> tx-report | ||
(assoc-in [:tx-meta :db/commitId] commit-id) | ||
(assoc :db-after commit-db))] | ||
(put! callback tx-report)))) | ||
(catch Exception e | ||
(doseq [[_ callback] txs] | ||
(put! callback e)) | ||
(log/error "Writer thread shutting down because of commit error." e) | ||
(close! commit-queue) | ||
(close! transaction-queue))) | ||
(<! (timeout commit-wait-time)) | ||
(recur (<?- commit-queue)))))))))])) | ||
|
||
;; public API | ||
|
||
|
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.
How do we plan to use
cost-center-fn
? Is it for profiling and collecting statistics about howCachedStorage
is being used?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.
The function can track a compute budget, e.g. for an API user and can throw when the budget is used up. I did not expose an interface to it yet to see whether it will prove useful. In our blockchainesque experiments with the datopia project we were able to render the access to the database permissionless by validating incoming data with Datalog (as a smart contract language) and this is complementary functionality to also make sure that such contracts get interrupted as soon as their budget is consumed. But I think this might be of interest also as an independent functionality just to meter an API.
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.
Thanks for the explanation :-)