MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Meteor :téléchargement de fichiers du client vers la collection Mongo vs système de fichiers vs GridFS

Vous pouvez télécharger des fichiers avec Meteor sans utiliser d'autres packages ou un tiers

Option 1 :DDP, enregistrement du fichier dans une collection 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})         
    }   
});

Explication

Tout d'abord, le fichier est extrait de l'entrée à l'aide de l'API de fichier HTML5. Un lecteur est créé à l'aide du nouveau FileReader. Le fichier est lu en tant que readAsArrayBuffer. Ce tampon de tableau, si vous console.log, renvoie {} et DDP ne peut pas l'envoyer sur le réseau, il doit donc être converti en Uint8Array.

Lorsque vous mettez ceci dans Meteor.call, Meteor exécute automatiquement EJSON.stringify(Uint8Array) et l'envoie avec DDP. Vous pouvez vérifier les données dans le trafic websocket de la console chrome, vous verrez une chaîne ressemblant à base64

Côté serveur, Meteor appelle EJSON.parse() et le reconvertit en buffer

Avantages

  1. Simple, sans piratage, sans forfaits supplémentaires
  2. Reste au principe Data on the Wire

Inconvénients

  1. Plus de bande passante :la chaîne base64 résultante est environ 33 % plus grande que le fichier d'origine
  2. Limite de taille de fichier :impossible d'envoyer des fichiers volumineux (limite d'environ 16 Mo ?)
  3. Pas de mise en cache
  4. Pas encore de gzip ni de compression
  5. Prenez beaucoup de mémoire si vous publiez des fichiers

Option 2 :XHR, publication du client vers le système de fichiers

/*** 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
});

Explication

Le fichier dans le client est saisi, un objet XHR est créé et le fichier est envoyé via 'POST' au serveur.

Sur le serveur, les données sont dirigées vers un système de fichiers sous-jacent. Vous pouvez en outre déterminer le nom du fichier, effectuer une désinfection ou vérifier s'il existe déjà, etc. avant de l'enregistrer.

Avantages

  1. En tirant parti de XHR 2 pour pouvoir envoyer un arraybuffer, aucun nouveau FileReader() n'est nécessaire par rapport à l'option 1
  2. Arraybuffer est moins volumineux que la chaîne base64
  3. Pas de limite de taille, j'ai envoyé un fichier ~ 200 Mo en localhost sans problème
  4. Le système de fichiers est plus rapide que mongodb (plus de détails plus tard dans l'analyse comparative ci-dessous)
  5. Cachable et gzip

Inconvénients

  1. XHR 2 n'est pas disponible dans les anciens navigateurs, par ex. sous IE10, mais bien sûr vous pouvez implémenter un post traditionnel
    J'ai seulement utilisé xhr =new XMLHttpRequest(), plutôt que HTTP.call('POST') car le HTTP.call actuel dans Meteor n'est pas encore capable d'envoyer arraybuffer (indiquez-moi si je me trompe).
  2. /path/to/dir/ doit être en dehors de meteor, sinon l'écriture d'un fichier dans /public déclenche un rechargement

Option 3 :XHR, enregistrer dans 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);
});     

Explication

Le script client est le même que dans l'option 2.

Selon la dernière ligne de Meteor 1.0.x mongo_driver.js, un objet global appelé MongoInternals est exposé, vous pouvez appeler defaultRemoteCollectionDriver() pour renvoyer l'objet de base de données actuel requis pour le GridStore. Dans la version A, le GridStore est également exposé par les MongoInternals. Le mongo utilisé par le météore actuel est la v1.4.x

Ensuite, à l'intérieur d'une route, vous pouvez créer un nouvel objet d'écriture en appelant var file =new GridStore(...) (API). Vous ouvrez ensuite le fichier et créez un flux.

J'ai également inclus une version B. Dans cette version, le GridStore est appelé à l'aide d'un nouveau lecteur mongodb via Npm.require('mongodb'), ce mongo est la dernière v2.0.13 à ce jour. La nouvelle API ne vous oblige pas à ouvrir le fichier, vous pouvez appeler stream(true) directement et démarrer le pipe

Avantages

  1. Identique à l'option 2, envoyé à l'aide de arraybuffer, moins de surcharge par rapport à la chaîne base64 de l'option 1
  2. Pas besoin de s'inquiéter du nettoyage des noms de fichiers
  3. Séparation du système de fichiers, pas besoin d'écrire dans le répertoire temporaire, la base de données peut être sauvegardée, rep, shard etc
  4. Pas besoin d'implémenter un autre package
  5. Cachable et gzippable
  6. Stockez des tailles beaucoup plus grandes par rapport à la collection mongo normale
  7. Utilisation de pipe pour réduire la surcharge de la mémoire

Inconvénients

  1. Mongo GridFS instable . J'ai inclus la version A (mongo 1.x) et B (mongo 2.x). Dans la version A, lors de la canalisation de fichiers volumineux> 10 Mo, j'ai eu beaucoup d'erreurs, y compris un fichier corrompu, un tuyau inachevé. Ce problème est résolu dans la version B en utilisant mongo 2.x, espérons que meteor passera bientôt à mongodb 2.x
  2. Confusion API . Dans la version A, vous devez ouvrir le fichier avant de pouvoir diffuser, mais dans la version B, vous pouvez diffuser sans appeler open. La documentation de l'API n'est pas non plus très claire et le flux n'est pas échangeable à 100 % avec la syntaxe Npm.require('fs'). Dans fs, vous appelez file.on('finish') mais dans GridFS vous appelez file.on('end') lors de l'écriture se termine/se termine.
  3. GridFS ne fournit pas d'atomicité d'écriture, donc s'il y a plusieurs écritures simultanées dans le même fichier, le résultat final peut être très différent
  4. Vitesse . Mongo GridFS est beaucoup plus lent que le système de fichiers.

Référence Vous pouvez voir dans l'option 2 et l'option 3, j'ai inclus var start =Date.now() et lors de l'écriture de fin, je console.log out le temps en ms , ci-dessous le résultat. Dual Core, 4 Go de RAM, disque dur, basé sur Ubuntu 14.04.

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

Vous pouvez voir que FS est beaucoup plus rapide que GridFS. Pour un fichier de 200 Mo, cela prend ~80 sec en utilisant GridFS mais seulement ~ 1 sec en FS. Je n'ai pas essayé de SSD, le résultat peut être différent. Cependant, dans la vraie vie, la bande passante peut dicter la vitesse à laquelle le fichier est diffusé du client au serveur, atteindre une vitesse de transfert de 200 Mo/sec n'est pas typique. En revanche, une vitesse de transfert d'environ 2 Mo/sec (GridFS) est plus la norme.

Conclusion

Ce n'est en aucun cas exhaustif, mais vous pouvez décider quelle option correspond le mieux à vos besoins.

  • DDP est le plus simple et colle au principe de base de Meteor mais les données sont plus volumineuses, non compressibles lors du transfert, non cachables. Mais cette option peut être utile si vous n'avez besoin que de petits fichiers.
  • XHR couplé au système de fichiers est la méthode "traditionnelle". API stable, rapide, "streamable", compressible, pouvant être mise en cache (ETag, etc.), mais doit être dans un dossier séparé
  • XHR couplé à GridFS , vous bénéficiez d'un jeu de représentants, évolutif, sans toucher au répertoire du système de fichiers, aux fichiers volumineux et à de nombreux fichiers si le système de fichiers limite les nombres, également compressible en cache. Cependant, l'API est instable, vous obtenez des erreurs dans plusieurs écritures, c'est lent..

Espérons que bientôt, meteor DDP pourra prendre en charge gzip, la mise en cache, etc. et GridFS pourra être plus rapide ...