Node.js CPU密集型的Join操作

ES6的Map函数提供了更有效率的性能。在node.js中有一个普遍的CPU密集型代码模式:join。当你有两个对象数组,希望在两个数组中发现匹配的对象时,join操作就发生了。

这也是关系数据库的核心问题,多年来持续优化和提高,说白了,就是两个集合遍历查找相互匹配的操作,类似数据表的Select join联表查询操作。

join操作最大问题是横向扩展不够,无法分区分库,容易造成单个数据库成为性能瓶颈,因此NoSQL破除了Join操作得以获得大数据处理的横向扩展性能。但是联表或联集合数组查询却变成了应用层代码必须面对的问题。

在node.js有好几种join实现,Zambonilli: Node.js CPU Intensive Joins一文比较了这几种实现的性能。

循环
首先是对两个数组分别遍历,这属于O(n*m)问题,是最糟糕的情况。这对于CPU会有严重的冲击和影响,因为Node.js是单线程,从而会堵塞住整个应用程序。


for (let i =0; i < foo.length; i++) {
let item = undefined;
for (let j =0; j < bar.length; j++) {
if (foo[i]._id === bar[j]._id) {
item = bar[j];
break;
}
}
}

Array.find
使用ES6的Array.find方法,这类似两个数组循环,也是一种O(n * m),对CPU也有严重的性能影响。

Map函数
ES6提供的新的数据结构,使用key/value结构,javascript运行库会以最好方式管理分配和搜索值,因此推荐使用这种方式。


let result = [];
for (let i =0; i < foo.length; i++) {
var item = barMap.get(foo[i]._id);
..
}

对象方式
ES6以前可以使用一个对象作为key/value存储,key是对象的一个字段名,而值是对象的字段值:


for (let i =0; i < foo.length; i++) {
var item = barMapObj[foo[i]._id];
...
}

Merge
这也需要对两个数组在merge之前进行key的排序,遍历两个数组检查key是否相等,如果相等就join值,如果不等,就将较小的key加入。


经过测试:
array.find 和循环join确实是很快,这是因为它们霸占了CPU太长时间,即使数组只有10,000项目也会花费1.4秒执行array.find join,花费1秒执行循环join. 这会使得Web网站或API有很低的性能,这样的CPU就再也不能处理其他请求了,所有的请求处理都必须等待近1.4秒等数组join完毕。

对于数组有100,000项目,循环join和array.find join花费3分钟完成。

ES6的map的join要稍微好于对象方式的join. 推荐你使用 ES6 Map,它是一个更快且干净的API。

如果在数据库进行两个数据集合的比较会如何?
参考:SQL的group聚合与嵌套循环子查询的性能分析
[该贴被banq于2016-05-31 11:40修改过]