21

TL;DR:我要么疯了,要么neo4j 的交易略有中断。看起来未提交的节点在已提交的事务之外可用,缺少属性 - 或者同样奇怪的东西。

我们的 node.js 应用程序使用 neo4j。其中一部分必须生成唯一的 ID。我们有以下密码查询,旨在定位 last:Id类型的节点并尝试:Id使用last_uuid+1.

MATCH (i:Id) WITH i ORDER BY i.uuid DESC LIMIT 1  #with it like a sub-return, will "run" the rest with the last i at read-time
CREATE (n:Id {label:"Test"}) 
SET n.uuid = i.uuid + 1
RETURN n

还有一个约束:

neo4j-sh (?)$ schema
Indexes
  ON :Id(uuid) ONLINE (for uniqueness constraint) 

Constraints
  ON (id:Id) ASSERT id.uuid IS UNIQUE

并且数据库被初始化以(:Id{uuid:1})启动这种快乐。

应用程序代码基本上会重试上述查询,直到成功。如果同时命中两个或多个 Id 创建请求,则只有其中一个会通过,其余的将失败并由应用代码重试。

这很有效,直到我们并行尝试。

代码开始返回没有 uuid 的数据。经过大量调查,事实证明查询的写入部分(CREATE ...)以某种方式从没有 .uuid(或其他)属性的 MATCH 接收 :Id。这应该是不可能的。这是在这些节点上运行的唯一代码。

最奇怪(也许)的事情是,如果我保存i'snodeid以便在数据库中定位该节点,它实际上存在并且具有 .uuid 属性。

为了隔离这种行为,我写了一个 PoC:neo4j-transaction-test使用 nodejs 运行应该非常简单。

它基本上比上面的代码多一点点 - 尝试创建 Id、设置prev_labelprev_nodeidprev_uuid先前节点的 (i) 值。它针对在 localhost:9339 上收到的每个 GET 请求运行查询并输出:

 > node server.js 
 * 1412125626667 Listening on 9339
 Req Id | Datetime | -> $uuid $nodeid
 1 1412125631677 'GET'       # When it first receives the GET request
 1 1412125631710 '->' 9 60   # When neo4j returns; numbers are $uuid $node_id)

当事情开始并发时,查询可能会失败:

3 1412125777096 '(retry) (0)' 'Node 64 already exists with label Id and property "uuid"=[13]'
4 1412125777098 '(retry) (0)' 'Node 64 already exists with label Id and property "uuid"=[13]'
de[]

这是意料之中的,它们被重试。如果我们以每秒几个请求(ab -n 1000 -c 10 http://localhost:9339/)“猛击”服务器,我们最终会看到:

...
59 1412127103011 'GET'
23 1412127103024 'ERROR - EMPTY UUID' '{"this_nodeid":22,"prev_nodeid":20,"label":"Test"}'

Error: Empty UUID received

(最终,我的意思是几乎立即)一个节点回来了,没有 uuid、prev_uuid 或 prev_label。this_nodeid 和 prev_nodeid 指的是 neo4j 的内部 id。如果我们查找这些,从前一个 ( i) Id 节点开始(按 nodeid - 20):

neo4j-sh (?)$ match (i) where id(i)=20 return i;
+--------------------------------------------------------------------------------------------+
| i                                                                                          |
+--------------------------------------------------------------------------------------------+
| Node[20]{uuid:10,label:"Test",prev_label:"Test",prev_uuid:9,prev_nodeid:17,this_nodeid:20} |
+--------------------------------------------------------------------------------------------+
1 row
19 ms

这正是它应该的样子。.uuid和所有。确实创建了新的,就像上面返回的一样:

neo4j-sh (?)$ match (i) where id(i)=22 return i;
+------------------------------------------------------+
| i                                                    |
+------------------------------------------------------+
| Node[22]{label:"Test",prev_nodeid:20,this_nodeid:22} |
+------------------------------------------------------+
1 row
17 ms

没有 prev_label 或 prev_uuid。这怎么可能?我错过了什么?是否有不完整的 :Id 节点泄漏到我的查询中?

我试过重启,擦除数据目录,擦除数据目录后重启,清理日志(没什么有趣的,甚至无聊,但在正确的时间 - 当上述情况发生时)。我现在正处于质疑我对这应该如何工作的理解的地步。

这是 12.04 和 neo4j 2.1.1。更多版本信息Neo4j 启动/关闭日志

我知道这不是创建 UUID 的最佳方式。这个问题是关于了解如果 neo4j 的事务按预期工作,这些结果是如何可能的。

4

1 回答 1

1

我们在并发事务写入期间注意到 Neo4J 中的一个类似问题,并且在 Neo4J 2.2.5 中引入了一个修复程序来解决这个问题(我们有企业支持并提出了一个票来解决这个问题)。您可能没有完全相同的问题,但可能值得再次尝试使用 2.2.5 来查看它是否仍然是一个问题。

就像你说的,有更好的方法来生成 id。此外,您应该使用MAX来获取最新的而不是LIMITand ORDER BY,但这也是重点;-)。

祝你好运。

于 2015-09-20T22:51:25.533 回答