Datomic 的递归数据日志查询真的很慢
Recursive Datalog queries for Datomic really slow
我目前正在评估 Datomic 的用例,用于存储和查询构成 ontology 的已解析符号。数据库中总共有 225122 个符号(实体)(因此这是一个相当大的 ontology,但对于数据库来说应该不是什么大问题)。
结构很标准,符号有
- parent 包含它们的符号(如子符号等)
- supersymbols(它们继承自的符号)
为了方便地访问符号,我们为每个符号设置了唯一的 name
。这加起来就是以下 Datomic 模式:
[{:db/ident :ml/name,
:db/valueType :db.type/string,
:db/cardinality :db.cardinality/one,
:db/unique :db.unique/identity}
{:db/ident :ml/parent,
:db/valueType :db.type/ref,
:db/index true,
:db/cardinality :db.cardinality/one}
{:db/ident :ml/superclass,
:db/valueType :db.type/ref,
:db/index true,
:db/cardinality :db.cardinality/one}]
现在我有了最基本的递归查询"give me all symbols that are (transitively) contained in symbol p
"。用原子术语来说:
(def rules
'[
[(ubersymbol ?c ?p) (?c :ml/parent ?p)]
[(ubersymbol ?c ?p) (?c :ml/parent ?c1) (ubersymbol ?c1 ?p) ]
])
(q '[:find ?c ?n :in $ % :where
(ubersymbol ?c ?d) [?d :ml/name "name of a root symbol"] [?c :ml/name ?n]]
current-db rules)
查询本身(因此是一个中等大小的符号)需要 5 和 5.5 秒和 returns 80 次点击. 不是毫秒,而是真正的秒。这只是我想询问的关于数据集的最基本的查询(它旨在从网络工具中使用,以帮助建模者理解 ontology 的结构)。
我是 运行 datomic-pro-0.9.5554
,有一个内存数据库并使用对等库(我按照 "getting started" 指南中的描述启动了服务器。
非常感谢您为 Datomic 提供帮助。
马库斯
编辑
正如fricke
自己发现的那样,是子句排序的问题,但在查询中,而不是在规则集中。一个更有效的版本是:
[:find ?c ?n :in $ % :where
[?d :ml/name "name of a root symbol"]
(ubersymbol ?c ?d)
[?c :ml/name ?n]]
以上查询可以通过以下方式进一步改进:
- 在查询正文中使用查询参数而不是动态参数
- 使用查找引用通过
:ml/name
解析输入实体
产生:
(d/q
'[:find ?c ?n :in % $ ?d :where
(ubersymbol ?c ?d)
[?c :ml/name ?n]]
rules current-db [:ml/name "name of a root symbol"])
我的理论是,您的规则不是以 Datalog 可以针对此读取模式优化的方式编写的 - 可能会导致遍历所有实体。我建议将它们改写如下:
[[(ubersymbol ?c ?p)
(?c :ml/parent ?p)]
[(ubersymbol ?c ?p)
;; we bind a child of the ancestor, instead of a parent of the descendant
(?c1 :ml/parent ?p)
(ubersymbol ?c ?c1)]]
这种编写规则集的方式经过优化,可以找到某个节点的后代。您最初编写它的方式经过优化,可以找到某个节点的祖先。
在我的机器上使用 Datomic 0.9.5385 在 50000 个实体的平衡二叉树上进行的快速基准测试表明,您使用第二种方法确实获得了所需的性能。
我目前正在评估 Datomic 的用例,用于存储和查询构成 ontology 的已解析符号。数据库中总共有 225122 个符号(实体)(因此这是一个相当大的 ontology,但对于数据库来说应该不是什么大问题)。
结构很标准,符号有
- parent 包含它们的符号(如子符号等)
- supersymbols(它们继承自的符号)
为了方便地访问符号,我们为每个符号设置了唯一的 name
。这加起来就是以下 Datomic 模式:
[{:db/ident :ml/name,
:db/valueType :db.type/string,
:db/cardinality :db.cardinality/one,
:db/unique :db.unique/identity}
{:db/ident :ml/parent,
:db/valueType :db.type/ref,
:db/index true,
:db/cardinality :db.cardinality/one}
{:db/ident :ml/superclass,
:db/valueType :db.type/ref,
:db/index true,
:db/cardinality :db.cardinality/one}]
现在我有了最基本的递归查询"give me all symbols that are (transitively) contained in symbol p
"。用原子术语来说:
(def rules
'[
[(ubersymbol ?c ?p) (?c :ml/parent ?p)]
[(ubersymbol ?c ?p) (?c :ml/parent ?c1) (ubersymbol ?c1 ?p) ]
])
(q '[:find ?c ?n :in $ % :where
(ubersymbol ?c ?d) [?d :ml/name "name of a root symbol"] [?c :ml/name ?n]]
current-db rules)
查询本身(因此是一个中等大小的符号)需要 5 和 5.5 秒和 returns 80 次点击. 不是毫秒,而是真正的秒。这只是我想询问的关于数据集的最基本的查询(它旨在从网络工具中使用,以帮助建模者理解 ontology 的结构)。
我是 运行 datomic-pro-0.9.5554
,有一个内存数据库并使用对等库(我按照 "getting started" 指南中的描述启动了服务器。
非常感谢您为 Datomic 提供帮助。
马库斯
编辑
正如fricke
自己发现的那样,是子句排序的问题,但在查询中,而不是在规则集中。一个更有效的版本是:
[:find ?c ?n :in $ % :where
[?d :ml/name "name of a root symbol"]
(ubersymbol ?c ?d)
[?c :ml/name ?n]]
以上查询可以通过以下方式进一步改进:
- 在查询正文中使用查询参数而不是动态参数
- 使用查找引用通过
:ml/name
解析输入实体
产生:
(d/q
'[:find ?c ?n :in % $ ?d :where
(ubersymbol ?c ?d)
[?c :ml/name ?n]]
rules current-db [:ml/name "name of a root symbol"])
我的理论是,您的规则不是以 Datalog 可以针对此读取模式优化的方式编写的 - 可能会导致遍历所有实体。我建议将它们改写如下:
[[(ubersymbol ?c ?p)
(?c :ml/parent ?p)]
[(ubersymbol ?c ?p)
;; we bind a child of the ancestor, instead of a parent of the descendant
(?c1 :ml/parent ?p)
(ubersymbol ?c ?c1)]]
这种编写规则集的方式经过优化,可以找到某个节点的后代。您最初编写它的方式经过优化,可以找到某个节点的祖先。
在我的机器上使用 Datomic 0.9.5385 在 50000 个实体的平衡二叉树上进行的快速基准测试表明,您使用第二种方法确实获得了所需的性能。