我们的node.js应用程序使用neo4j。它的一部分必须生成唯一的ID。我们有以下Cypher查询,旨在定位最后一个
: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
并且 DB 是用 (:Id{uuid:1})
初始化的,以启动这个乐趣。
应用程序代码基本上会重试上述查询,直到它成功为止。如果两个或更多的 Id 创建请求同时到达,则只有一个将通过,其余的将失败并由应用程序代码重试。
这个方法一直运作良好,直到我们尝试并行操作时。
代码开始返回没有 uuid 的数据。经过大量调查,结果发现查询的写入部分(CREATE...)从 MATCH 中接收到一个没有 .uuid(或其他)属性的 :Id。这是不可能的。这是唯一在这些节点上操作的代码。
最奇怪(也许)的是,如果我保存 i
的 nodeid
以定位 DB 中的那个节点,实际上它存在,并且具有 .uuid 属性。
为了隔离这种行为,我编写了一个 PoC:neo4j-transaction-test 它应该很容易在 nodejs 上运行。
它基本上比上面的代码多一点 - 尝试创建 Id,设置 prev_label
、prev_nodeid
和 prev_uuid
为上一个节点(i)的值。它为每个接收到本地主机: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
(这里“eventually”的意思是几乎瞬间)一个节点回来了,但没有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的事务按预期工作,这些结果如何可能。
http://localhost:7474/db/data/cypher
或http://localhost:7474/db/data/transaction/commit
与您的Cypher有效载荷。这将验证问题是否出现在一个或两个REST端点而不是Node库中。 - JohnMark13