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

Meteor:cargar archivos del cliente a la colección Mongo frente al sistema de archivos frente a GridFS

Puede lograr la carga de archivos con Meteor sin usar más paquetes o un tercero

Opción 1:DDP, guardar archivo en una colección mongo

/*** client.js ***/

// asign a change event into input tag
'change input' : function(event,template){ 
    var file = event.target.files[0]; //assuming 1 file only
    if (!file) return;

    var reader = new FileReader(); //create a reader according to HTML5 File API

    reader.onload = function(event){          
      var buffer = new Uint8Array(reader.result) // convert to binary
      Meteor.call('saveFile', buffer);
    }

    reader.readAsArrayBuffer(file); //read the file as arraybuffer
}

/*** server.js ***/ 

Files = new Mongo.Collection('files');

Meteor.methods({
    'saveFile': function(buffer){
        Files.insert({data:buffer})         
    }   
});

Explicación

Primero, el archivo se toma de la entrada usando la API de archivos HTML5. Se crea un lector utilizando el nuevo FileReader. El archivo se lee como readAsArrayBuffer. Este arraybuffer, si console.log, devuelve {} y DDP no puede enviarlo por cable, por lo que debe convertirse a Uint8Array.

Cuando coloca esto en Meteor.call, Meteor automáticamente ejecuta EJSON.stringify(Uint8Array) y lo envía con DDP. Puede verificar los datos en el tráfico websocket de la consola de Chrome, verá una cadena que se parece a base64

En el lado del servidor, Meteor llama a EJSON.parse() y lo vuelve a convertir en búfer

Ventajas

  1. Simple, sin trucos, sin paquetes adicionales
  2. Apéguese al principio de datos en el cable

Contras

  1. Más ancho de banda:la cadena base64 resultante es aproximadamente un 33 % más grande que el archivo original
  2. Límite de tamaño de archivo:no se pueden enviar archivos grandes (¿límite ~ 16 MB?)
  3. Sin almacenamiento en caché
  4. No hay gzip ni compresión todavía
  5. Usa mucha memoria si publicas archivos

Opción 2:XHR, publicar desde el cliente al sistema de archivos

/*** client.js ***/

// asign a change event into input tag
'change input' : function(event,template){ 
    var file = event.target.files[0]; 
    if (!file) return;      

    var xhr = new XMLHttpRequest(); 
    xhr.open('POST', '/uploadSomeWhere', true);
    xhr.onload = function(event){...}

    xhr.send(file); 
}

/*** server.js ***/ 

var fs = Npm.require('fs');

//using interal webapp or iron:router
WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
    //var start = Date.now()        
    var file = fs.createWriteStream('/path/to/dir/filename'); 

    file.on('error',function(error){...});
    file.on('finish',function(){
        res.writeHead(...) 
        res.end(); //end the respone 
        //console.log('Finish uploading, time taken: ' + Date.now() - start);
    });

    req.pipe(file); //pipe the request to the file
});

Explicación

Se captura el archivo en el cliente, se crea un objeto XHR y el archivo se envía a través de 'POST' al servidor.

En el servidor, los datos se canalizan a un sistema de archivos subyacente. Además, puede determinar el nombre del archivo, realizar una desinfección o verificar si ya existe, etc. antes de guardar.

Ventajas

  1. Aprovechando XHR 2 para que pueda enviar arraybuffer, no se necesita un nuevo FileReader() en comparación con la opción 1
  2. Arraybuffer es menos voluminoso en comparación con la cadena base64
  3. Sin límite de tamaño, envié un archivo de ~ 200 MB en localhost sin problema
  4. El sistema de archivos es más rápido que mongodb (más de esto más adelante en la evaluación comparativa a continuación)
  5. Caché y gzip

Contras

  1. XHR 2 no está disponible en navegadores antiguos, p. debajo de IE10, pero, por supuesto, puede implementar una publicación tradicional
    . Solo usé xhr =new XMLHttpRequest(), en lugar de HTTP.call('POST') porque la HTTP.call actual en Meteor aún no puede enviar arraybuffer (señálame si me equivoco).
  2. /path/to/dir/ tiene que estar fuera de Meteor, de lo contrario, escribir un archivo en /public desencadena una recarga

Opción 3:XHR, guardar en GridFS

/*** client.js ***/

//same as option 2


/*** version A: server.js ***/  

var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var GridStore = MongoInternals.NpmModule.GridStore;

WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
    //var start = Date.now()        
    var file = new GridStore(db,'filename','w');

    file.open(function(error,gs){
        file.stream(true); //true will close the file automatically once piping finishes

        file.on('error',function(e){...});
        file.on('end',function(){
            res.end(); //send end respone
            //console.log('Finish uploading, time taken: ' + Date.now() - start);
        });

        req.pipe(file);
    });     
});

/*** version B: server.js ***/  

var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var GridStore = Npm.require('mongodb').GridStore; //also need to add Npm.depends({mongodb:'2.0.13'}) in package.js

WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
    //var start = Date.now()        
    var file = new GridStore(db,'filename','w').stream(true); //start the stream 

    file.on('error',function(e){...});
    file.on('end',function(){
        res.end(); //send end respone
        //console.log('Finish uploading, time taken: ' + Date.now() - start);
    });
    req.pipe(file);
});     

Explicación

El script del cliente es el mismo que en la opción 2.

De acuerdo con la última línea de Meteor 1.0.x mongo_driver.js, se expone un objeto global llamado MongoInternals, puede llamar a defaultRemoteCollectionDriver() para devolver el objeto db de la base de datos actual que se requiere para GridStore. En la versión A, GridStore también está expuesto por MongoInternals. El mongo utilizado por el meteorito actual es v1.4.x

Luego, dentro de una ruta, puede crear un nuevo objeto de escritura llamando a var file =new GridStore (...) (API). Luego abre el archivo y crea una transmisión.

También incluí una versión B. En esta versión, GridStore se llama usando una nueva unidad mongodb a través de Npm.require('mongodb'), este mongo es el último v2.0.13 a partir de este escrito. La nueva API no requiere que abra el archivo, puede llamar a stream(true) directamente y comenzar a canalizar

Ventajas

  1. Igual que en la opción 2, enviado mediante arraybuffer, menos sobrecarga en comparación con la cadena base64 en la opción 1
  2. No hay necesidad de preocuparse por la desinfección de nombres de archivos
  3. Separación del sistema de archivos, no es necesario escribir en el directorio temporal, se puede hacer una copia de seguridad de la base de datos, representación, fragmento, etc.
  4. No es necesario implementar ningún otro paquete
  5. Se puede guardar en caché y se puede comprimir con gzip
  6. Almacene tamaños mucho más grandes en comparación con la colección mongo normal
  7. Uso de tubería para reducir la sobrecarga de memoria

Contras

  1. Mongo GridFS inestable . Incluí la versión A (mongo 1.x) y B (mongo 2.x). En la versión A, cuando canalizaba archivos grandes> 10 MB, recibí muchos errores, incluido un archivo dañado, canalización sin terminar. Este problema se resuelve en la versión B usando mongo 2.x, con suerte meteor se actualizará a mongodb 2.x pronto
  2. Confusión de API . En la versión A, debe abrir el archivo antes de poder transmitir, pero en la versión B, puede transmitir sin abrir. El documento API tampoco es muy claro y la transmisión no es 100% intercambiable en sintaxis con Npm.require('fs'). En fs, llama a file.on('finish') pero en GridFS llama a file.on('end') cuando escribe termina/finaliza.
  3. GridFS no proporciona atomicidad de escritura, por lo que si hay varias escrituras simultáneas en el mismo archivo, el resultado final puede ser muy diferente
  4. Velocidad . Mongo GridFS es mucho más lento que el sistema de archivos.

Valor de referencia Puede ver en la opción 2 y la opción 3, incluí var start =Date.now() y cuando escribo end, consola.cierro la hora en ms , a continuación se muestra el resultado. Doble núcleo, 4 GB de RAM, HDD, basado en ubuntu 14.04.

file size   GridFS  FS
100 KB      50      2
1 MB        400     30
10 MB       3500    100
200 MB      80000   1240

Puede ver que FS es mucho más rápido que GridFS. Para un archivo de 200 MB, toma ~80 segundos usando GridFS pero solo ~1 segundo en FS. No he probado SSD, el resultado puede ser diferente. Sin embargo, en la vida real, el ancho de banda puede determinar qué tan rápido se transmite el archivo del cliente al servidor, no es típico lograr una velocidad de transferencia de 200 MB/seg. Por otro lado, una velocidad de transferencia de ~2 MB/seg (GridFS) es más la norma.

Conclusión

De ninguna manera esto es completo, pero puede decidir qué opción es la mejor para sus necesidades.

  • DD es el más simple y se adhiere al principio central de Meteor, pero los datos son más voluminosos, no se pueden comprimir durante la transferencia, no se pueden almacenar en caché. Pero esta opción puede ser buena si solo necesita archivos pequeños.
  • XHR junto con el sistema de archivos es la forma 'tradicional'. API estable, rápida, 'transmitible', comprimible, almacenable en caché (ETag, etc.), pero debe estar en una carpeta separada
  • XHR junto con GridFS , obtiene el beneficio del conjunto de representantes, escalable, sin tocar el directorio del sistema de archivos, archivos grandes y muchos archivos si el sistema de archivos restringe los números, también comprimible en caché. Sin embargo, la API es inestable, se obtienen errores en varias escrituras, es lenta.

Con suerte, pronto, Meteor DDP podrá admitir gzip, almacenamiento en caché, etc., y GridFS podrá ser más rápido. ...