fbpx

GridFSを使ってMongoDBにサイズの大きい画像や動画ファイルを保存する #mongodb #documentdb

MongoDBテクニカルサポートの山森です。

ドキュメントDBは、スキーマレスだとよく言われます。JSON形式(※1)であればどんなデータも柔軟に保存することができます。MongoDBは画像や動画ファイルをドキュメント内にバイナリデータとして保存することが可能です。ドキュメント(RDBで言うところのレコード)のサイズのリミットは16MB(※2)ですが、GridFSを用いることで16MB以上のデータを扱うことができます。

GridFSとはどんなものなのでしょうか。16MBを超えるファイルを、MongoDB内ではどのように取り扱うのでしょうか?実際にデータを操作して確かめてみましょう。

※1 厳密にはBSON(Binary JSON)という形式でデータを取り扱います。詳細は「JSON and BSON」をご覧ください。

※2 MongoDB Limits and Thresholds - BSON Documents

検証環境

今回はMongoDB Server Community Edition 7.0.8を使用します。この記事の時に構築したP1S2のレプリカセットですが、MongoDB Atlasを使用しても構いません。

前準備:16MB以上のファイルを用意する

今回はこちらのサイトから100MBの画像ファイルをお借りしました。大きいファイルを扱えるかどうか確認するための検証なので、画像ファイルでなくても構いません。Linux環境ならddコマンドなどで用意しても良いでしょう。

画像をダウンロードしておきます。

nika@gaia:~$ curl -O "https://sample-img.lb-product.com/wp-content/themes/hitchcock/images/100MB.png"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  100M  100  100M    0     0  10.2M      0  0:00:09  0:00:09 --:--:--  9.9M
nika@gaia:~$ ls -lh 100MB.png
-rw-r--r-- 1 nika nika 100M  5月 30 11:29 100MB.png

画像ファイルをMongoDBに追加する

今回は、MongoDBに画像ファイルを保存するためにmongofilesというコマンドを使います。MongoDB Server Community Editionと一緒にインストールされています。GridFSを使用するのに、MongoDB Server側の設定変更は必要ありません。

mongofilesで、gridtestというデータベースに100MB.pngの画像ファイルを追加します。データベースが存在しない場合は新しく作られるので、事前に用意する必要はありません。また、今回はlocalhostで動作するMongoDB Serverに対して接続しているので、接続文字列は必要ありません。

nika@gaia:~$ mongofiles -v -d gridtest put 100MB.png
2024-05-30T11:31:21.625+0900    using write concern: &{majority false 0}
2024-05-30T11:31:21.627+0900    connected to: mongodb://localhost/
2024-05-30T11:31:21.627+0900    handling mongofiles 'put' command...
2024-05-30T11:31:21.628+0900    adding gridFile: 100MB.png

2024-05-30T11:31:22.310+0900    added gridFile: 100MB.png

無事追加されたようです。MongoDBの中ではどうなっているのか見てみましょう。mongoshでlocalhostのMongoDB Serverに接続します。blacktriplestarはレプリカセットの名前です。gridtestというデータベースができていますね。

その中にfs.chunksとfs.filesという2つのコレクション(RDBで言うところのテーブル)ができています。これがGridFSの実態です。

blacktriplestar [direct: primary] test> use gridtest
switched to db gridtest
blacktriplestar [direct: primary] gridtest> show dbs
admin         80.00 KiB
config       228.00 KiB
gridtest     100.55 MiB
local        109.82 MiB
mobilesuits   40.00 KiB

blacktriplestar [direct: primary] gridtest> show collections
fs.chunks
fs.files

それぞれのコレクションの中身をのぞいてみましょう。fs.filesのドキュメント数は1件だけです。

実際のデータの中身はこうなっていました。

{
  "_id": {
    "$oid": "665808c95c2d806dd999c073"
  },
  "length": {
    "$numberLong": "104857600"
  },
  "chunkSize": 261120,
  "uploadDate": {
    "$date": "2024-05-30T05:04:09.942Z"
  },
  "filename": "100MB.png",
  "metadata": {}
}

こちらはGUIインターフェースのCompass上でドキュメントを見たときの画面です。

lengthは100MB.pngのファイルサイズです。OS側でもサイズを見てみましょう。同じ値になっていますね。

nika@gaia:~$ ls -l 100MB.png
-rw-r--r-- 1 nika nika 104857600  5月 30 11:39 100MB.png

chunkSizeは1つのチャンクあたりのサイズです。MongoDBはこの大きさで16MB以上のファイルを複数のチャンク(1つの大きな塊から切り分けたものという意味)に切り分け、ドキュメントとして保存します。

104857600 Byteを 261120 Byteで割った数、つまり今回の100MB.pngを保存するために、402個のドキュメントが存在しているはずです。

今度はfs.chunksを見てみましょう。Compassではドキュメントの総数を常に表示してくれます。画像の左上を見てみると、「Documents 402」と表示されています。

確かに、402個のドキュメントが存在していますね!

ドキュメントの中身はどうなっているのでしょうか。その中のひとつを見てみましょう。

{
  "_id": {
    "$oid": "665808c95c2d806dd999c074"
  },
  "files_id": {
    "$oid": "665808c95c2d806dd999c073"
  },
  "n": 0,
  "data": {
    "$binary": {
      "base64": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAKAAQMAAAA2A1tgAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABlBMVEX/ZgD///(途中省略)9Xb7b8AAAAAWJLR0QB/wIt3gAAAAd0SU1FB+MDEJTeEdFSGRIdFhnRTJ0SWZmcXAzNEl1Z051bllTRXhmcWlXSGNRZWRSd29WbEV4RGpUTzNFWWFONnZ1UVFYV2Z1VnBCRlFWdFVDcWcyOUk1Vll5WHVtRElPZ3VXV2NvQkRPeG80SXZMc2liMG1ucnVLSG9XS005N1FVRE51aFdzVmwyQ3JPZmpi",
      "subType": "00"
    }
  }
}

画像ファイルがバイナリとして保存されていることが分かります。バイナリ部分は実際はとても長いので、一部を省略させていただきました。

files_id は fs.filesのObjectId(MongoDBがドキュメントに自動的に一意に付与するID)と同じ値です。これで、このドキュメントが100MB.pngのチャンクの一部であることが分かります。

"n":0はチャンクのシーケンス番号です。連番となっており、0から始まります。今回の場合は401まで存在します。

画像ファイルをMongoDBから取り出す

取り出すときもmongofilesを使います。チャンクなどを意識せずに簡単に取り出すことができます。

nika@gaia:~$ mongofiles -v -d gridtest get 100MB.png
2024-05-30T11:39:03.699+0900    using write concern: &{majority false 0}
2024-05-30T11:39:03.701+0900    connected to: mongodb://localhost/
2024-05-30T11:39:03.701+0900    handling mongofiles 'get' command...
2024-05-30T11:39:03.807+0900    finished writing to 100MB.png

nika@gaia:~$ ls -l
合計 102432
-rw-r--r-- 1 nika nika 104857600  5月 30 11:39 100MB.png
-rw-r--r-- 1 nika nika     28794  5月 16 11:13 mdiag.sh

fs.filesでファイル名を管理しているので、間違えると取り出すことができません。ちなみに、ファイル名を間違えた(存在しないファイル名を指定した)場合はこうなります。

nika@gaia:~$ mongofiles -v -d gridtest get 100megabyte.png
2024-05-30T15:53:11.211+0900    using write concern: &{majority false 0}
2024-05-30T15:53:11.214+0900    connected to: mongodb://localhost/
2024-05-30T15:53:11.214+0900    handling mongofiles 'get' command...
2024-05-30T15:53:11.214+0900    Failed: requested files not found: [100megabyte.png]

まとめ

MongoDBではGridFSを用いて、16MB以上のデータをチャンクと言う単位に分割して保存していることが分かりました。かなり初期から実装されている機能のようで、GridFSに関するブログ記事はたくさんありますが、最新の7.0系でも変わらずに使えることが分かりました。

アイキャッチ画像についての蛇足…チャンクは「1つの大きな塊を切り分けたもの」という意味なので、ピザの画像を採用しました。おいしそうですね。

Author

MongoDB日本語サポート担当。ITインフラや運用・監視・保守が好きです。
無駄のない構成やアーキテクチャを見てうっとりしています。

k-yamamoriの記事一覧

新規CTA