MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

MongoDB calcula valores de duas matrizes, classifica e limita

O processamento atual é mapReduce


Se você precisar executar isso no servidor e classificar os principais resultados e manter os 100 primeiros, poderá usar mapReduce para isso assim:
db.test.mapReduce(
    function() {
        var input = [0.1,0.3,0.4];
        var value = Array.sum(this.vals.map(function(el,idx) {
            return Math.abs( el - input[idx] )
        }));

        emit(null,{ "output": [{ "_id": this._id, "value": value }]});
    },
    function(key,values) {
        var output = [];

        values.forEach(function(value) {
            value.output.forEach(function(item) {
                output.push(item);
            });
        });

        output.sort(function(a,b) {
            return a.value < b.value;
        });

        return { "output": output.slice(0,100) };
    },
    { "out": { "inline": 1 } }
)

Assim, a função mapeadora faz o cálculo e produz tudo sob a mesma chave para que todos os resultados sejam enviados ao redutor. A saída final estará contida em uma matriz em um único documento de saída, portanto, é importante que todos os resultados sejam emitidos com o mesmo valor de chave e que a saída de cada emissão seja ela mesma uma matriz para que mapReduce possa funcionar corretamente.

A ordenação e redução são feitas no próprio redutor, à medida que cada documento emitido é inspecionado os elementos são colocados em um único array temporário, ordenados, e os principais resultados são retornados.

Isso é importante, e apenas a razão pela qual o emissor produz isso como uma matriz, mesmo que seja um único elemento no início. O MapReduce funciona processando os resultados em "pedaços", portanto, mesmo que todos os documentos emitidos tenham a mesma chave, nem todos são processados ​​de uma só vez. Em vez disso, o redutor coloca seus resultados de volta na fila de resultados emitidos para serem reduzidos até que haja apenas um único documento restante para essa chave específica.

Estou restringindo a saída "fatia" aqui a 10 para brevidade da listagem e incluindo as estatísticas para enfatizar, pois os 100 ciclos de redução chamados nesta amostra de 10.000 podem ser vistos:
{
    "results" : [
        {
            "_id" : null,
            "value" : {
                "output" : [
                    {
                        "_id" : ObjectId("56558d93138303848b496cd4"),
                        "value" : 2.2
                    },
                    {
                        "_id" : ObjectId("56558d96138303848b49906e"),
                        "value" : 2.2
                    },
                    {
                        "_id" : ObjectId("56558d93138303848b496d9a"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d93138303848b496ef2"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d94138303848b497861"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d94138303848b497b58"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d94138303848b497ba5"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d94138303848b497c43"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d95138303848b49842b"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d96138303848b498db4"),
                        "value" : 2.1
                    }
                ]
            }
        }
    ],
    "timeMillis" : 1758,
    "counts" : {
            "input" : 10000,
            "emit" : 10000,
            "reduce" : 100,
            "output" : 1
    },
    "ok" : 1
}

Portanto, esta é uma saída de documento único, no formato mapReduce específico, onde o "valor" contém um elemento que é uma matriz do resultado classificado e limitado.

O processamento futuro é agregado


No momento da escrita, a versão estável mais recente do MongoDB é 3.0, e isso não possui a funcionalidade para tornar sua operação possível. Mas a próxima versão 3.2 apresenta novos operadores que tornam isso possível:
db.test.aggregate([
    { "$unwind": { "path": "$vals", "includeArrayIndex": "index" }},
    { "$group": {
        "_id": "$_id",
        "result": {
            "$sum": {
                "$abs": {
                    "$subtract": [ 
                        "$vals", 
                        { "$arrayElemAt": [ { "$literal": [0.1,0.3,0.4] }, "$index" ] } 
                    ]
                }
            }
        }
    }},
    { "$sort": { "result": -1 } },
    { "$limit": 100 }
])

Também limitando aos mesmos 10 resultados para brevidade, você obtém uma saída assim:
{ "_id" : ObjectId("56558d96138303848b49906e"), "result" : 2.2 }
{ "_id" : ObjectId("56558d93138303848b496cd4"), "result" : 2.2 }
{ "_id" : ObjectId("56558d96138303848b498e31"), "result" : 2.1 }
{ "_id" : ObjectId("56558d94138303848b497c43"), "result" : 2.1 }
{ "_id" : ObjectId("56558d94138303848b497861"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b499037"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b498db4"), "result" : 2.1 }
{ "_id" : ObjectId("56558d93138303848b496ef2"), "result" : 2.1 }
{ "_id" : ObjectId("56558d93138303848b496d9a"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b499182"), "result" : 2.1 }

Isso é possível em grande parte devido a $unwind sendo modificado para projetar um campo nos resultados que contém o índice do array e também devido a $arrayElemAt que é um novo operador que pode extrair um elemento de matriz como um valor singular de um índice fornecido.

Isso permite a "pesquisa" de valores por posição de índice de sua matriz de entrada para aplicar a matemática a cada elemento. A matriz de entrada é facilitada pelo $literal operador então $arrayElemAt não reclama e o recongiza como um array (parece ser um pequeno bug no momento, já que outras funções de array não têm o problema de entrada direta) e obtém o valor de índice correspondente apropriado usando o campo "index" produzido por $unwind para comparação.

A matemática é feita por $subtract e, claro, outro novo operador em $abs para atender a sua funcionalidade. Além disso, como foi necessário desenrolar o array em primeiro lugar, tudo isso é feito dentro de um $grupo estágio acumulando todos os membros da matriz por documento e aplicando a adição de entradas por meio do $soma acumulador.

Finalmente, todos os documentos de resultados são processados ​​com $sort e, em seguida, o $limit é aplicado apenas para retornar os principais resultados.

Resumo


Mesmo com a nova funcionalidade prestes a ser disponibilizada ao framework de agregação para MongoDB é discutível qual abordagem é realmente mais eficiente para os resultados. Isso se deve em grande parte à necessidade de $unwind o conteúdo da matriz, que efetivamente produz uma cópia de cada documento por membro da matriz no pipeline a ser processado e que geralmente causa uma sobrecarga.

Portanto, embora mapReduce seja a única maneira atual de fazer isso até um novo lançamento, ele pode realmente superar a instrução de agregação, dependendo da quantidade de dados a serem processados, e apesar do fato de a estrutura de agregação funcionar em operadores codificados nativos em vez de JavaScript traduzido operações.

Como em todas as coisas, o teste é sempre recomendado para ver qual caso se adapta melhor aos seus propósitos e qual oferece o melhor desempenho para o processamento esperado.

Amostra


Claro que o resultado esperado para o documento de amostra fornecido na pergunta é 0.9 pela matemática aplicada. Mas apenas para fins de teste, aqui está uma pequena listagem usada para gerar alguns dados de exemplo que eu queria pelo menos verificar se o código mapReduce estava funcionando como deveria:
var bulk = db.test.initializeUnorderedBulkOp();

var x = 10000;

while ( x-- ) {
    var vals = [0,0,0];

    vals = vals.map(function(val) {
        return Math.round((Math.random()*10),1)/10;
    });

    bulk.insert({ "vals": vals });

    if ( x % 1000 == 0) {
        bulk.execute();
        bulk = db.test.initializeUnorderedBulkOp();
    }
}

As matrizes são valores de ponto decimal único totalmente aleatórios, portanto, não há muita distribuição nos resultados listados que dei como saída de amostra.