Estoy tratando de lidiar con series de tiempo usando MongoDB. La solución común adoptada por la comunidad es usar subdocumentos para almacenar información en diferentes niveles de granularidad (consulte Diseño de esquema para datos de series temporales en MongoDB ).
Por ejemplo, echa un vistazo al siguiente documento:
{ timestamp_minute: ISODate("2013-10-10T23:06:00.000Z"), type: “memory_used”, values: [ 999999, // 1 second … 1000000, // nth second 1500000, // n+1th second … 2000000 // 60th ] }El documento está indexado por información de minutos y contiene un subdocumento que almacena información más detallada para cada segundo.
Hasta aquí todo bien. Este tipo de enfoque requiere una optimización para funcionar correctamente:
Otra optimización [..] es preasignar todos los documentos para el próximo período de tiempo; Esto nunca hace que un documento existente crezca o se mueva en el disco.
Para implementar la optimización anterior, se podría usar la propiedad $setOnInsert en el método de update .
db.getCollection('aCollection').update( { timestamp_minute: ISODate("2013-10-10T23:06:00.000Z"), type: “memory_used” }, { $setOnInsert: { values: {'0': 0, '1': 0, '2': 0}}, $inc: {"values.30": 1} }, { upsert: true } )El problema es que no es posible utilizar el mismo campo en la misma actualización en dos operaciones diferentes. La instrucción de actualización anterior genera el siguiente error:
Cannot update 'values' and 'values.30' at the same timeEste problema se rastrea en este problema .
Mi pregunta es: ¿hay alguna solución? Prefijo que no puedo usar ningún lote que preasigne documentos vacíos, porque no puedo saber a priori el valor de los campos indexados (en el ejemplo anterior, los valores del campo type .
Gracias por adelantado.
Mis colegas y yo encontramos una solución. Podemos llamarlo inicialización de tres pasos .
Recuerda que MongoDB garantiza la atomicidad de las operaciones sobre un único documento. Con este hecho en mente podemos operar de la siguiente manera:
El código debería parecerse a algo similar al siguiente fragmento de código.
// Firt of all, try the update var result = db.test.update( {timestamp_minute: ISODate("2013-10-10T23:06:00.000Z"), type: “memory_used”}, {$inc: {"values.39": 1}}, {upsert: false} ); // If the update do not succeed, then try to insert the document if (result.nModified === 0) { try { db.test.insert(/* Put here the whole document */); } catch (err) { console.log(err); } // Here we are sure that the document exists. // Retry to execute the update statement db.test.update(/* Same update as above */); } El procedimiento anterior funciona si se cumple una condición previa: el valor _id debe derivarse de otros campos en el documento. En nuestro ejemplo, el valor de _id sería '2013-10-10T23:06:00.000Z-memory_used . Solo usando esta técnica, el inserto en el punto 2 fallará correctamente.