MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Meteor:przesyłanie pliku z klienta do kolekcji Mongo vs system plików vs GridFS

Możesz przesyłać pliki za pomocą Meteor bez korzystania z dodatkowych pakietów lub stron trzecich

Opcja 1:DDP, zapisywanie pliku do kolekcji 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})         
    }   
});

Wyjaśnienie

Najpierw plik jest pobierany z danych wejściowych za pomocą interfejsu API plików HTML5. Czytnik jest tworzony za pomocą nowego FileReadera. Plik jest odczytywany jako readAsArrayBuffer. Ten bufor tablicy, jeśli console.log, zwraca {} i DDP nie może wysłać tego przez przewód, więc musi zostać przekonwertowany na Uint8Array.

Gdy umieścisz to w Meteor.call, Meteor automatycznie uruchomi EJSON.stringify(Uint8Array) i wyśle ​​go za pomocą DDP. Możesz sprawdzić dane w ruchu websocket konsoli Chrome, zobaczysz ciąg przypominający base64

Po stronie serwera Meteor wywołuje EJSON.parse() i konwertuje go z powrotem do bufora

Zalety

  1. Prosty, bez hacków, bez dodatkowych pakietów
  2. Trzymaj się zasady danych na przewodzie

Wady

  1. Większa przepustowość:wynikowy ciąg base64 jest ~ 33% większy niż oryginalny plik
  2. Limit rozmiaru pliku:nie można wysyłać dużych plików (limit ~ 16 MB?)
  3. Brak pamięci podręcznej
  4. Nie ma jeszcze gzip ani kompresji
  5. Zajmuj dużo pamięci, jeśli publikujesz pliki

Opcja 2:XHR, wysyłaj wiadomości od klienta do systemu plików

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

Wyjaśnienie

Plik w kliencie zostaje przechwycony, tworzony jest obiekt XHR, a plik jest wysyłany przez „POST” na serwer.

Na serwerze dane są przesyłane do bazowego systemu plików. Możesz dodatkowo określić nazwę pliku, przeprowadzić oczyszczanie lub sprawdzić, czy już istnieje itp. przed zapisaniem.

Zalety

  1. Korzystając z XHR 2, dzięki czemu możesz wysłać bufor tablicy, nie jest potrzebny nowy FileReader() w porównaniu z opcją 1
  2. Bufor tablicy jest mniej obszerny w porównaniu z ciągiem base64
  3. Brak limitu rozmiaru, bez problemu wysłałem plik ~ 200 MB na localhost
  4. System plików jest szybszy niż mongodb (więcej na ten temat w dalszej części testu porównawczego poniżej)
  5. Buforowanie i gzip

Wady

  1. XHR 2 nie jest dostępny w starszych przeglądarkach, m.in. poniżej IE10, ale oczywiście możesz zaimplementować tradycyjny post
    Użyłem tylko xhr =new XMLHttpRequest(), a nie HTTP.call('POST'), ponieważ obecny HTTP.call w Meteor nie jest jeszcze w stanie wysłać arraybuffer (wskaż mi, jeśli się mylę).
  2. /ścieżka/do/katalogu/ musi znajdować się poza meteorem, w przeciwnym razie zapisanie pliku w /public spowoduje przeładowanie

Opcja 3:XHR, zapisz w 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);
});     

Wyjaśnienie

Skrypt klienta jest taki sam jak w opcji 2.

Zgodnie z ostatnią linią Meteor 1.0.x mongo_driver.js, globalny obiekt o nazwie MongoInternals jest ujawniony, możesz wywołać defaultRemoteCollectionDriver(), aby zwrócić bieżący obiekt bazy danych, który jest wymagany dla GridStore. W wersji A GridStore jest również udostępniany przez MongoInternals. Mongo używane przez obecny meteor to v1.4.x

Następnie wewnątrz trasy możesz utworzyć nowy obiekt zapisu, wywołując var file =new GridStore(...) (API). Następnie otwierasz plik i tworzysz strumień.

Dołączyłem również wersję B. W tej wersji GridStore nazywa się używając nowego dysku mongodb przez Npm.require('mongodb'), ten mongo jest najnowszą wersją 2.0.13 w momencie pisania tego tekstu. Nowy interfejs API nie wymaga otwierania pliku, możesz bezpośrednio wywołać stream(true) i rozpocząć przesyłanie

Zalety

  1. Tak samo jak w opcji 2, wysyłane przy użyciu bufora tablicy, mniej narzutu w porównaniu z ciągiem base64 w opcji 1
  2. Nie musisz się martwić o czyszczenie nazwy pliku
  3. Oddzielenie od systemu plików, nie ma potrzeby zapisywania do katalogu tymczasowego, można wykonać kopię zapasową bazy danych, rep, shard itp.
  4. Nie ma potrzeby wdrażania żadnego innego pakietu
  5. Możliwość buforowania i skompresowania gzipem
  6. Przechowuj znacznie większe rozmiary w porównaniu do normalnej kolekcji mongo
  7. Korzystanie z potoku w celu zmniejszenia przeciążenia pamięci

Wady

  1. Niestabilny system Mongo GridFS . Dołączyłem wersję A (mongo 1.x) i B (mongo 2.x). W wersji A, podczas przesyłania dużych plików> 10 MB, otrzymywałem wiele błędów, w tym uszkodzony plik, niedokończony potok. Ten problem został rozwiązany w wersji B za pomocą mongo 2.x, miejmy nadzieję, że meteor wkrótce zaktualizuje się do mongodb 2.x
  2. Pomyłka z API . W wersji A musisz otworzyć plik, zanim będzie można przesyłać strumieniowo, ale w wersji B możesz przesyłać strumieniowo bez wywoływania open. Dokumentacja API również nie jest zbyt przejrzysta, a składnia strumienia nie jest w 100% wymienna z Npm.require('fs'). W fs wywołujesz file.on('finish'), ale w GridFS wywołujesz file.on('end') podczas pisania finish/ends.
  3. GridFS nie zapewnia atomowości zapisu, więc jeśli istnieje wiele równoczesnych zapisów do tego samego pliku, ostateczny wynik może być bardzo różny
  4. Prędkość . Mongo GridFS jest znacznie wolniejszy niż system plików.

Wzorzec Jak widać w opcji 2 i 3, włączyłem var start =Date.now() i pisząc end, console.log wylogowuje czas w ms , poniżej znajduje się wynik. Dwurdzeniowy, 4 GB pamięci RAM, HDD, oparty na ubuntu 14.04.

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

Widać, że FS jest znacznie szybszy niż GridFS. W przypadku pliku o wielkości 200 MB zajmuje to ~80 sekund przy użyciu GridFS, ale tylko ~1 sekunda w FS. SSD nie próbowałem, wynik może być inny. Jednak w rzeczywistości przepustowość może decydować o szybkości przesyłania pliku z klienta do serwera, a osiągnięcie prędkości transferu 200 MB/s nie jest typowe. Z drugiej strony prędkość transferu ~2 MB/s (GridFS) jest bardziej normą.

Wniosek

W żadnym wypadku nie jest to kompleksowe, ale możesz zdecydować, która opcja najlepiej odpowiada Twoim potrzebom.

  • DDP jest najprostszy i trzyma się podstawowej zasady Meteor, ale dane są bardziej obszerne, nie można ich kompresować podczas przesyłania i nie można ich przechowywać w pamięci podręcznej. Ale ta opcja może być dobra, jeśli potrzebujesz tylko małych plików.
  • XHR w połączeniu z systemem plików to „tradycyjny” sposób. Stabilny interfejs API, szybki, „strumieniowy”, kompresowalny, buforowany (ETag itp.), ale musi znajdować się w osobnym folderze
  • XHR w połączeniu z GridFS , zyskujesz zestaw powtórzeń, skalowalny, nie dotykający katalogu systemu plików, duże pliki i wiele plików, jeśli system plików ogranicza liczby, a także możliwość skompresowania w pamięci podręcznej. Jednak API jest niestabilne, pojawiają się błędy w wielokrotnych zapisach, jest to...

Mamy nadzieję, że wkrótce meteor DDP będzie obsługiwać gzip, buforowanie itp., a GridFS może być szybszy ...



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. ustaw pole jako puste dla obiektu mongo za pomocą mongoose

  2. mongo - problem z połączeniem rubin

  3. MongoDB jako baza danych szeregów czasowych

  4. MongoDB zmienia nazwę pola bazy danych w tablicy

  5. MongoDB:Błąd krytyczny:nie znaleziono klasy „MongoClient”