sql >> Database teknologi >  >> NoSQL >> MongoDB

Meteor:upload af fil fra klient til Mongo samling vs filsystem vs GridFS

Du kan opnå filupload med Meteor uden at bruge flere pakker eller en tredjepart

Mulighed 1:DDP, gemmer fil til en mongo-samling

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

Forklaring

Først hentes filen fra input ved hjælp af HTML5 File API. En læser oprettes ved hjælp af den nye FileReader. Filen læses som readAsArrayBuffer. Denne arraybuffer, hvis du console.log, returnerer {} og DDP kan ikke sende denne over ledningen, så den skal konverteres til Uint8Array.

Når du sætter dette i Meteor.call, kører Meteor automatisk EJSON.stringify(Uint8Array) og sender det med DDP. Du kan tjekke dataene i chrome-konsollens websocket-trafik, du vil se en streng, der ligner base64

På serversiden kalder Meteor EJSON.parse() og konverterer det tilbage til buffer

Fordele

  1. Simpel, ingen hacky måde, ingen ekstra pakker
  2. Hold dig til Data on the Wire-princippet

Udemper

  1. Mere båndbredde:den resulterende base64-streng er ~ 33 % større end den originale fil
  2. Grænse for filstørrelse:kan ikke sende store filer (grænse ~ 16 MB?)
  3. Ingen cachelagring
  4. Ingen gzip eller komprimering endnu
  5. Optag meget hukommelse, hvis du udgiver filer

Mulighed 2:XHR, post fra klient til filsystem

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

Forklaring

Filen i klienten gribes, et XHR-objekt oprettes og filen sendes via 'POST' til serveren.

På serveren overføres dataene til et underliggende filsystem. Du kan desuden bestemme filnavnet, udføre desinficering eller kontrollere, om det allerede eksisterer osv., før du gemmer.

Fordele

  1. Ved at drage fordel af XHR 2, så du kan sende arraybuffer, er ingen ny FileReader() nødvendig sammenlignet med mulighed 1
  2. Arraybuffer er mindre omfangsrig sammenlignet med base64-streng
  3. Ingen størrelsesgrænse, jeg sendte en fil ~ 200 MB i localhost uden problemer
  4. Filsystemet er hurtigere end mongodb (mere af dette senere i benchmarking nedenfor)
  5. Cachable og gzip

Udemper

  1. XHR 2 er ikke tilgængelig i ældre browsere, f.eks. under IE10, men du kan selvfølgelig implementere en traditionel post
    Jeg brugte kun xhr =new XMLHttpRequest(), i stedet for HTTP.call('POST'), fordi det nuværende HTTP.call i Meteor endnu ikke er i stand til at sende arraybuffer (peg mig, hvis jeg tager fejl).
  2. /path/to/dir/ skal være uden for meteor, ellers udløser skrivning af en fil i /public en genindlæsning

Mulighed 3:XHR, gem til 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);
});     

Forklaring

Klientscriptet er det samme som i mulighed 2.

Ifølge Meteor 1.0.x mongo_driver.js sidste linje, er et globalt objekt kaldet MongoInternals afsløret, du kan kalde defaultRemoteCollectionDriver() for at returnere det aktuelle database-db-objekt, som kræves til GridStore. I version A er GridStore også eksponeret af MongoInternals. Mongoen brugt af nuværende meteor er v1.4.x

Så inde i en rute kan du oprette et nyt skriveobjekt ved at kalde var file =new GridStore(...) (API). Du åbner derefter filen og opretter en stream.

Jeg inkluderede også en version B. I denne version kaldes GridStore ved at bruge et nyt mongodb-drev via Npm.require('mongodb'), denne mongo er den seneste v2.0.13, når dette skrives. Den nye API kræver ikke, at du åbner filen, du kan ringe direkte til stream(true) og begynde at pibe

Fordele

  1. Samme som i mulighed 2, sendt med arraybuffer, mindre overhead sammenlignet med base64-streng i mulighed 1
  2. Ingen grund til at bekymre sig om rensning af filnavne
  3. Adskillelse fra filsystem, ingen grund til at skrive til temp dir, db kan sikkerhedskopieres, rep, shard osv.
  4. Ingen grund til at implementere nogen anden pakke
  5. Cachbar og kan gzippes
  6. Opbevar meget større størrelser sammenlignet med normal mongokollektion
  7. Brug af pipe til at reducere hukommelsesoverbelastning

Udemper

  1. Ustabil Mongo GridFS . Jeg inkluderede version A (mongo 1.x) og B (mongo 2.x). I version A, når jeg overførte store filer> 10 MB, fik jeg masser af fejl, inklusive beskadiget fil, ufærdig pipe. Dette problem er løst i version B ved hjælp af mongo 2.x, forhåbentlig vil meteor snart opgradere til mongodb 2.x
  2. API-forvirring . I version A skal du åbne filen, før du kan streame, men i version B kan du streame uden at kalde åben. API-dokumentet er heller ikke særlig tydeligt, og streamen kan ikke 100% syntaks udskiftes med Npm.require('fs'). I fs kalder du file.on('finish'), men i GridFS kalder du file.on('end'), når skrivning afsluttes/slutter.
  3. GridFS giver ikke skriveatomicitet, så hvis der er flere samtidige skrivninger til den samme fil, kan det endelige resultat være meget anderledes
  4. Hastighed . Mongo GridFS er meget langsommere end filsystemet.

Benchmark Du kan se i mulighed 2 og mulighed 3, jeg inkluderede var start =Date.now(), og når jeg skriver slut, console.log jeg tiden ud i ms , nedenfor er resultatet. Dual Core, 4 GB ram, HDD, ubuntu 14.04 baseret.

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

Du kan se, at FS er meget hurtigere end GridFS. For en fil på 200 MB tager det ~80 sek ved at bruge GridFS, men kun ~ 1 sek i FS. Jeg har ikke prøvet SSD, resultatet kan være anderledes. I det virkelige liv kan båndbredden dog diktere, hvor hurtigt filen streames fra klient til server, og det er ikke typisk at opnå en overførselshastighed på 200 MB/sek. På den anden side er en overførselshastighed ~2 MB/sek (GridFS) mere normen.

Konklusion

Dette er på ingen måde omfattende, men du kan bestemme, hvilken mulighed der passer bedst til dit behov.

  • DDP er den enkleste og holder sig til kerne Meteor-princippet, men dataene er mere omfangsrige, ikke komprimerbare under overførsel, ikke cachbare. Men denne mulighed kan være god, hvis du kun har brug for små filer.
  • XHR koblet med filsystem er den 'traditionelle' måde. Stabil API, hurtig, 'streambar', komprimerbar, cachbar (ETag osv.), men skal være i en separat mappe
  • XHR koblet med GridFS , får du fordelen af ​​rep sæt, skalerbar, ingen berøring filsystem dir, store filer og mange filer, hvis filsystemet begrænser tallene, også cachable komprimerbar. API'et er dog ustabilt, du får fejl i flere skrivninger, det er s..l..o..w..

Forhåbentlig snart kan meteor DDP understøtte gzip, caching osv., og GridFS kan være hurtigere ...



  1. Hvad er $unwind-operatøren i MongoDB?

  2. Lagring af kataloghierarki i et nøgleværdidatalager

  3. MongoDB shell og server matcher ikke

  4. Hvordan får man opdateret dokument tilbage fra findOneAndUpdate-metoden?