12月 09

image filterイメージ先週金曜日(12/2)にクックパッドインフラ勉強会に参加しまして、そこで同社の成田さんから「今日からできるApacheモジュール開発と運用」という発表がありました。
リアルタイム画像変換モジュールの「TOFU」を開発するに至った経緯と、Apacheモジュール開発についてのお話でした。

TOFUは、S3に置かれたマスターとなる画像ファイルを取得し、与えられたパラメータでリアルタイム(オンザフライ)にリサイズ・トリミングを行うモジュール(mod_tofu)です。

実際は、モジュールによる画像取得・変換をベースに、キャッシュや配信までも含めた一連の画像配信システムと言えそうです。

この仕組みをNginxを使って実装できないかと考えて、リアルタイム変換の仕組みをNginxだけで実現する方法を実験してみました。


準備するもの

HttpImageFilterModule

このモジュールはパッケージに同梱されているオプションモジュールの一つで、JPEG、GIF、PNGを変換するフィルターを提供します。
有効にするには、ビルド時にパラメータを指定する必要があります。

./configure --with-http_image_filter_module

ビルドし直したnginxの入れ替えを行う場合は、こちらを参考にしてください。

また、ビルドにはlibgdが必要なのでGDライブラリを別途インストールしておく必要があります

最も簡単な例

/imagesディレクトリにあるjpgファイルを全て150*150のサムネイルに変換したい場合は、以下の設定を書くだけです。

location ~ /images/.*\.jpg$ {
   image_filter  crop  150  150;
}

このままだと元サイズのファイルにアクセスできなかったり、エラー制御がしにくいのですが、このモジュールがいかに簡単に使えるかがわかると思います。

ローカルファイルで実験

前述の最も簡単な例を発展させて、外部からのパラメータに従って、まずはローカルディスクにある画像に対してリアルタイム変換をしてみます。

対象とするファイル

  • momiji.jpg
  • JPEGファイル
  • 1024×683 ピクセル
  • 274,661 バイト

外部からのパラメータ

  • width(幅)
  • height(高さ)
  • type(リサイズの方法)
    • resize(縦横比を変えずにサイズ変換)
    • crop(サイズ変換後に指定されたサイズに切り取る)
  • quality(JPEGの画質≒圧縮率)

要件

/imagesディレクトリにあるJPEGファイル(*.jpg)にパラメータを付けてアクセスをした時、パラメータに従って変換された画像を返す。

結果

以下のように、パラメータを変えるだけで欲しい画像サイズに変換されます。


/images/momiji.jpg?width=150&height=150&type=resize


/images/momiji.jpg?width=200&height=200&type=crop


/images/momiji.jpg?width=400&height=100&type=crop&quality=5

こちらのリンク先でパラメータをいろいろ変更してテストができます。お試し下さい。
http://nginx.labs.cloudrop.jp/images/momiji.jpg?width=150&height=150

設定方法

処理の流れ(ローカル)

  1. /images でパラメータの有無をチェックします。なければ通常の画像を返すようにしています。(何もしない)
  2. パラメータがあれば /image_filter へリライトされ、パラメータから変数に代入し、$arg_typeを元に /resize か /corp へリライトします。
  3. /reize /crop でそれぞれ処理をし、変換された画像が返されます。

※ ImageFilterが処理できない場合は415エラーが発生するので、それを1*1pxの透過GIFに置き換えています。

設定ファイル

location ~ /images/.*\.jpg$ {
    if ($query_string ~ .*=.*) {
      rewrite ^/images/(.*\.jpg)$ /image_filter/$1 last;
    }
}

location ~ ^/image_filter/(.*\.jpg)$ {
    internal;

    set $file $1;
    set $width 150;
    set $height 150;
    set $quality 75;

    if ($arg_width ~ (\d*)) {
        set $width $1;
    }
    if ($arg_height ~ (\d*)) {
        set $height $1;
    }
    if ($arg_quality ~ (100|[1-9][0-9]|[1-9])) {
        set $quality $1;
    }

    if ($arg_type = "resize") {
        rewrite ^ /resize last;
    }
  
    rewrite ^ /crop last;
}

location /resize {
    internal;
    rewrite ^ /images/$file break;
    image_filter  resize  $width $height;
    image_filter_jpeg_quality $quality;
    error_page 415 = @empty;
}

location /crop {
    internal;
    rewrite ^ /images/$file break;
    image_filter  crop  $width $height;
    image_filter_jpeg_quality $quality;
    error_page 415 = @empty;
}

location @empty {
    empty_gif;
}

パフォーマンス

ローカル&no cache(req/s)グラフ

ab(ApacheBench)で100リクエストを並列で一度に送り、1秒あたりのリクエスト処理数を5回測定した平均値。クライアントとサーバーは別でインターネットを経由して測定。

ローカルでの処理なのでネットワークのボトルネックはありません。一方、キャッシュを利用していないので、リクエストごとに変換処理が動くことになります。
無変換を基準に考えると、サイズが大きめの画像へのリサイズは時間がかかりますが、小さくなるに従って無変換よりもパフォーマンスが上がります。
変換に処理時間がかかるものの、Webで利用するサムネイル程度のサイズにすれば十分実用的な速度だと思います。

S3経由で実験

今度はTOFUの仕様に近づくべく、S3(リモートサーバー)にあるファイルを取得するようにします。
実験のサーバーは「さくらのVPS512」でS3は「USスタンダードリージョン」です。ネットワークのレイテンシは150ms程です。

対象とするファイル

  • sanzaru.jpg
  • US Standard リージョン
  • JPEGファイル
  • 1024×683 ピクセル
  • 194,728 バイト

外部からのパラメータ(ローカルと同じ)

  • width(幅)
  • height(高さ)
  • type(リサイズの方法)
    • resize(縦横比を変えずにサイズ変換)
    • crop(サイズ変換後に指定されたサイズに切り取る)
  • quality(JPEGの画質≒圧縮率)

要件

JPEGファイル(*.jpg)にパラメータを付けてアクセスをした時、S3上にある同名ファイルを取得し、パラメータに従って変換された画像を返す。
変換した画像はキャシュし、2回目以降はキャッシュを利用する。

結果


/s3images/sanzaru.jpg?width=150&height=150&type=resize


/s3images/sanzaru.jpg?width=200&height=200&type=crop


/s3images/sanzaru.jpg?width=400&height=100&type=crop&quality=5

こちらのリンク先でパラメータをいろいろ変更してテストができます。お試し下さい。
http://nginx.labs.cloudrop.jp/s3images/sanzaru.jpg?width=200&height=200&type=crop

設定方法

処理の流れ(S3)

  1. 80番ポートで待ち受ける/s3images で8080番ポートで動くバックエンドの/s3images へリクエストをそのまま渡します。
  2. 8080番ポートの/s3imagesでパラメータから変数に代入し、$arg_typeを元に/s3_resize か/s3_corp へ、パラメータがなければ/s3_original へリライトします。
  3. /s3_resize /s3_crop /s3_originalでそれぞれS3からファイルを取得・変換処理をし、画像を80番ポートのフロントエンドへ返します。
  4. 80番ポートのフロントエンドはバックエンドから返ってきたレスポンスをキャッシュし、画像を返します。以降、すでにキャッシュ済みリクエストだった場合は、バックエンドへ送らず、キャッシュを返します。

設定ファイル

server {
    listen 80;
    server_name localhost;
    root /var/www/html;

    location /s3images {
        proxy_pass http://localhost:8080;
        proxy_cache s3cache;
        proxy_cache_key $scheme$host$uri$arg_width$arg_height$arg_type$arg_quality;
        proxy_cache_valid  200 60m;
    }
}

server {
    listen 8080;
    server_name localhost;
    root /var/www/html;

    access_log logs/proxy_access.log;
    access_log logs/error_access.log;

    resolver 8.8.8.8;

    location ~ /s3images/(.*\.jpg)$ {

        set $s3host MY_S3_HOST;
        set $file $1;
        set $width 150;
        set $height 150;
        set $quality 75;

        if ($query_string !~ .*=.*) {
          rewrite ^ /s3_original last;
        }

        if ($arg_width ~ (\d*)) {
            set $width $1;
        }
        if ($arg_height ~ (\d*)) {
            set $height $1;
        }
        if ($arg_quality ~ (100|[1-9][0-9]|[1-9])) {
            set $quality $1;
        }

        if ($arg_type = "resize") {
            rewrite ^ /s3_resize last;
        }

        rewrite ^ /s3_crop last;
    }

    location /s3_original {
        internal;
        proxy_pass http://$s3host/$file;
    }

    location /s3_resize {
        internal;
        proxy_pass http://$s3host/$file;
        image_filter  resize  $width  $height;
        image_filter_jpeg_quality  $quality;
        error_page 415 = @empty;
    }

    location /s3_crop {
        internal;
        proxy_pass http://$s3host/$file;
        image_filter  crop  $width  $height;
        image_filter_jpeg_quality  $quality;
        error_page 415 = @empty;
    }

    location @empty {
        empty_gif;
    }
}

8080ポート側にresolverの設定が入っています。NginxはOS標準の名前解決方法を利用してくれないため、ネームサーバーをresolverに設定する必要があります。
ここではGoogle Public DNSが設定されていますが、利用するサーバーの最寄り(通常同じDC内)のネームサーバーを指定します。

パフォーマンス

S3経由&cache(req/s)グラフ

ab(ApacheBench)で100リクエストを並列で一度に送り、1秒あたりのリクエスト処理数を5回測定した平均値。クライアントとサーバーは別でインターネットを経由して測定。

S3からファイルを取得する処理を含めると、ネットワークのボトルネックがあり、1リクエスト2秒程度かかります。それを含めてしまうと、ネットワークに引きずられて正確な値がでないので、キャッシュに対して測定しています。

キャッシュからの応答になるので、純粋にファイルサイズの大きさに比例していきます。マスターの画像が変更されないことがわかっている場合、もしくは、変更があった場合にハッシュ値などを使ってキャッシュを書き換えるなどすると、実用的な速度で利用できることがわかります。

まとめ

細かい設定(例えば、左上から右へ10px下へ20pxを起点に切り取るなど)はできませんし、pngからjpgなどのフォーマット変換もできないので、多くの機能を望む場合は、他のプログラムを利用するか、Nginxモジュールを書く事になると思います。

とは言っても、モジュールをインストールして、設定を書くだけで利用できるので、プロトタイプ開発の用途には使えますし、リモートサーバーのファイル自体をキャッシュしたり、Proxy Cacheを適切に設定したり、多段にキャッシュを用意したりすれば、用途に限りがありますが、プロダクションでも使えると思います。

Nginxモジュール開発の参考

Tagged with:
11月 07

本日の第4回さくらの夕べで明らかになった情報のメモを整理します。
ほとんどの情報は明日のプレスリリースで公開される情報です。

リリース日

2011年11月15日 15:00に石狩データセンターがオープンし、そのタイミングでリリース予定とのこと。申込みページはこちら[さくらのクラウド] になる予定。

さくらのクラウド プラン別価格表

プラン名 CPU メモリ ディスク 月額料金 日割料金
プラン1 仮想1コア 2GB 20GB ¥2,500 ¥126
プラン2 仮想1コア 3GB ¥3,750 ¥189
プラン3 仮想2コア 4GB ¥5,200 ¥260
プラン4 仮想2コア 6GB ¥7,800 ¥390
プラン5 仮想3コア 8GB ¥10,400 ¥520
プラン6 仮想3コア 12GB ¥15,600 ¥780
プラン7 仮想4コア 16GB ¥20,800 ¥1,040
プラン8 仮想4コア 24GB ¥31,200 ¥1,560
プラン9 仮想6コア 32GB ¥41,500 ¥2,080
プラン10 仮想8コア 48GB ¥52,800 ¥2,640
プラン11 仮想10コア 64GB ¥64,000 ¥3,200
プラン12 仮想12コア 96GB ¥81,600 ¥4,080
プラン13 仮想12コア 128GB ¥96,000 ¥4,800

上記のプランをベースにローカルネットワークで通信をする場合は仮想スイッチ(¥5,250)、ストレージを増やしたい場合は容量に応じて追加料金でディスクを増やす料金体系。転送量は込み込みなので、転送量の多いサービスはAWSなどに比べてさらに料金が圧縮できる。
ベースの料金は1日〜20日までの利用は日割料金、それ以上の利用は月額料金で課金される。

ロケーション

さくらのクラウドのロケーションは新設の石狩データセンターになるので、在庫は潤沢(1,000台でも2,000台でも)。
東京や大阪などのリージョンも要望が多ければ考える予定。
クラウドとVPSや専用サーバーを同一の石狩データセンターで借りれば、レイテンシーが1ms以下で通信できるミクスチャー環境が構築できる。

その他気になったこと

SSD

ストレージとしてSSDやioDriveも選択できるようにする予定とのこと。手軽に利用できるようになれば、I/Oで頭を悩ませなくて済むようになりそう。

オートスケール

オートスケールは実装する予定はないとのこと。
クラウドをコントロールするAPIが用意されるので、サードパーティーが参入できる部分。

Tagged with:
5月 19

mongoDB
「第3回 MongoDB 勉強会 in Tokyo」 : ATND
先週土曜日(2011年5月14日)にフューチャーアーキテクトさんのセミナールームにて行われたMongoDBの勉強会に行ってきました。


MongoDBの仕様・機能などの体系的な話からチューニングの話、開発に利用する話、プロダクションとして運用している話と、バランスよくまとまっていてとても勉強になりました。
RDBとの違いや特有の挙動をきちんと把握して利用すれば、十分プロダクションで利用出来るというビジョンが見えました。

詳細は各発表者の方のスライドを見るとわかると思いますが、当日会場でメモしたものでダイジェスト的にまとめました。
MongoDB勉強会は毎月開催(予定)しているそうなので、興味を持たれた方は是非参加して見てください。
MongoDBコミュニティー:MongoDB JP | Google グループ

【発表1】MongoDB全機能解説1

MongoDB特有の仕様や機能など、プロダクションで使う場合には特に注意が必要な部分を中心に説明されていました。

ロック

Read/Writeの各オペレーション時にDBに対してロックがかかる。
MongoDBをフロントエンドで使う場合、DBがロックして応答が返って来なくなるなどの問題が起きる可能性があるので、時間がかかる処理には注意が必要とのこと。

実際、単体のRead/Writeは十分高速でロックの待ち時間で問題が起こることはないが、複数のWrite処理をアトミックで行なったり({atomic: ture})、MapReduceで重い処理をさせるケース(ロックはかからないが処理が重いので)が問題になる。

mongos

mongosサーバーは協調する複数のmongodサーバーがある場合、それを一つのクラスターとして扱うためのルーティングサーバーで、それ自体ではストレージする機能はない。
アプリケーションは複数あるmongodサーバーの中でどれががマスターでどれがスレーブなのかを知らなくてもmongosサーバに問い合わせればいい。

database

32bit環境ではDBサイズが2.5GBまで扱えないので、要64bit環境とのこと。
参考:32-bit limitations

BSON

データはBSONという形式で保存されている。データ型が複数あり、どのデータ型で保存されるのかはドライバーに依存する。
→ 例えば、PHPの場合はMongoInt32やMongoInt64などのデータ型用のクラスが用意されてる。
オブジェクト内の順序もみているので、順番が入れ替わるだけで別のオブジェクトととされてしまう。
→ { ‘key1’: ‘value1’, ‘key2’: ‘value2’ } ≠ { ‘key2’: ‘value2’, ‘key1’: ‘value1’ }

Capped Collection

サイズを指定してCollectionを作成するとCapped Collectionになる。
サイズが固定なのでサイズを越えた場合は古いものから自動的に削除される。
とにかく書き込みが速い。でも削除できない。
_idにすらindexがつかない。indexを張ることもできるが遅くなる(それでも速い方)。
shardingすることはできない。
用途 → ロギング(ディスクサイズを圧迫しない)
   → キャッシング
   → MapReduceの一時的なCollectinとして

Insert周り

Bulk Insert

複数のdocumentを配列に入れて一括でインサートする「Bulk Insert」が速い。
ただ、どのくらいのサイズまでが最適なのかはスペック依存で、経験上16MBを超えるとエラーが出るそう。

fire and forget

インサートはデフォルトではデータベースからの応答を待たない
応答を確認するには、各種ドライバーでインサート時に応答を待つ処理を明示的にオプションとして指定する必要がある。
→ PHPではarray(‘safe’=>true)

トランザクション

RDBのように複数のオペレーションをトランザクション処理することは出来ないので、連続インサートする場合、途中でエラーになっても一連のインサートをロールバックすることは出来ない

getLastErrorメソッド
db.getLastError();

でオペレーションの最後に起こったエラーがわかるので、このメソッドは重要。

fsyncコマンド

メモリ上のデータを強制的にストレージにフラッシュするコマンド。

db.runCommand({fsync:1});

fsyncは自動的に60秒に1回走るようになっていて、mongod起動時に、–syncdelayオプションで変更可能だが10genとしては非推奨とのこと。
つまり、最長60秒間に行われたデータの更新は喪失する可能性がある。

Journaling(v1.7.5)

オペレーション前にJournalingファイルに保存し、オペレーションに失敗した場合、次回実行される。
100ms毎にジャーナリングされる。つまり、100ms間のオペレーションは喪失する可能性がある仕様。

Query

printjson

シェル(mongo)でデータを確認するときには

db.collection.find().forEach(printjson);

とするとJSON形式にフォーマットされ見やすくなる。

入れ子

入れ子になったオブジェクトをサブオブジェクト形式でfindする場合は、完全に一致したオブジェクトでないと取得できない。
サブオブジェクト内のあるキーだけを検索対象としたい場合は、dot notation形式({author.name: ‘Jane’})で問い合わせる。

var doc = { title: 'タイトル', body: '本文', author: { first_name: '太郎', family_name: '山田'} };
db.collection.insert(doc);
db.collection.find({author.first_name: '太郎'}); //◯ヒットする
db.collection.find({author: { first_name: '太郎'} }); //×ヒットしない

Update

Update

デフォルトでは最初にヒットしたオブジェクトのみを更新する。
→ ヒットした全てを更新対象としたい場合は{ ‘multi’: true } オプションを付ける。
→ 複数の更新をアトミックに行うには { ‘$atomic’: true } オプションを付ける。

Save

同じ_idのオブジェクトがあれば自動的にドキュメント全体を更新する。

modifier オペレーション

アップデートのオプションとして$inc/$set/$push/$popなどのオペレーションがある。
modifierオペレーションを指定しないでアップデートを行うとオブジェクト(ドキュメント)をまるごと上書きするのでとても怖い。

in-place update

データサイズが変わらない更新の場合は高速。(例えば、{ ‘$inc’; { key: 1 } } など。)
また、データの更新を高速に行うため、インサートされた際に実データサイズよりも大きめにストレージを使って保存している。
→ 多少のサイズの増加も高速に更新出来る。逆に言うと無駄にストレージを使う。

Snapshot

MongoDBはデータサイズが大きく増えた場合、データベースファイルの新しい領域にデータを格納し直す。この瞬間にfind()すると同じドキュメントが2個取れたりする可能性がある
find()で取り出した複数のドキュメントをupdate()するようなケースでは問題になるので、重複を排除して結果を返すsnapshot()を使う。

db.collection.find({name:'Jane'}).snapshot();

Indexについて

B-Tree indexes

インデックスを生成するオペレーションensureIndex()はデフォルトではDBをブロックする。
{ background: true } オプションを付けるとロックしないので、基本的にこれを必ず付けること。

db.collection.ensureIndex({name: 1}, {background:true});

参考:Indexing as a Background Operation

Sparse Index(v 1.7.4)

インデックスの設定されたフィールドを持っていないドキュメントは無視される。
→ コレクション全体的の中でSparse Indexに設定されたフィールドに値を持っているドキュメントがが少ない場合にパフォーマンスが出るインデックス。

Unique Index

インデックスを張ったキーが存在しないドキュメントを登録するとnullが挿入され、nullがユニークの対象となる。
→ インデックスを張ったキーが存在しないドキュメントをもう一つ登録すると重複エラーになる。
ただし、sharding環境では重複する可能性がある

Replication

  • Master-Slave:一般的なM/S構成
  • Replica Sets:自動フェイルオーバ機能などを提供
Master-Slave

Master->Slaveは非同期でコピーされる。
mongodをそれぞれ、–master、 –slaveを付けて起動するだけ。
→ slaveには直接書き込みできない。(mongos経由)

同期は各データベースがそれぞれローカルに自動的に作成するoplog(operation log)コレクションを介して行われる。

  1. スレーブ(セカンダリ)はマスターのoplogを監視している。 
  2. マスター(プライマリ)は更新系のオペレーションをoplogに書き込み、自分のデータベースに実行する。
  3. スレーブはマスターとの差分のoplogを自分のoplogへコピーし、そのオペレーションを自分のデータベースに実行する。

MySQLのバイナリログと同じようなイメージ。

Replica Sets

3台〜12台で構成でき、自動フェイルオーバー機能/自動リカバリ機能を持つ。
ノードタイプには、Standard(データあり。Primaryになれる。)、Passive(データあり。Primaryになれない。)、Arbiter(データなし。投票権はある。)がある。
フェイルオーバーは、起動時に設定するprimary値の高いもの > 最も新鮮なデータを持っているもの > 投票数の多いもの の順で順番付けされ、決定したセカンダリーがプライマリになる。
ダウンしているサーバーが多かったり、最終更新データが古すぎたりするとどのセカンダリーもプライマリにならないことがある。→ 手動で対応。

【発表2】ソーシャルアプリのプロトタイプ制作にMongoDBを活用

Sleepy.Mongoose

アプリケーションとデータベースの通信はSleepy.Mongoose(pythonで実装されたRestインターフェース)を利用して、curlで実装している。
→ Sleepy.Mongooseにはcount()が実装されていないのでforkしたものをGitHubに置いてあるとのこと。
bibrost / sleepy.mongoose

Cray Model

自動車開発のプロセスで粘土細工(Cray Model)で何度も何度も型を作ってから金型を作るように、プロトタイプの開発がしたい。
RDBのようにスキーマを定義して、チーム内で変更を共有して、不整合が起きないようにCREATE TABLEしたりALTER TABLEしたりするのは面倒。
MongoDBはアプリケーション上でいくらでもデータ構造を変更できるので、ソースさえ共有すればよく、途中でドキュメント構造が変わっても、手を止めることなくcollection名を変えるだけでいい。→まさに粘土細工のように失敗したら壊してまた作ればいい。

なぜ最終的にRDBにするのか

  • MongoDBを保守できるエンジニアがいない。
  • MongoDBをクリティカルなプロダクションで使うことが難しい(トランザクションがない)。

【発表3】MongoDBチューニング 〜スロークエリ撲滅までの道筋〜

オプティマイザ

RDBで多く採用されている一般的なオプティマイザは統計情報を元にしたコストベースで、本番環境と検証環境で統計情報が異なっていると実行計画が違ってきたり、悪い実行計画も行われる。
MongoDBのオプティマイザはコストベースではなく、新しいクエリー実行がある度に計算する仕様。(オーバーヘッド・CPUコストが高いが、最適な計画が行われる)

  • 新しいクエリー実行時に複数の実行計画を並行に実施して一番速いのを採用・学習し、他のものは途中で止める。
  • 100回データ更新が行われ時やindexが変更された時、最初の評価時からスキャンするドキュメントが大きく増えた場合(nscannedの値が10倍になったら)再評価を実施する。

explainメッソド

explain()で実行計画を確認できる。

db.collection.find(query).explain();

勘所としてはBasicCursorは全件スキャンなので×、millisが長いと処理時間が遅いので×、nscannedが多いとスキャン対象のドキュメント数が多いので×、など。

スロークエリの特定

  • slowmsオプションをつけて起動する。
    → ログファイルに保存される。
  • db.setProfilingLevel()を実行する。
    → db.system.profileコレクションに保存されていく。(デフォルト100ms)
  • db.currentOp()を実行する
    → 現在実行中のクエリが見られる。
  • 複合インデックス

    順序が大事。
    db.collection.find({x: 10}).sort({ y: 1});でも { x: 1, y: 1 }のインデックスが使われる。

    hintメソッド

    hintメソッドを使うと特定のインデックスを使わせることができる。
    インデックス設定されていれば、
    db.collection.find().hint({uid:1}); より db.collection.find().hint({uid:1, path:1}); の方がより絞られて高速になる。

    mongostat

    mongoやmongodなどと一緒にインストールされるmongostatコマンド。
    ロックされている割合やインデックスが利用されなかった割合などの統計情報が確認できる。

    killメソッド

    db.killOp(opId)で実行中のクエリをkillできる。

    【発表4】MongoDBを使用したモバイルゲーム開発

    東京ガールズスナップの構成

    Java(appサーバー2台)+ MongoDB(Replica Setで3台)で運用している。
    MongoDBに対応したSpring Data Documentを利用して開発した。

    MongoDBで良かったこと

    階層型にデータが持てるのでkey-value的に使えた。
    → ドキュメント内に配列を持つことができるので、RDBのようなリレーションや連結などしなくても直接$pushや$popで複数のデータを出し入れできる。

    バイナリデータが保存しやすい
    当初の想定していた利用法ではなかったが画像データのキャッシュストレージとして利用できた。
    → 4MBまでだったらバイナリデータをそのまま格納できる。

    MongoDBが問題になったこと

    • オペレーションミスが大変なことになる(update時に$setで指定しないと全部上書きする)
    • アプリ経由ではなくコンソールでデータを更新したらデータ型が変わってしまった(intがdoubleに)
    • ドキュメント設計の最適化に時間がかかった(ベストプラクティスがわからない)
    Tagged with:
    preload preload preload