Datomic 中的查询结果分页
Query result pagination in Datomic
我想解决一个假设情况,但找不到理想的答案。假设您有一个可以从查询中 return 编辑的庞大数据集,您如何对其进行分页以使对内存的影响最小? datoms API, iterating over the datoms and filtering one by one? The index-range API,但我必须做与 datoms API 相同的事情,遍历项目并逐一过滤?执行一个 return 只有 id 的初始查询,然后对这些 id 进行分页,以便它们可以在另一个查询中使用以检索整个数据集?
在 SQL 中,您通常可以在查询本身中定义分页:
SELECT col1, col2, ...
FROM ...
WHERE ...
ORDER BY -- this is a MUST there must be ORDER BY statement
-- the paging comes here
OFFSET 10 ROWS -- skip 10 rows
FETCH NEXT 10 ROWS ONLY; -- take 10 rows
你看过这个页面吗:http://docs.datomic.com/query.html#memory-usage
好像说所有的中间结果都要装进内存。我认为这也适用于最终结果。
您可以尝试询问:https://forum.datomic.com/
旁注:当 Datomic returns 和 entity 时,它是 "lazy map" 的一种形式,即不完全可见明确使其具体化,例如
(let [plain-map (into {} entity-map) ]
(println plain-map))
有很多事情要考虑。
首先,在撰写本文时,Datomic 附带的 Datalog 实现是急切的,并且不会溢出到磁盘,这意味着 Datalog 查询的结果集必须适合内存。
这并不意味着 Datalog 与大结果不兼容,因为您可以让每个 Datalog 查询只处理一小部分数据。例如,您可以使用 Datalog 来计算查询的 'logical' 部分(return 的实体),以及实体 API 或 Pull API 到(懒惰地)计算查询的 'content' 部分(每个实体的 return 属性)。鉴于实体 ID 只是一个 Java 长(8 字节),这可以为您节省两个数量级的内存占用量之一。使用实体 API:
的示例
(defn export-customers
[db search-criteria]
(->>
;; logical part - Datalog-based, eager
(d/q '[:find [?customer ...] :in % $ ?search-criteria :where
(customer-matches-criteria ?search-criteria ?customer)]
(my-rules) db search-criteria)
;; content part - Entity API based, lazy
(map (fn [eid]
(let [customer (d/entity db eid)]
(select-keys customer
[:customer/id
:customer/email
:customer/firstName
:customer/lastName
:customer/subscription-time]))))
))
您可以通过急切地将整个结果存储在辅助 blob 存储中来补充这种方法,然后针对它进行轮询以进行分页。
如果您的查询逻辑不是太复杂,您也可以想象根本不使用 Datalog,例如使用原始索引访问(例如使用 Datoms API 或索引范围 API)以一种懒惰的方式。
最后,您应该考虑到 Datomic 可能不适合为您的分析查询提供服务。由于 Datomic 的变化检测是微不足道的,因此将派生数据流式传输到二级存储相当容易,二级存储可以更好地计算分析查询(例如 ElasticSearch、Google BigQuery、PostgreSQL 等)
我想解决一个假设情况,但找不到理想的答案。假设您有一个可以从查询中 return 编辑的庞大数据集,您如何对其进行分页以使对内存的影响最小? datoms API, iterating over the datoms and filtering one by one? The index-range API,但我必须做与 datoms API 相同的事情,遍历项目并逐一过滤?执行一个 return 只有 id 的初始查询,然后对这些 id 进行分页,以便它们可以在另一个查询中使用以检索整个数据集?
在 SQL 中,您通常可以在查询本身中定义分页:
SELECT col1, col2, ...
FROM ...
WHERE ...
ORDER BY -- this is a MUST there must be ORDER BY statement
-- the paging comes here
OFFSET 10 ROWS -- skip 10 rows
FETCH NEXT 10 ROWS ONLY; -- take 10 rows
你看过这个页面吗:http://docs.datomic.com/query.html#memory-usage
好像说所有的中间结果都要装进内存。我认为这也适用于最终结果。
您可以尝试询问:https://forum.datomic.com/
旁注:当 Datomic returns 和 entity 时,它是 "lazy map" 的一种形式,即不完全可见明确使其具体化,例如
(let [plain-map (into {} entity-map) ]
(println plain-map))
有很多事情要考虑。
首先,在撰写本文时,Datomic 附带的 Datalog 实现是急切的,并且不会溢出到磁盘,这意味着 Datalog 查询的结果集必须适合内存。
这并不意味着 Datalog 与大结果不兼容,因为您可以让每个 Datalog 查询只处理一小部分数据。例如,您可以使用 Datalog 来计算查询的 'logical' 部分(return 的实体),以及实体 API 或 Pull API 到(懒惰地)计算查询的 'content' 部分(每个实体的 return 属性)。鉴于实体 ID 只是一个 Java 长(8 字节),这可以为您节省两个数量级的内存占用量之一。使用实体 API:
的示例(defn export-customers
[db search-criteria]
(->>
;; logical part - Datalog-based, eager
(d/q '[:find [?customer ...] :in % $ ?search-criteria :where
(customer-matches-criteria ?search-criteria ?customer)]
(my-rules) db search-criteria)
;; content part - Entity API based, lazy
(map (fn [eid]
(let [customer (d/entity db eid)]
(select-keys customer
[:customer/id
:customer/email
:customer/firstName
:customer/lastName
:customer/subscription-time]))))
))
您可以通过急切地将整个结果存储在辅助 blob 存储中来补充这种方法,然后针对它进行轮询以进行分页。
如果您的查询逻辑不是太复杂,您也可以想象根本不使用 Datalog,例如使用原始索引访问(例如使用 Datoms API 或索引范围 API)以一种懒惰的方式。
最后,您应该考虑到 Datomic 可能不适合为您的分析查询提供服务。由于 Datomic 的变化检测是微不足道的,因此将派生数据流式传输到二级存储相当容易,二级存储可以更好地计算分析查询(例如 ElasticSearch、Google BigQuery、PostgreSQL 等)