去重 ArangoDB 文档集合
deduplicating ArangoDB document collection
我确信有一种简单快捷的方法可以做到这一点,但它正在逃避我。我有一个包含一些重复记录的大型数据集,我想删除重复项。 (重复项由一个 属性 唯一标识,但文档的其余部分也应相同)。
我试图通过几种不同的方式创建一个只有唯一值的新集合,但它们都很慢。例如:
FOR doc IN Documents
COLLECT docId = doc.myId, doc2 = doc
INSERT doc2 IN Documents2
或
FOR doc IN Documents
LET existing = (FOR doc2 IN Documents2
FILTER doc.myId == doc2.myId
RETURN doc2)
UPDATE existing WITH doc IN Documents2
或(这给我一个 "violated unique constraint" 错误)
FOR doc IN Documents
UPSERT {myId: doc.myId}}]}
INSERT doc
UPDATE doc IN Documents2
TL;DR
删除重复记录并将它们写入另一个集合并不需要那么长时间(不到 60 秒),至少在我的台式机上是这样(Windows 10, Intel 6700K 4x4.0GHz, 32GB RAM, Evo 850 SSD).
但是某些查询需要适当的索引,否则它们将永远存在。索引需要一些内存,但与查询执行期间对记录进行分组所需的内存相比,它可以忽略不计。如果内存不足,性能将受到影响,因为操作系统需要在内存和大容量存储之间交换数据。这对于旋转磁盘来说尤其是一个问题,而对于快速闪存设备则不是那么多。
准备
我生成了 220 万条记录,其中包含 5-20 个随机属性和每个属性 160 个乱码字符。此外,每条记录都有一个属性myid
。 187k条记录有唯一id,60k myid
存在两次,70k存在三次。集合大小报告为 4.83GB:
// 1..2000000: 300s
// 1..130000: 20s
// 1..70000: 10s
FOR i IN 1..2000000
LET randomAttributes = MERGE(
FOR j IN 1..FLOOR(RAND() * 15) + 5
RETURN { [CONCAT("attr", j)]: RANDOM_TOKEN(160) }
)
INSERT MERGE(randomAttributes, {myid: i}) INTO test1
启动 ArangoDB 前的内存消耗为 3.4GB,启动后为 4.0GB,加载 test1
源集合后约为 8.8GB。
基线
从 test1
读取并将 所有文档 (2.2m) 插入 test2
在我的系统上花费了 20 秒,内存峰值约为 17.6GB :
FOR doc IN test1
INSERT doc INTO test2
按 myid
分组而不写入大约花费了。对我来说是 9 秒,查询期间 RAM 峰值为 9GB:
LET result = (
FOR doc IN test1
COLLECT myid = doc.myid
RETURN 1
)
RETURN LENGTH(result)
分组失败
我在只有 3 个记录和一个重复 myid
的数据集上尝试了您的 COLLECT docId = doc.myId, doc2 = doc
方法。它表明查询实际上没有 group/remove 重复。因此,我试图寻找替代查询。
与 INTO 分组
要将重复的 myid
分组在一起但保留访问完整文档的可能性,可以使用 COLLECT ... INTO
。我只是选择了每组的第一个文档来删除多余的 myid
s。查询用了大约 40 秒将具有唯一 myid
属性的 2m 记录写入 test2
。我没有准确测量内存消耗,但我看到了从 14GB 到 21GB 的不同内存峰值。也许截断测试集合并重新运行查询会增加所需的内存,因为一些陈旧的条目会以某种方式阻碍(压缩/密钥生成)?
FOR doc IN test1
COLLECT myid = doc.myid INTO groups
INSERT groups[0].doc INTO test2
使用子查询分组
以下查询显示更稳定的内存消耗,峰值为 13.4GB:
FOR doc IN test1
COLLECT myid = doc.myid
LET doc2 = (
FOR doc3 IN test1
FILTER doc3.myid == myid
LIMIT 1
RETURN doc3
)
INSERT doc2[0] INTO test2
但是请注意,它需要 test1
中的 myid
上的哈希索引才能实现约 38 秒的查询执行时间。否则子查询将导致数百万次集合扫描并花费 ages.
使用 INTO 和 KEEP 分组
我们可以只将 _id
分配给一个变量并 KEEP
它,而不是存储属于一个组的整个文档,这样我们就可以使用 [=37 查找文档主体=]:
FOR doc IN test1
LET d = doc._id
COLLECT myid = doc.myid INTO groups KEEP d
INSERT DOCUMENT(groups[0].d) INTO test2
内存使用:加载源集合后 8.1GB,查询期间达到 13.5GB 峰值。 2m记录只用了30秒!
使用 INTO 和投影分组
出于好奇,我也尝试了投影而不是 KEEP:
FOR doc IN test1
COLLECT myid = doc.myid INTO groups = doc._id
INSERT DOCUMENT(groups[0]) INTO test2
加载 test1
后 RAM 为 8.3GB,峰值为 17.8GB(在查询执行期间实际上有两个峰值,均超过 17GB)。 2m记录用了35s完成。
更新
我用 UPSERT 做了一些尝试,但看到了一些奇怪的结果。事实证明这是对 ArangoDB 的 upsert 实现的疏忽。 v3.0.2 contains a fix 我现在得到正确的结果:
FOR doc IN test1
UPSERT {myid: doc.myid}
INSERT doc
UPDATE {} IN test2
在 test2
中处理 myid
上的(唯一)哈希索引需要 40 秒,RAM 峰值约为 13.2GB。
就地删除重复项
我首先将所有文档从 test1
复制到 test2
(2.2m 记录),然后我尝试 REMOVE
只复制 test2
中的副本:
FOR doc IN test2
COLLECT myid = doc.myid INTO keys = doc._key
LET allButFirst = SLICE(keys, 1) // or SHIFT(keys)
FOR k IN allButFirst
REMOVE k IN test2
内存为 8.2GB(仅加载 test2
),在查询期间增加到 13.5GB。删除重复项 (200k) 大约需要 16 秒。
验证
以下查询将 myid
组合在一起并汇总每个 ID 出现的频率。 运行对目标集合test2
,结果应该是{"1": 2000000}
,否则还是有重复的。我仔细检查了上面的查询结果,并检查了所有内容。
FOR doc IN test2
COLLECT myid = doc.myid WITH COUNT INTO count
COLLECT c = count WITH COUNT INTO cc
RETURN {[c]: cc}
结论
ArangoDB v3.0 的性能似乎是合理的,但如果没有足够的 RAM 可用,它可能会降低。不同的查询大致在同一时间内完成,但显示出不同的 RAM 使用特征。对于某些查询,索引是必要的,以避免高计算复杂度(这里:全集合扫描;最坏情况下 2,200,000,000,000 次读取?)。
你能在你的数据上尝试我提出的解决方案并检查你机器上的性能吗?
我确信有一种简单快捷的方法可以做到这一点,但它正在逃避我。我有一个包含一些重复记录的大型数据集,我想删除重复项。 (重复项由一个 属性 唯一标识,但文档的其余部分也应相同)。
我试图通过几种不同的方式创建一个只有唯一值的新集合,但它们都很慢。例如:
FOR doc IN Documents
COLLECT docId = doc.myId, doc2 = doc
INSERT doc2 IN Documents2
或
FOR doc IN Documents
LET existing = (FOR doc2 IN Documents2
FILTER doc.myId == doc2.myId
RETURN doc2)
UPDATE existing WITH doc IN Documents2
或(这给我一个 "violated unique constraint" 错误)
FOR doc IN Documents
UPSERT {myId: doc.myId}}]}
INSERT doc
UPDATE doc IN Documents2
TL;DR
删除重复记录并将它们写入另一个集合并不需要那么长时间(不到 60 秒),至少在我的台式机上是这样(Windows 10, Intel 6700K 4x4.0GHz, 32GB RAM, Evo 850 SSD).
但是某些查询需要适当的索引,否则它们将永远存在。索引需要一些内存,但与查询执行期间对记录进行分组所需的内存相比,它可以忽略不计。如果内存不足,性能将受到影响,因为操作系统需要在内存和大容量存储之间交换数据。这对于旋转磁盘来说尤其是一个问题,而对于快速闪存设备则不是那么多。
准备
我生成了 220 万条记录,其中包含 5-20 个随机属性和每个属性 160 个乱码字符。此外,每条记录都有一个属性myid
。 187k条记录有唯一id,60k myid
存在两次,70k存在三次。集合大小报告为 4.83GB:
// 1..2000000: 300s
// 1..130000: 20s
// 1..70000: 10s
FOR i IN 1..2000000
LET randomAttributes = MERGE(
FOR j IN 1..FLOOR(RAND() * 15) + 5
RETURN { [CONCAT("attr", j)]: RANDOM_TOKEN(160) }
)
INSERT MERGE(randomAttributes, {myid: i}) INTO test1
启动 ArangoDB 前的内存消耗为 3.4GB,启动后为 4.0GB,加载 test1
源集合后约为 8.8GB。
基线
从 test1
读取并将 所有文档 (2.2m) 插入 test2
在我的系统上花费了 20 秒,内存峰值约为 17.6GB :
FOR doc IN test1
INSERT doc INTO test2
按 myid
分组而不写入大约花费了。对我来说是 9 秒,查询期间 RAM 峰值为 9GB:
LET result = (
FOR doc IN test1
COLLECT myid = doc.myid
RETURN 1
)
RETURN LENGTH(result)
分组失败
我在只有 3 个记录和一个重复 myid
的数据集上尝试了您的 COLLECT docId = doc.myId, doc2 = doc
方法。它表明查询实际上没有 group/remove 重复。因此,我试图寻找替代查询。
与 INTO 分组
要将重复的 myid
分组在一起但保留访问完整文档的可能性,可以使用 COLLECT ... INTO
。我只是选择了每组的第一个文档来删除多余的 myid
s。查询用了大约 40 秒将具有唯一 myid
属性的 2m 记录写入 test2
。我没有准确测量内存消耗,但我看到了从 14GB 到 21GB 的不同内存峰值。也许截断测试集合并重新运行查询会增加所需的内存,因为一些陈旧的条目会以某种方式阻碍(压缩/密钥生成)?
FOR doc IN test1
COLLECT myid = doc.myid INTO groups
INSERT groups[0].doc INTO test2
使用子查询分组
以下查询显示更稳定的内存消耗,峰值为 13.4GB:
FOR doc IN test1
COLLECT myid = doc.myid
LET doc2 = (
FOR doc3 IN test1
FILTER doc3.myid == myid
LIMIT 1
RETURN doc3
)
INSERT doc2[0] INTO test2
但是请注意,它需要 test1
中的 myid
上的哈希索引才能实现约 38 秒的查询执行时间。否则子查询将导致数百万次集合扫描并花费 ages.
使用 INTO 和 KEEP 分组
我们可以只将 _id
分配给一个变量并 KEEP
它,而不是存储属于一个组的整个文档,这样我们就可以使用 [=37 查找文档主体=]:
FOR doc IN test1
LET d = doc._id
COLLECT myid = doc.myid INTO groups KEEP d
INSERT DOCUMENT(groups[0].d) INTO test2
内存使用:加载源集合后 8.1GB,查询期间达到 13.5GB 峰值。 2m记录只用了30秒!
使用 INTO 和投影分组
出于好奇,我也尝试了投影而不是 KEEP:
FOR doc IN test1
COLLECT myid = doc.myid INTO groups = doc._id
INSERT DOCUMENT(groups[0]) INTO test2
加载 test1
后 RAM 为 8.3GB,峰值为 17.8GB(在查询执行期间实际上有两个峰值,均超过 17GB)。 2m记录用了35s完成。
更新
我用 UPSERT 做了一些尝试,但看到了一些奇怪的结果。事实证明这是对 ArangoDB 的 upsert 实现的疏忽。 v3.0.2 contains a fix 我现在得到正确的结果:
FOR doc IN test1
UPSERT {myid: doc.myid}
INSERT doc
UPDATE {} IN test2
在 test2
中处理 myid
上的(唯一)哈希索引需要 40 秒,RAM 峰值约为 13.2GB。
就地删除重复项
我首先将所有文档从 test1
复制到 test2
(2.2m 记录),然后我尝试 REMOVE
只复制 test2
中的副本:
FOR doc IN test2
COLLECT myid = doc.myid INTO keys = doc._key
LET allButFirst = SLICE(keys, 1) // or SHIFT(keys)
FOR k IN allButFirst
REMOVE k IN test2
内存为 8.2GB(仅加载 test2
),在查询期间增加到 13.5GB。删除重复项 (200k) 大约需要 16 秒。
验证
以下查询将 myid
组合在一起并汇总每个 ID 出现的频率。 运行对目标集合test2
,结果应该是{"1": 2000000}
,否则还是有重复的。我仔细检查了上面的查询结果,并检查了所有内容。
FOR doc IN test2
COLLECT myid = doc.myid WITH COUNT INTO count
COLLECT c = count WITH COUNT INTO cc
RETURN {[c]: cc}
结论
ArangoDB v3.0 的性能似乎是合理的,但如果没有足够的 RAM 可用,它可能会降低。不同的查询大致在同一时间内完成,但显示出不同的 RAM 使用特征。对于某些查询,索引是必要的,以避免高计算复杂度(这里:全集合扫描;最坏情况下 2,200,000,000,000 次读取?)。
你能在你的数据上尝试我提出的解决方案并检查你机器上的性能吗?