sql >> データベース >  >> NoSQL >> MongoDB

Meteor:クライアントからMongoコレクションへのファイルのアップロードvsファイルシステムvs GridFS

    これ以上パッケージやサードパーティを使用せずに、Meteorを使用してファイルのアップロードを実現できます

    オプション1:DDP、ファイルを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})         
        }   
    });
    

    説明

    まず、HTML5ファイルAPIを使用して入力からファイルを取得します。リーダーは、新しいFileReaderを使用して作成されます。ファイルはreadAsArrayBufferとして読み取られます。このarraybufferは、console.logの場合、{}を返し、DDPはこれをネットワーク経由で送信できないため、Uint8Arrayに変換する必要があります。

    これをMeteor.callに入れると、Meteorは自動的にEJSON.stringify(Uint8Array)を実行し、DDPで送信します。 ChromeコンソールのWebSocketトラフィックのデータを確認できます。base64に似た文字列が表示されます

    サーバー側では、MeteorがEJSON.parse()を呼び出し、それをバッファーに変換し直します

    長所

    1. シンプル、ハッキーな方法、追加のパッケージはありません
    2. ワイヤーの原則に関するデータに固執する

    短所

    1. より多くの帯域幅:結果のbase64文字列は元のファイルよりも約33%大きくなります
    2. ファイルサイズの制限:大きなファイルを送信できません(制限〜16 MB?)
    3. キャッシュなし
    4. gzipまたは圧縮はまだありません
    5. ファイルを公開する場合は、大量のメモリを消費します

    オプション2:XHR、クライアントからファイルシステムへの投稿

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

    説明

    クライアント内のファイルが取得され、XHRオブジェクトが作成され、ファイルが「POST」を介してサーバーに送信されます。

    サーバーでは、データは基盤となるファイルシステムにパイプされます。保存する前に、ファイル名を決定したり、サニタイズを実行したり、ファイル名がすでに存在するかどうかを確認したりすることもできます。

    長所

    1. XHR 2を利用してarraybufferを送信できるため、オプション1と比較して新しいFileReader()は必要ありません
    2. Arraybufferはbase64文字列に比べてかさばりません
    3. サイズ制限なし、ローカルホストで200MBまでのファイルを問題なく送信しました
    4. ファイルシステムはmongodbよりも高速です(これについては、以下のベンチマークで詳しく説明します)
    5. Cachableとgzip

    短所

    1. XHR 2は、古いブラウザでは使用できません。 IE10より下ですが、もちろん、従来の投稿を実装できます
      Meteorの現在のHTTP.callはまだarraybufferを送信できないため、HTTP.call('POST')ではなくxhr =new XMLHttpRequest()のみを使用しました(私が間違っている場合は私を指してください。)
    2. / path / to / dir /は流星の外にある必要があります。そうでない場合、/publicにファイルを書き込むとリロードがトリガーされます

    オプション3:XHR、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);
    });     
    

    説明

    クライアントスクリプトはオプション2と同じです。

    Meteor 1.0.x mongo_driver.jsの最後の行によると、MongoInternalsというグローバルオブジェクトが公開されています。defaultRemoteCollectionDriver()を呼び出して、GridStoreに必要な現在のデータベースdbオブジェクトを返すことができます。バージョンAでは、GridStoreはMongoInternalsによっても公開されます。現在の流星で使用されているmongoはv1.4.xです

    次に、ルート内で、var file =new GridStore(...)(API)を呼び出すことにより、新しい書き込みオブジェクトを作成できます。次に、ファイルを開いてストリームを作成します。

    バージョンBも含めました。このバージョンでは、GridStoreはNpm.require('mongodb')を介して新しいmongodbドライブを使用して呼び出されます。このモンゴは、この記事の執筆時点で最新のv2.0.13です。新しいAPIでは、ファイルを開く必要はありません。stream(true)を直接呼び出して、パイピングを開始できます

    長所

    1. オプション2と同じで、arraybufferを使用して送信され、オプション1のbase64文字列と比較してオーバーヘッドが少なくなります
    2. ファイル名のサニタイズについて心配する必要はありません
    3. ファイルシステムからの分離。一時ディレクトリに書き込む必要はありません。データベースはバックアップ、繰り返し、シャードなどが可能です。
    4. 他のパッケージを実装する必要はありません
    5. キャッシュ可能でgzip圧縮可能
    6. 通常のmongoコレクションと比較してはるかに大きなサイズを保存します
    7. パイプを使用してメモリの過負荷を軽減する

    短所

    1. 不安定なMongoGridFS 。バージョンA(mongo 1.x)とB(mongo 2.x)を含めました。バージョンAでは、10 MBを超える大きなファイルをパイプ処理すると、ファイルの破損、パイプの未完成など、多くのエラーが発生しました。この問題は、mongo 2.xを使用するバージョンBで解決されています。うまくいけば、meteorはすぐにmongodb2.xにアップグレードされます
    2. APIの混乱 。バージョンAでは、ストリーミングする前にファイルを開く必要がありますが、バージョンBでは、openを呼び出さずにストリーミングできます。 APIドキュメントもあまり明確ではなく、ストリームはNpm.require('fs')と100%構文交換可能ではありません。 fsではfile.on('finish')を呼び出しますが、GridFSでは書き込みが終了/終了するときにfile.on('end')を呼び出します。
    3. GridFSは書き込みアトミック性を提供しないため、同じファイルへの複数の同時書き込みがある場合、最終結果は大きく異なる可能性があります
    4. 速度 。 MongoGridFSはファイルシステムよりもはるかに低速です。

    ベンチマーク オプション2とオプション3でわかるように、var start =Date.now()を含め、endを書き込むときに、 msで時間をconsole.logoutします。 、以下は結果です。デュアルコア、4 GB RAM、HDD、ubuntu14.04ベース。

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

    FSはGridFSよりもはるかに高速であることがわかります。 200 MBのファイルの場合、GridFSを使用すると約80秒かかりますが、FSでは約1秒しかかかりません。 SSDを試したことがないので、結果が異なる場合があります。ただし、実際には、帯域幅によってファイルがクライアントからサーバーにストリーミングされる速度が決まる場合があり、200MB/秒の転送速度を達成することは一般的ではありません。一方、転送速度は最大2 MB /秒(GridFS)が標準です。

    結論

    これは包括的なものではありませんが、ニーズに最適なオプションを決定できます。

    • DDP は最も単純で、Meteorのコア原則に準拠していますが、データはよりかさばり、転送中に圧縮できず、キャッシュできません。ただし、このオプションは、小さなファイルのみが必要な場合に適しています。
    • ファイルシステムと組み合わせたXHR 「伝統的な」方法です。安定したAPI、高速、「ストリーミング可能」、圧縮可能、キャッシュ可能(ETagなど)。ただし、別のフォルダーに配置する必要があります
    • GridFSと組み合わせたXHR 、rep set、スケーラブル、ファイルシステムディレクトリに触れない、大きなファイル、およびファイルシステムが数を制限している場合は多くのファイル、またキャッシュ可能な圧縮可能の利点を得ることができます。ただし、APIは不安定であり、複数の書き込みでエラーが発生します。これはs..l..o..w ..

    うまくいけば、すぐに、meteor DDPはgzip、キャッシングなどをサポートできるようになり、GridFSはより高速にできるようになります。 ...



    1. Dockerコンテナ内のRedis接続エラー

    2. $graphLookupのObjectIdを文字列に一致させる

    3. 従来のIDを使用したMeteorコレクションの更新

    4. 動的に生成されたフォームの結果をMongoDbに保存するにはどうすればよいですか?