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_label
、prev_nodeid
和prev_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 的事务按预期工作,这些结果是如何可能的。