MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

Meteor:mengunggah file dari klien ke koleksi Mongo vs sistem file vs GridFS

Anda dapat mencapai pengunggahan file dengan Meteor tanpa menggunakan paket atau pihak ketiga lagi

Opsi 1:DDP, menyimpan file ke koleksi 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})         
    }   
});

Penjelasan

Pertama, file diambil dari input menggunakan HTML5 File API. Pembaca dibuat menggunakan FileReader baru. File dibaca sebagai readAsArrayBuffer. Arraybuffer ini, jika Anda console.log, mengembalikan {} dan DDP tidak bisa mengirim ini melalui kabel, sehingga harus dikonversi ke Uint8Array.

Saat Anda memasukkan ini ke Meteor.call, Meteor secara otomatis menjalankan EJSON.stringify(Uint8Array) dan mengirimkannya dengan DDP. Anda dapat memeriksa data di lalu lintas websocket konsol chrome, Anda akan melihat string yang menyerupai base64

Di sisi server, Meteor memanggil EJSON.parse() dan mengubahnya kembali menjadi buffer

Pro

  1. Sederhana, tanpa peretasan, tanpa paket tambahan
  2. Tetap pada prinsip Data on the Wire

Kontra

  1. Lebih banyak bandwidth:string base64 yang dihasilkan ~ 33% lebih besar dari file aslinya
  2. Batas ukuran file:tidak dapat mengirim file besar (batas ~ 16 MB?)
  3. Tanpa cache
  4. Belum ada gzip atau kompresi
  5. Menghabiskan banyak memori jika Anda memublikasikan file

Opsi 2:XHR, posting dari klien ke sistem file

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

Penjelasan

File di klien diambil, objek XHR dibuat dan file dikirim melalui 'POST' ke server.

Di server, data disalurkan ke sistem file yang mendasarinya. Anda juga dapat menentukan nama file, melakukan sanitasi atau memeriksa apakah sudah ada, dll sebelum menyimpan.

Pro

  1. Memanfaatkan XHR 2 sehingga Anda dapat mengirim arraybuffer, tidak diperlukan FileReader() baru dibandingkan dengan opsi 1
  2. Arraybuffer kurang besar dibandingkan dengan string base64
  3. Tidak ada batasan ukuran, saya mengirim file ~ 200 MB di localhost tanpa masalah
  4. Sistem file lebih cepat daripada mongodb (lebih banyak lagi nanti di benchmarking di bawah)
  5. Dapat disimpan dalam cache dan gzip

Kontra

  1. XHR 2 tidak tersedia di browser lama, mis. di bawah IE10, tetapi tentu saja Anda dapat mengimplementasikan posting tradisional
    Saya hanya menggunakan xhr =new XMLHttpRequest(), daripada HTTP.call('POST') karena HTTP.call saat ini di Meteor belum dapat mengirim arraybuffer (tunjuk saya jika saya salah).
  2. /path/to/dir/ harus berada di luar meteor, jika tidak, menulis file di /public akan memicu pemuatan ulang

Opsi 3:XHR, simpan ke 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);
});     

Penjelasan

Skrip klien sama seperti pada opsi 2.

Menurut Meteor 1.0.x mongo_driver.js baris terakhir, objek global yang disebut MongoInternals terbuka, Anda dapat memanggil defaultRemoteCollectionDriver() untuk mengembalikan objek db database saat ini yang diperlukan untuk GridStore. Dalam versi A, GridStore juga diekspos oleh MongoInternals. Mongo yang digunakan oleh meteor saat ini adalah v1.4.x

Kemudian di dalam rute, Anda dapat membuat objek tulis baru dengan memanggil var file =new GridStore(...) (API). Anda kemudian membuka file dan membuat aliran.

Saya juga menyertakan versi B. Dalam versi ini, GridStore dipanggil menggunakan drive mongodb baru melalui Npm.require('mongodb'), mongo ini adalah v2.0.13 terbaru pada saat tulisan ini dibuat. API baru tidak mengharuskan Anda untuk membuka file, Anda dapat memanggil stream(true) secara langsung dan mulai melakukan pemipaan

Pro

  1. Sama seperti pada opsi 2, dikirim menggunakan arraybuffer, lebih sedikit overhead dibandingkan dengan string base64 pada opsi 1
  2. Tidak perlu khawatir tentang pembersihan nama file
  3. Pemisahan dari sistem file, tidak perlu menulis ke temp dir, db dapat di-backup, rep, shard dll
  4. Tidak perlu mengimplementasikan paket lain
  5. Dapat disimpan dalam cache dan dapat di-gzip
  6. Menyimpan ukuran yang jauh lebih besar dibandingkan dengan koleksi mongo biasa
  7. Menggunakan pipa untuk mengurangi kelebihan memori

Kontra

  1. Grid Mongo Tidak Stabil . Saya menyertakan versi A (mongo 1.x) dan B (mongo 2.x). Di versi A, ketika melakukan pemipaan file besar> 10 MB, saya mendapatkan banyak kesalahan, termasuk file yang rusak, pipa yang belum selesai. Masalah ini diselesaikan di versi B menggunakan mongo 2.x, semoga meteor segera ditingkatkan ke mongodb 2.x
  2. Kebingungan API . Di versi A, Anda perlu membuka file sebelum dapat melakukan streaming, tetapi di versi B, Anda dapat melakukan streaming tanpa memanggil open. Dokumen API juga tidak terlalu jelas dan alirannya tidak 100% sintaks dapat ditukar dengan Npm.require('fs'). Di fs, Anda memanggil file.on('finish') tetapi di GridFS Anda memanggil file.on('end') saat menulis selesai/berakhir.
  3. GridFS tidak menyediakan atomisitas penulisan, jadi jika ada beberapa penulisan bersamaan ke file yang sama, hasil akhirnya mungkin sangat berbeda
  4. Kecepatan . Mongo GridFS jauh lebih lambat daripada sistem file.

Tolok ukur Anda dapat melihat di opsi 2 dan opsi 3, saya menyertakan var start =Date.now() dan saat menulis end, saya console.log out waktu dalam ms , di bawah ini adalah hasilnya. Dual Core, ram 4 GB, HDD, berbasis ubuntu 14.04.

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

Anda dapat melihat bahwa FS jauh lebih cepat daripada GridFS. Untuk file 200 MB, dibutuhkan ~80 detik menggunakan GridFS tetapi hanya ~ 1 detik di FS. Saya belum mencoba SSD, hasilnya mungkin berbeda. Namun, dalam kehidupan nyata, bandwidth dapat menentukan seberapa cepat file dialirkan dari klien ke server, mencapai kecepatan transfer 200 MB/detik bukanlah hal yang biasa. Di sisi lain, kecepatan transfer ~2 MB/dtk (GridFS) lebih normal.

Kesimpulan

Ini tidak berarti komprehensif, tetapi Anda dapat memutuskan opsi mana yang terbaik untuk kebutuhan Anda.

  • DDP adalah yang paling sederhana dan berpegang pada prinsip inti Meteor tetapi datanya lebih besar, tidak dapat dikompresi selama transfer, tidak dapat disimpan dalam cache. Namun opsi ini mungkin bagus jika Anda hanya membutuhkan file kecil.
  • XHR digabungkan dengan sistem file adalah cara 'tradisional'. API yang stabil, cepat, 'streamable', dapat dikompresi, dapat disimpan dalam cache (ETag dll), tetapi harus berada di folder terpisah
  • XHR digabungkan dengan GridFS , Anda mendapatkan manfaat dari rep set, scalable, tidak ada dir sistem file yang menyentuh, file besar dan banyak file jika sistem file membatasi jumlahnya, juga dapat dikompresi dalam cache. Namun, API tidak stabil, Anda mendapatkan kesalahan dalam banyak penulisan, ini adalah s..l..o..w..

Semoga segera, meteor DDP bisa support gzip, caching dll dan GridFS bisa lebih cepat ...



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Bagaimana cara menggunakan transaksi MongoDB menggunakan Mongoose?

  2. Mengapa Mongoose menambahkan array kosong?

  3. MongoDB C# Driver 2.0 - Perbarui dokumen

  4. Cara Membuat Indeks Teks di MongoDB

  5. Kemungkinan duplikat Mongo ObjectId dihasilkan dalam dua koleksi berbeda?