sql >> Databáze >  >> NoSQL >> MongoDB

Meteor:nahrávání souboru z klienta do kolekce Mongo vs souborový systém vs GridFS

Pomocí Meteoru můžete nahrát soubory bez použití dalších balíčků nebo třetí strany

Možnost 1:DDP, uložení souboru do sbírky 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})         
    }   
});

Vysvětlení

Nejprve je soubor uchopen ze vstupu pomocí HTML5 File API. Čtečka je vytvořena pomocí nového FileReaderu. Soubor se čte jako readAsArrayBuffer. Tento arraybuffer, pokud console.log, vrací {} a DDP to nemůže poslat po drátě, takže musí být převeden na Uint8Array.

Když to vložíte do Meteor.call, Meteor automaticky spustí EJSON.stringify(Uint8Array) a odešle to s DDP. Data můžete zkontrolovat v provozu webového soketu konzoly Chrome, uvidíte řetězec připomínající base64

Na straně serveru Meteor zavolá EJSON.parse() a převede jej zpět na vyrovnávací paměť

Výhody

  1. Jednoduchý, žádný složitý způsob, žádné další balíčky
  2. Držte se principu Data on the Wire

Nevýhody

  1. Větší šířka pásma:výsledný řetězec base64 je ~ o 33 % větší než původní soubor
  2. Limit velikosti souboru:nelze odesílat velké soubory (limit ~ 16 MB?)
  3. Žádné ukládání do mezipaměti
  4. Zatím bez gzip nebo komprese
  5. Při publikování souborů zabere hodně paměti

Možnost 2:XHR, odeslání z klienta do systému souborů

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

Vysvětlení

Soubor v klientovi je uchopen, je vytvořen objekt XHR a soubor je odeslán pomocí 'POST' na server.

Na serveru jsou data přenesena do základního systému souborů. Před uložením můžete dodatečně určit název souboru, provést dezinfekci nebo zkontrolovat, zda již existuje atd.

Výhody

  1. Při využití výhod XHR 2, abyste mohli odesílat arraybuffer, není potřeba žádný nový FileReader() ve srovnání s možností 1
  2. Arraybuffer je méně objemný než řetězec base64
  3. Bez omezení velikosti, bez problémů jsem odeslal soubor ~ 200 MB v localhost
  4. Systém souborů je rychlejší než mongodb (více o tom později ve srovnání níže)
  5. Uložitelné do mezipaměti a gzip

Nevýhody

  1. XHR 2 není k dispozici ve starších prohlížečích, např. pod IE10, ale samozřejmě můžete implementovat tradiční příspěvek
    Použil jsem pouze xhr =new XMLHttpRequest(), spíše než HTTP.call('POST'), protože aktuální HTTP.call v Meteoru ještě není schopen odeslat arraybuffer (ukažte mi, pokud se mýlím).
  2. /path/to/dir/ musí být mimo meteor, jinak zápis souboru do /public spustí opětovné načtení

Možnost 3:XHR, uložit do 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);
});     

Vysvětlení

Klientský skript je stejný jako u možnosti 2.

Podle posledního řádku Meteor 1.0.x mongo_driver.js je odhalen globální objekt nazvaný MongoInternals, můžete zavolat defaultRemoteCollectionDriver() a vrátit aktuální databázový db objekt, který je vyžadován pro GridStore. Ve verzi A je GridStore vystaven také MongoInternals. Mongo používané aktuálním meteorem je v1.4.x

Poté můžete uvnitř trasy vytvořit nový objekt pro zápis voláním var file =new GridStore(...) (API). Poté otevřete soubor a vytvořte stream.

Zahrnul jsem také verzi B. V této verzi se GridStore nazývá pomocí nového disku mongodb přes Npm.require('mongodb'), toto mongo je nejnovější v2.0.13 v době psaní tohoto článku. Nové API nevyžaduje, abyste soubor otevírali, můžete přímo zavolat stream(true) a spustit pipetování

Výhody

  1. Stejné jako u možnosti 2, odeslané pomocí arraybuffer, menší režie ve srovnání s řetězcem base64 u možnosti 1
  2. Nemusíte si dělat starosti s dezinfekcí názvu souboru
  3. Oddělení od systému souborů, není třeba zapisovat do dočasného adresáře, db lze zálohovat, opakovat, shard atd.
  4. Není třeba implementovat žádný další balíček
  5. Uložitelné do mezipaměti a lze je zazipovat
  6. V porovnání s běžnou sbírkou mongo skladujte mnohem větší velikosti
  7. Použití kanálu ke snížení přetížení paměti

Nevýhody

  1. Nestabilní Mongo GridFS . Zahrnul jsem verzi A (mongo 1.x) a B (mongo 2.x). Ve verzi A jsem při spojování velkých souborů> 10 MB dostal spoustu chyb, včetně poškozeného souboru, nedokončeného potrubí. Tento problém je vyřešen ve verzi B pomocí mongo 2.x, doufejme, že meteor brzy upgraduje na mongodb 2.x
  2. Zmatek v rozhraní API . Ve verzi A musíte před streamováním otevřít soubor, ale ve verzi B můžete streamovat bez volání open. Dokument API také není příliš jasný a stream není 100% zaměnitelný s Npm.require('fs'). V fs zavoláte file.on('finish'), ale v GridFS zavoláte file.on('end') při zápisu finishes/ends.
  3. GridFS neposkytuje atomicity zápisu, takže pokud existuje více souběžných zápisů do stejného souboru, konečný výsledek se může velmi lišit
  4. Rychlost . Mongo GridFS je mnohem pomalejší než souborový systém.

Srovnávací U možnosti 2 a možnosti 3 vidíte, že jsem zahrnul var start =Date.now() a při psaní end jsem console.log out čas v ms , níže je výsledek. Dual Core, 4 GB RAM, HDD, založené na ubuntu 14.04.

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

Můžete vidět, že FS je mnohem rychlejší než GridFS. U souboru o velikosti 200 MB to trvá ~80 sekund pomocí GridFS, ale pouze ~ 1 sekundu ve FS. SSD jsem nezkoušel, výsledek může být jiný. V reálném životě však může šířka pásma určovat, jak rychle je soubor streamován z klienta na server, dosažení přenosové rychlosti 200 MB/s není typické. Na druhou stranu je běžnější přenosová rychlost ~2 MB/s (GridFS).

Závěr

V žádném případě to není vyčerpávající, ale můžete se rozhodnout, která možnost je pro vaše potřeby nejlepší.

  • DDP je nejjednodušší a drží se základního principu Meteor, ale data jsou objemnější, nelze je během přenosu komprimovat, nelze je uložit do mezipaměti. Ale tato možnost může být dobrá, pokud potřebujete pouze malé soubory.
  • XHR ve spojení se systémem souborů je „tradiční“ způsob. Stabilní API, rychlé, 'streamovatelné', komprimovatelné, cachovatelné (ETag atd.), ale musí být v samostatné složce
  • XHR ve spojení s GridFS , získáte výhodu sady opakování, škálovatelné, nedotýkající se adresáře systému souborů, velké soubory a mnoho souborů, pokud souborový systém omezuje počty, také komprimovatelné do mezipaměti. Rozhraní API je však nestabilní, při více zápisech dochází k chybám, je to s..l..o..w..

Doufejme, že brzy bude meteor DDP podporovat gzip, ukládání do mezipaměti atd. a GridFS může být rychlejší ...



  1. mongodb:dotaz na časové období mezi dvěma datovými poli

  2. Měl by být `StackExchange.Redis.ConnectionMultiplexer` `AddSingleton` nebo `AddScope` ve vkládání závislostí .NET Core?

  3. Mongodb $lookup Nefunguje s _id

  4. SQL LPAD()