sql >> Base de Datos >  >> NoSQL >> MongoDB

Rendimiento de MongoDB:ejecución de operaciones Map-Reduce de MongoDB en secundarias

Map-reduce es quizás la más versátil de las operaciones de agregación que admite MongoDB.

Map-Reduce es un modelo de programación popular que se originó en Google para procesar y agregar grandes volúmenes de datos en paralelo. Una discusión detallada sobre Map-Reduce está fuera del alcance de este artículo, pero esencialmente es un proceso de agregación de varios pasos. Los dos pasos más importantes son la etapa del mapa (procesar cada documento y emitir los resultados) y la etapa de reducción (cotejar los resultados emitidos durante la etapa del mapa).

MongoDB admite tres tipos de operaciones de agregación:Map-Reduce, tubería de agregación y comandos de agregación de propósito único. Puede usar este documento de comparación de MongoDB para ver cuál se ajusta a sus necesidades. https://scalegrid.io/blog/mongodb-performance-running-mongodb-map-reduce-operations-on-secondaries/

En mi última publicación, vimos, con ejemplos, cómo ejecutar canalizaciones de agregación en secundarias. En esta publicación, veremos la ejecución de trabajos Map-Reduce en las réplicas secundarias de MongoDB.

Mapa de MongoDB-Reducir

MongoDB admite la ejecución de trabajos Map-Reduce en los servidores de bases de datos. Esto ofrece la flexibilidad de escribir tareas de agregación complejas que no se realizan tan fácilmente a través de canalizaciones de agregación. MongoDB le permite escribir mapas personalizados y reducir funciones en Javascript que se pueden pasar a la base de datos a través de Mongo Shell o cualquier otro cliente. En conjuntos de datos grandes y en constante crecimiento, uno puede incluso considerar ejecutar trabajos Map-Reduce incrementales para evitar procesar datos más antiguos cada vez.

Históricamente, los métodos map y reduce solían ejecutarse en un contexto de subproceso único. Sin embargo, esa limitación se eliminó en la versión 2.4.

¿Por qué ejecutar trabajos Map-Reduce en el secundario?

Al igual que otros trabajos de agregación, Map-Reduce también es un trabajo "por lotes" que requiere muchos recursos, por lo que es una buena opción para ejecutarse en réplicas de solo lectura. Las advertencias al hacerlo son:

1) Debería estar bien usar datos ligeramente obsoletos. O puede modificar la preocupación de escritura para asegurarse de que las réplicas estén siempre sincronizadas con la principal. Esta segunda opción supone que recibir un golpe en el rendimiento de escritura es aceptable.

2) El resultado del trabajo Map-Reduce no debe escribirse en otra colección dentro de la base de datos, sino devolverse a la aplicación (es decir, no debe escribirse en la base de datos).

Veamos cómo hacer esto a través de ejemplos, tanto desde el shell mongo como desde el controlador Java.

Map-Reduce en conjuntos de réplicas

Conjunto de datos

A modo de ilustración, utilizaremos un conjunto de datos bastante simple:un volcado diario de registros de transacciones de un minorista. Una entrada de muestra se parece a:

RS-replica-0:PRIMARY> use test
switched to db test
RS-replica-0:PRIMARY> show tables
txns
RS-replica-0:PRIMARY> db.txns.findOne()
{
    "_id" : ObjectId("584a3b71cdc1cb061957289b"),
    "custid" : "cust_66",
    "txnval" : 100,
    "items" : [{"sku": sku1", "qty": 1, "pr": 100}, ...],
...
}

En nuestros ejemplos, calcularemos el gasto total de un cliente determinado ese día. Por lo tanto, dado nuestro esquema, los métodos map y reduce se verán así:

var mapFunction = function() { emit(this.custid, this.txnval); } // Emit the custid and txn value from each record
var reduceFunction = function(key, values) { return Array.sum(values); } // Sum all the txn values for a given custid

Con nuestro esquema establecido, veamos Map-Reduce en acción.

Concha MongoDB

Para garantizar que se ejecute un trabajo Map-Reduce en el secundario, la preferencia de lectura debe establecerse en secundario . Como dijimos anteriormente, para que Map-Reduce se ejecute en un secundario, la salida del resultado debe ser en línea (De hecho, ese es el único valor de salida permitido en los secundarios). Veamos cómo funciona.

$ mongo -u admin -p pwd --authenticationDatabase admin --host RS-replica-0/server-1.servers.example.com:27017,server-2.servers.example.com:27017
MongoDB shell version: 3.2.10
connecting to: RS-replica-0/server-1.servers.example.com:27017,server-2.servers.example.com:27017/test
2016-12-09T08:15:19.347+0000 I NETWORK  [thread1] Starting new replica set monitor for server-1.servers.example.com:27017,server-2.servers.example.com:27017
2016-12-09T08:15:19.349+0000 I NETWORK  [ReplicaSetMonitorWatcher] starting
RS-replica-0:PRIMARY> db.setSlaveOk()
RS-replica-0:PRIMARY> db.getMongo().setReadPref('secondary')
RS-replica-0:PRIMARY> db.getMongo().getReadPrefMode()
secondary
RS-replica-0:PRIMARY> var mapFunc = function() { emit(this.custid, this.txnval); }
RS-replica-0:PRIMARY> var reduceFunc = function(key, values) { return Array.sum(values); }
RS-replica-0:PRIMARY> db.txns.mapReduce(mapFunc, reduceFunc, {out: { inline: 1 }})
{
    "results" : [
        {
            "_id" : "cust_0",
            "value" : 72734
        },
        {
            "_id" : "cust_1",
            "value" : 67737
        },
...
    ]
    "timeMillis" : 215,
    "counts" : {
        "input" : 10000,
        "emit" : 10000,
        "reduce" : 909,
        "output" : 101
    },
    "ok" : 1

}

Un vistazo a los registros en el secundario confirma que el trabajo se ejecutó en el secundario.

...
2016-12-09T08:17:24.842+0000 D COMMAND  [conn344] mr ns: test.txns
2016-12-09T08:17:24.843+0000 I COMMAND  [conn344] command test.$cmd command: listCollections { listCollections: 1, filter: { name: "txns" }, cursor: {} } keyUpdates:0 writeConflicts:0 numYields:0 reslen:150 locks:{ Global: { acquireCount: { r: 4 } }, Database: { acquireCount: { r: 1, R: 1 } }, Collection: { acquireCount: { r: 1 } } } protocol:op_query 0ms
2016-12-09T08:17:24.865+0000 I COMMAND  [conn344] query test.system.js planSummary: EOF ntoreturn:0 ntoskip:0 keysExamined:0 docsExamined:0 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:0 nreturned:0 reslen:20 locks:{ Global: { acquireCount: { r: 6 } }, Database: { acquireCount: { r: 2, R: 1 } }, Collection: { acquireCount: { r: 2 } } } 0ms
2016-12-09T08:17:25.063+0000 I COMMAND  [conn344] command test.txns command: mapReduce { mapreduce: "txns", map: function () { emit(this.custid, this.txnval); }, reduce: function (key, values) { return Array.sum(values); }, out: { inline: 1.0 } } planSummary: COUNT keyUpdates:0 writeConflicts:0 numYields:78 reslen:4233 locks:{ Global: { acquireCount: { r: 366 } }, Database: { acquireCount: { r: 3, R: 180 } }, Collection: { acquireCount: { r: 3 } } } protocol:op_command 220ms
...

Java

Ahora intentemos ejecutar un trabajo Map-Reduce en las réplicas de lectura desde una aplicación Java. En el controlador MongoDB Java, establecer la Preferencia de lectura funciona. La salida está en línea de forma predeterminada, por lo que no es necesario pasar parámetros adicionales. Aquí hay un ejemplo usando la versión 3.2.2 del controlador:

public class MapReduceExample {

    private static final String MONGO_END_POINT = "mongodb://admin:[email protected]:27017,server-2.servers.example.com:27017/admin?replicaSet=RS-replica-0";
    private static final String COL_NAME = "txns";
    private static final String DEF_DB = "test";

    public MapReduceExample() { }

    public static void main(String[] args) {
        MapReduceExample writer = new MapReduceExample();
        writer.mapReduce();
    }

    public static final String mapfunction = "function() { emit(this.custid, this.txnval); }";
    public static final String reducefunction = "function(key, values) { return Array.sum(values); }";

    private void mapReduce() {
        printer("Initializing...");
        Builder options = MongoClientOptions.builder().readPreference(ReadPreference.secondary());
        MongoClientURI uri = new MongoClientURI(MONGO_END_POINT, options);
        MongoClient client = new MongoClient(uri);
        MongoDatabase database = client.getDatabase(DEF_DB);
        MongoCollection collection = database.getCollection(COL_NAME);
        MapReduceIterable iterable = collection.mapReduce(mapfunction, reducefunction); // inline by default
        MongoCursor cursor = iterable.iterator();
        while (cursor.hasNext()) {
           Document result = cursor.next();
           printer("Customer: " + result.getString("_id") + ", Total Txn value: " + result.getDouble("value"));
        }
        printer("Done...");
    }
...
}

Como se desprende de los registros, el trabajo se ejecutó en el secundario:

...
2016-12-09T08:32:31.419+0000 D COMMAND  [conn371] mr ns: test.txns
2016-12-09T08:32:31.420+0000 I COMMAND  [conn371] command test.$cmd command: listCollections { listCollections: 1, filter: { name: "txns" }, cursor: {} } keyUpdates:0 writeConflicts:0 numYields:0 reslen:150 locks:{ Global: { acquireCount: { r: 4 } }, Database: { acquireCount: { r: 1, R: 1 } }, Collection: { acquireCount: { r: 1 } } } protocol:op_query 0ms
2016-12-09T08:32:31.444+0000 I COMMAND  [conn371] query test.system.js planSummary: EOF ntoreturn:0 ntoskip:0 keysExamined:0 docsExamined:0 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:0 nreturned:0 reslen:20 locks:{ Global: { acquireCount: { r: 6 } }, Database: { acquireCount: { r: 2, R: 1 } }, Collection: { acquireCount: { r: 2 } } } 0ms
2016-12-09T08:32:31.890+0000 I COMMAND  [conn371] command test.txns command: mapReduce { mapreduce: "txns", map: function() { emit(this.custid, this.txnval); }, reduce: function(key, values) { return Array.sum(values); }, out: { inline: 1 }, query: null, sort: null, finalize: null, scope: null, verbose: true } planSummary: COUNT keyUpdates:0 writeConflicts:0 numYields:156 reslen:4331 locks:{ Global: { acquireCount: { r: 722 } }, Database: { acquireCount: { r: 3, R: 358 } }, Collection: { acquireCount: { r: 3 } } } protocol:op_query 470ms
...

MongoDB Map-Reduce en clústeres fragmentados

MongoDB admite Map-Reduce en clústeres fragmentados, tanto cuando una colección fragmentada es la entrada como la salida de un trabajo de Map-Reduce. Sin embargo, MongoDB actualmente no admite la ejecución de trabajos de reducción de mapas en los secundarios de un clúster fragmentado. Entonces, incluso si la opción de salida se establece en en línea , los trabajos Map-Reduce siempre se ejecutarán en los servidores primarios de un clúster fragmentado. Este problema se está rastreando a través de este error de JIRA.

La sintaxis de ejecutar un trabajo Map-Reduce en un clúster fragmentado es la misma que en un conjunto de réplicas. Así que los ejemplos proporcionados en la sección anterior se mantienen. Si el ejemplo anterior de Java se ejecuta en un clúster fragmentado, aparecen mensajes de registro en los primarios que indican que el comando se ejecutó allí.

...
2016-11-24T08:46:30.828+0000 I COMMAND  [conn357] command test.$cmd command: mapreduce.shardedfinish { mapreduce.shardedfinish: { mapreduce: "txns", map: function() { emit(this.custid, this.txnval); }, reduce: function(key, values) { return Array.sum(values); }, out: { in
line: 1 }, query: null, sort: null, finalize: null, scope: null, verbose: true, $queryOptions: { $readPreference: { mode: "secondary" } } }, inputDB: "test", shardedOutputCollection: "tmp.mrs.txns_1479977190_0", shards: { Shard-0/primary.shard0.example.com:27017,secondary.shard0.example.com:27017: { result: "tmp.mrs.txns_1479977190_0", timeMillis: 123, timing: { mapTime: 51, emitLoop: 116, reduceTime: 9, mode: "mixed", total: 123 }, counts: { input: 9474, emit: 9474, reduce: 909, output: 101 }, ok: 1.0, $gleS
tats: { lastOpTime: Timestamp 1479977190000|103, electionId: ObjectId('7fffffff0000000000000001') } }, Shard-1/primary.shard1.example.com:27017,secondary.shard1.example.com:27017: { result: "tmp.mrs.txns_1479977190_0", timeMillis: 71, timing:
 { mapTime: 8, emitLoop: 63, reduceTime: 4, mode: "mixed", total: 71 }, counts: { input: 1526, emit: 1526, reduce: 197, output: 101 }, ok: 1.0, $gleStats: { lastOpTime: Timestamp 1479977190000|103, electionId: ObjectId('7fffffff0000000000000001') } } }, shardCounts: { Sha
rd-0/primary.shard0.example.com:27017,secondary.shard0.example.com:27017: { input: 9474, emit: 9474, reduce: 909, output: 101 }, Shard-1/primary.shard1.example.com:27017,secondary.shard1.example.com:27017: { inpu
t: 1526, emit: 1526, reduce: 197, output: 101 } }, counts: { emit: 11000, input: 11000, output: 202, reduce: 1106 } } keyUpdates:0 writeConflicts:0 numYields:0 reslen:4368 locks:{ Global: { acquireCount: { r: 2 } }, Database: { acquireCount: { r: 1 } }, Collection: { acqu
ireCount: { r: 1 } } } protocol:op_command 115ms
2016-11-24T08:46:30.830+0000 I COMMAND  [conn46] CMD: drop test.tmp.mrs.txns_1479977190_0
...

Visite nuestra página de productos de MongoDB para conocer nuestra extensa lista de funciones.