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つの大きな塊を切り分けたもの」という意味なので、ピザの画像を採用しました。おいしそうですね。