xodus 游标的有效期是多长时间?

How long is a xodus cursor valid?

我正在使用 Clojure 中的 xodus 并正在评估以惰性方式遍历所有 key/value 对的可能性,就像 Clojure 中常见的那样。

我最初的理解是,所有通过 Cursor 的数据访问都应该发生在只读 Transaction 内部,因为每个事务都在其自己的数据库快照上运行。

但是如果您在事务内部创建了一个游标,看起来在事务结束后仍然可以继续迭代相同的事务快照。事实上,即使关闭了游标,它似乎仍然可以使用。

我想这不是一种安全的方法,因为我怀疑 gc 有时会使快照无效。

对于特定事务中的游标究竟可以使用多长时间,我仍然有点困惑,我无法在文档中找到答案。

下面是 Clojure 中的一个例子,演示了在事务完成后和键被重新分配后游标仍然可以用来检索数据的事实。

使用 xodus 1.3.232.

(ns chat-bot.xodus-cursor
  (:import [jetbrains.exodus.env Environments StoreConfig TransactionalComputable]
           [jetbrains.exodus.bindings IntegerBinding]))

(def store-name "test")

(defn startup []
  (Environments/newInstance "cursor-test"))

(defn shutdown [env]
  (.close env))

(defn fill [env n base]
  (.computeInTransaction
    env
    (reify TransactionalComputable
      (compute [this txn]
        (let [store (.openStore env store-name StoreConfig/WITHOUT_DUPLICATES txn)]
          (doseq [k (range n)]
            (.put store txn (IntegerBinding/intToEntry k) (IntegerBinding/intToEntry (+ base k)))))))))

(defn lazy-cursor [txn cursor has-next]
  (lazy-seq
    (when has-next
      (let [kv [(IntegerBinding/entryToInt (.getKey cursor)) (IntegerBinding/entryToInt (.getValue cursor))]]
        (println "realized" kv "txn finished" (.isFinished txn))
        (cons kv (lazy-cursor txn cursor (.getNext cursor)))))))

(defn get-seq [env]
  (.computeInReadonlyTransaction
    env
    (reify TransactionalComputable
      (compute [this txn]
        (let [store (.openStore env store-name StoreConfig/WITHOUT_DUPLICATES txn)]
          (with-open [cursor (.openCursor store txn)]
            (lazy-cursor txn cursor (.getNext cursor))))))))

(defn do-it []
  (let [env (startup)]
    (fill env 5 0) ;; put some data into the store
    (let [kvs0 (get-seq env)] ;; get the data sequence, not realized yet
      (fill env 5 10) ;; override the data
      (let [kvs1 (get-seq env)] ;; get the data sequence again
        (shutdown env)
        [kvs0 kvs1])))) ;; return both original and overridden data sequence


输出将是

(def s (do-it))  ;; sequences are still not realized

s  ;; output sequences to realize them

realized [0 0] txn finished true
realized [1 1] txn finished true
realized [2 2] txn finished true
realized [3 3] txn finished true
realized [4 4] txn finished true
realized [0 10] txn finished true
realized [1 11] txn finished true
realized [2 12] txn finished true
realized [3 13] txn finished true
realized [4 14] txn finished true
=> [([0 0] [1 1] [2 2] [3 3] [4 4]) ([0 10] [1 11] [2 12] [3 13] [4 14])]

;; the original and the re-assigned key/value sequence is returned

只要您希望在一段时间后最终完成(中止)它们,您就可以保持只读事务未完成。未完成的事务阻止删除数据库 GC 移动的旧数据。因此,您可以保持事务未完成的时间取决于您的工作量:写入负载越大,时间越短。例如,如果没有那么多写入并且数据库大小在几个小时内增加了 1-2-3%,那么您可以将只读事务保留几个小时而不会对性能产生任何影响。唯一的缺点是如果您的应用程序无法正常关闭数据库,那么在下次启动时它将从头开始计算文件利用率,即它将在后台遍历整个数据库。