Игры с map-reduce
by Michel Beloshitsky
Не так давно я узнал о существовании интересной javascript-машины – nodejs. Через него и во многом благодаря сайту nodejs.ru я узнал также о многих других баззвордах современности – nosql, libev и все такое. Nosql системы заинтересовали, хотелось чего-то более выразительного и легкого (выразительного и легкого для меня, sql тоже вполне хорош, но в некоторых случаях…).
Собственно так и родился движок assou.
Сейчас, на раннем этапе, очевидно, он не может претендовать на что-то кроме исследовательского проекта, да и вообще непонятно, сможет ли. В будущем планирую сделать его чем-то вроде sqlite для nodejs – чтобы оно хоть и не блестало гиперпроизводительностью, но работало бы более менее быстро и не хапало лишних зависимосте. Не для хайлоада, проще говоря, а так, потестить. Но особо на хорошее не надейтесь.
Assou
/**
* assou - toylike map-reduce engine
*
* 2010, Michel Beloshitsky.
*
* Placed in public domain.
*/
var assou = function () {
var storage = {}, k = 0, views = {}
/* Basic key management */
function put(json) { storage[++k]=json }
function del(k) {
if (storage[k] !== undefined) {
delete storage[k]
return {ok:true}
} else {
return {ok:false, err: 'Key not found'}
}
}
/* Views */
function updateView(name) {
var worker, intermed = {}, i
function emitMap (k, v) {
if (intermed[k]) { intermed[k].push(v) } else { (intermed[k] = [v]) }
}
function emitReduce (k, v, storage) { storage[k] = v }
function defaultReduce (k, v, emit) { emit(v) }
if (worker = views[name]) {
worker[2] = {}
worker[1] = worker[1] || defaultReduce
for (i in storage)
worker[0](i, storage[i], emitMap)
for (i in intermed)
worker[1](i, intermed[i], function (v) { emitReduce(i, v, worker[2]) })
worker[3] = false
return worker[2]
} else {
return {ok:false, err:'View "' + name + '"not found.'}
}
}
function view(name, map, reduce) {
if (typeof map == 'function' &&
(typeof reduce == 'function' || reduce === undefined)) {
views[name] = [map, reduce, null, false]
return {ok:true}
} else {
return {ok:false, err: 'Wrong arguments. ' +
'Map must be a function. ' +
'Reduce must be a function or undefined.'}
}
}
function get(vw) {
if (views[vw]) {
return updateView(vw)
} else {
return {ok:false, err:'View "'+vw+'" not found.'}
}
}
/* Builtin '*' view */
view('*', function (k, v, emit) { emit(k,v) })
return {put: put, get: get, del: del, view: view }
}
Пара тривиальных тестов
Заполняем хранилище хоть чем-нибудь, с чем можно работать.
/**
* assou-test - assou map-reduce engine simple test.
*/
var tst = assou()
tst.put({msg:'Hey, hey!', tags:['one', 'two']})
tst.put({msg:'Hey, hey!', tags:['one', 'two'], path:['top','leaf']})
tst.put({msg:'Nice bikini!', tags:['two']})
tst.put({msg:'Hm...', tags:['zero'], path:['top']})
tst.put({msg:'Unbelievable message.'})
tst.put({msg:'Nested message.', path:['top','nest']})
tst.put({msg:'Another nested message.', path:['top','nest']})
tst.put({msg:'Yet Another nested message.', path:['top','nest','ynest']})
Создаем пару видов: вид по тегам и вывод дерева (для деревьев у нас мало сообщений, так что получится не совсем наглядно).
/* Views */
tst.view('tags',
function (k, v, emit) {
var i;
if (v.tags)
for (i in v.tags)
emit(v.tags[i], v)
})
tst.view('threads',
function (k, v, emit) {
var i;
if (v.path && v.path.length)
emit(v.path[0], v)
})
Вот что из всего этого получилось. Вид ‘*’ является видом по умолчанию и просто выводит все, что есть в базе.
/* Out data */
JSON.stringify(tst.get('*'))
{
"1": [{
"msg": "Hey, hey!",
"tags": ["one", "two"]
}],
"2": [{
"msg": "Hey, hey!",
"tags": ["one", "two"],
"path": ["top", "leaf"]
}],
"3": [{
"msg": "Nice bikini!",
"tags": ["two"]
}],
"4": [{
"msg": "Hm...",
"tags": ["zero"],
"path": ["top"]
}],
"5": [{
"msg": "Unbelievable message."
}],
"6": [{
"msg": "Nested message.",
"path": ["top", "nest"]
}],
"7": [{
"msg": "Another nested message.",
"path": ["top", "nest"]
}],
"8": [{
"msg": "Yet Another nested message.",
"path": ["top", "nest", "ynest"]
}]
}
JSON.stringify(tst.get('tags'))
{
"one": [{
"msg": "Hey, hey!",
"tags": ["one", "two"]
},
{
"msg": "Hey, hey!",
"tags": ["one", "two"],
"path": ["top", "leaf"]
}],
"two": [{
"msg": "Hey, hey!",
"tags": ["one", "two"]
},
{
"msg": "Hey, hey!",
"tags": ["one", "two"],
"path": ["top", "leaf"]
},
{
"msg": "Nice bikini!",
"tags": ["two"]
}],
"zero": [{
"msg": "Hm...",
"tags": ["zero"],
"path": ["top"]
}]
}
JSON.stringify(tst.get('threads'))
{
"top": [{
"msg": "Hey, hey!",
"tags": ["one", "two"],
"path": ["top", "leaf"]
},
{
"msg": "Hm...",
"tags": ["zero"],
"path": ["top"]
},
{
"msg": "Nested message.",
"path": ["top", "nest"]
},
{
"msg": "Another nested message.",
"path": ["top", "nest"]
},
{
"msg": "Yet Another nested message.",
"path": ["top", "nest", "ynest"]
}]
}
Вот кстати последний случай с деревом показал, что в этот вид неплохо было бы добавить еще и reduce – для того чтобы узлы дерева выводились в строго определенном порядке.
А впереди самое интересное: я начну открывать для себя всякое интересное типа b-tree, пропускать через движок бешеные гигабайты тестов и развлекаться подобным образом. Если все это не потеряет актуальность.
Литаратура
- Jeffrey Dean, Sanjay Ghemawat, MapReduce: Simplified Data Processing on Large Clusters, Google, Inc.
[...] с map-reduce продолжаются. На этот раз написано то, что ляжет в [...]