query: memoize resolved index scans per query (join speedup)
ober
79fc97eaed3bdbd2a4e9d3c8b23e0d56843d4a45
--- a/lib/jerboa-db/query/engine.ss +++ b/lib/jerboa-db/query/engine.ss @@ -183,6 +183,14 @@ [b4 (and b3 (unify op-spec (datom-added? d) b3))]) b4)) + ;; Per-query memo for resolved index scans: maps (db index e a v tx) -> the + ;; current datoms for that bound-component lookup, so a nested-loop join that + ;; re-probes the same key across many outer bindings (e.g. track/duration of a + ;; track that recurs in a shared-artist join) pays the index scan + current- + ;; resolution once instead of per binding. Bound fresh per query-db call; + ;; read-only against an immutable db-value, so results are identical. + (def current-query-cache (make-parameter #f)) + (def (evaluate-data-pattern db pattern bindings schema) ;; pattern: (e-spec a-spec v-spec) or (e-spec a-spec v-spec tx-spec) ;; or (e-spec a-spec v-spec tx-spec op-spec) @@ -230,16 +238,18 @@ [(vaet) (make-datom (greatest-fixnum) aid (if v-val v-val +max-val+) (greatest-fixnum) #t)])] - [raw-datoms (dbi-range idx lo hi)] - ;; Apply db-level filters (as-of, since, history) - [datoms (filter (lambda (d) (db-filter-datom? db d)) raw-datoms)]) - ;; For non-history queries, resolve to current state: - ;; Group by (e, a, v), keep only those whose highest-tx datom is added?=#t. - ;; In history mode, return all datoms. + [cache (current-query-cache)] + [ckey (and cache (list db index-name e-val aid v-val tx-val))]) + ;; Resolve to current datoms for this lookup (memoized per query). (let ([effective-datoms - (if (db-value-history? db) - datoms - (resolve-current-datoms datoms))]) + (or (and cache (hashtable-ref cache ckey #f)) + (let* ([raw-datoms (dbi-range idx lo hi)] + [datoms (filter (lambda (d) (db-filter-datom? db d)) raw-datoms)] + [ed (if (db-value-history? db) + datoms + (resolve-current-datoms datoms))]) + (when cache (hashtable-set! cache ckey ed)) + ed))]) ;; Produce new bindings for each matching datom (let loop ([ds effective-datoms] [results '()]) (if (null? ds) @@ -1297,6 +1307,7 @@ ;; ---- Top-level query function ---- (def (query-db parsed db . inputs) + (parameterize ([current-query-cache (make-hashtable equal-hash equal?)]) (let ([find-vars (parsed-query-find-vars parsed)] [in-vars (parsed-query-in-vars parsed)] [where-clauses (parsed-query-where-clauses parsed)] @@ -1316,7 +1327,7 @@ ;; :limit on a shape we don't stream — correct via truncation [limit (limit-take (routed-result parsed db inputs) limit)] ;; no limit — full routing - [else (routed-result parsed db inputs)]))) + [else (routed-result parsed db inputs)])))) (def (query-db-general schema find-vars in-vars where-clauses rules-ht db inputs) (let* ([rules-ht rules-ht]