sql >> Database >  >> NoSQL >> MongoDB

Meteor:bestand uploaden van client naar Mongo-verzameling versus bestandssysteem versus GridFS

U kunt bestanden uploaden met Meteor zonder meer pakketten of een derde partij te gebruiken

Optie 1:DDP, bestand opslaan in een mongo-verzameling

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

Uitleg

Eerst wordt het bestand uit de invoer gehaald met behulp van HTML5 File API. Er wordt een reader gemaakt met de nieuwe FileReader. Het bestand wordt gelezen als readAsArrayBuffer. Deze arraybuffer, als je console.log, retourneert {} en DDP kan dit niet over de draad verzenden, dus het moet worden geconverteerd naar Uint8Array.

Wanneer u dit in Meteor.call plaatst, voert Meteor automatisch EJSON.stringify(Uint8Array) uit en verzendt het met DDP. U kunt de gegevens controleren in het websocket-verkeer van de Chrome-console, u ziet een tekenreeks die lijkt op base64

Aan de serverkant roept Meteor EJSON.parse() aan en converteert het terug naar buffer

Pluspunten

  1. Eenvoudig, geen hacky manier, geen extra pakketten
  2. Houd je aan het Data on the Wire-principe

Nadelen

  1. Meer bandbreedte:de resulterende base64-string is ~ 33% groter dan het originele bestand
  2. Bestandsgroottelimiet:kan geen grote bestanden verzenden (limiet ~ 16 MB?)
  3. Geen cache
  4. Nog geen gzip of compressie
  5. Veel geheugen in beslag nemen als je bestanden publiceert

Optie 2:XHR, post van client naar bestandssysteem

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

Uitleg

Het bestand in de client wordt gepakt, er wordt een XHR-object gemaakt en het bestand wordt via 'POST' naar de server gestuurd.

Op de server worden de gegevens doorgesluisd naar een onderliggend bestandssysteem. U kunt bovendien de bestandsnaam bepalen, sanering uitvoeren of controleren of deze al bestaat enz. voordat u opslaat.

Pluspunten

  1. Profiterend van XHR 2 zodat u arraybuffer kunt verzenden, is er geen nieuwe FileReader() nodig in vergelijking met optie 1
  2. Arraybuffer is minder omvangrijk in vergelijking met base64-tekenreeks
  3. Geen maximale grootte, ik heb zonder problemen een bestand van ~ 200 MB naar localhost verzonden
  4. Bestandssysteem is sneller dan mongodb (hierover later meer in benchmarking hieronder)
  5. Cachable en gzip

Nadelen

  1. XHR 2 is niet beschikbaar in oudere browsers, b.v. onder IE10, maar je kunt natuurlijk een traditionele post implementeren
    Ik heb alleen xhr =new XMLHttpRequest() gebruikt in plaats van HTTP.call('POST') omdat de huidige HTTP.call in Meteor nog geen arraybuffer kan verzenden (wijs me aan als ik het mis heb).
  2. /pad/naar/dir/ moet buiten meteor zijn, anders wordt het opnieuw geladen door een bestand in /public te schrijven

Optie 3:XHR, opslaan in 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);
});     

Uitleg

Het clientscript is hetzelfde als in optie 2.

Volgens de laatste regel van Meteor 1.0.x mongo_driver.j wordt een globaal object met de naam MongoInternals weergegeven, u kunt defaultRemoteCollectionDriver() aanroepen om het huidige database-db-object te retourneren dat vereist is voor de GridStore. In versie A wordt de GridStore ook zichtbaar gemaakt door de MongoInternals. De mongo die wordt gebruikt door de huidige meteoor is v1.4.x

Vervolgens kunt u binnen een route een nieuw schrijfobject maken door var file =new GridStore(...) (API) aan te roepen. Vervolgens opent u het bestand en maakt u een stream.

Ik heb ook een versie B toegevoegd. In deze versie wordt de GridStore aangeroepen met een nieuwe mongodb-drive via Npm.require('mongodb'), deze mongo is de nieuwste v2.0.13 op het moment van schrijven. Voor de nieuwe API hoeft u het bestand niet te openen, u kunt stream(true) rechtstreeks aanroepen en beginnen met piping

Pluspunten

  1. Hetzelfde als in optie 2, verzonden met arraybuffer, minder overhead vergeleken met base64 string in optie 1
  2. U hoeft zich geen zorgen te maken over het opschonen van bestandsnamen
  3. Scheiding van het bestandssysteem, het is niet nodig om naar de tijdelijke map te schrijven, de db kan worden geback-upt, rep, shard enz.
  4. Het is niet nodig om een ​​ander pakket te implementeren
  5. Cachable en kan worden gezipt
  6. Bewaar veel grotere maten in vergelijking met de normale mongo-collectie
  7. Pijp gebruiken om geheugenoverbelasting te verminderen

Nadelen

  1. Instabiele Mongo GridFS . Ik heb versie A (mongo 1.x) en B (mongo 2.x) toegevoegd. In versie A, bij het pipen van grote bestanden> 10 MB, kreeg ik veel fouten, waaronder een beschadigd bestand, onvoltooide pijp. Dit probleem is opgelost in versie B met behulp van mongo 2.x, hopelijk zal meteor binnenkort upgraden naar mongodb 2.x
  2. API-verwarring . In versie A moet je het bestand openen voordat je kunt streamen, maar in versie B kun je streamen zonder open te bellen. Het API-document is ook niet erg duidelijk en de stream is niet 100% syntaxis uitwisselbaar met Npm.require('fs'). In fs roept u file.on('finish') aan, maar in GridFS roept u file.on('end') aan wanneer het schrijven eindigt/eindigt.
  3. GridFS biedt geen schrijf-atomiciteit, dus als er meerdere gelijktijdige schrijfacties naar hetzelfde bestand zijn, kan het uiteindelijke resultaat heel anders zijn
  4. Snelheid . Mongo GridFS is veel langzamer dan het bestandssysteem.

Benchmark Je kunt in optie 2 en optie 3 zien dat ik var start =Date.now() heb toegevoegd en bij het schrijven van end, console.log de tijd uit in ms , hieronder het resultaat. Dual Core, 4 GB ram, HDD, op ubuntu 14.04 gebaseerd.

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

Je kunt zien dat FS veel sneller is dan GridFS. Voor een bestand van 200 MB duurt het ~80 sec met GridFS, maar slechts ~ 1 sec in FS. SSD heb ik niet geprobeerd, het resultaat kan anders zijn. In het echte leven kan de bandbreedte echter bepalen hoe snel het bestand van client naar server wordt gestreamd, het bereiken van een overdrachtssnelheid van 200 MB/sec is niet gebruikelijk. Aan de andere kant is een overdrachtssnelheid van ~2 MB/sec (GridFS) meer de norm.

Conclusie

Dit is zeker niet allesomvattend, maar u kunt zelf beslissen welke optie het beste bij u past.

  • DDP is de eenvoudigste en houdt vast aan het kernprincipe van Meteor, maar de gegevens zijn omvangrijker, niet samendrukbaar tijdens de overdracht, niet in cache op te nemen. Maar deze optie kan goed zijn als je alleen kleine bestanden nodig hebt.
  • XHR gekoppeld aan bestandssysteem is de 'traditionele' manier. Stabiele API, snel, 'streamable', compressible, cachable (ETag etc), maar moet in een aparte map staan
  • XHR gekoppeld aan GridFS , krijg je het voordeel van rep set, schaalbaar, geen aanraken bestandssysteem dir, grote bestanden en veel bestanden als het bestandssysteem de aantallen beperkt, ook in cache comprimeerbaar. De API is echter onstabiel, je krijgt fouten bij meerdere schrijfacties, het is s..l..o..w..

Hopelijk kan meteor DDP binnenkort gzip, caching enz. ondersteunen en kan GridFS sneller zijn ...



  1. Documenten samenvoegen bij het importeren van een bestand in MongoDB

  2. Hoe update je meerdere velden met Update.Set in MongoDB met behulp van de officiële c#-driver?

  3. Auditlogboekregistratie voor MongoDB

  4. Spring data redis overschrijven standaard serializer