rabbit_swiftというオブジェクトストレージのクライアント Rubyモジュールを作成しているため、その際判明したオブジェクトストレージの挙動についての記事になります。
#OpenStackのファイルサイズ5GB制限
ConoHaオブジェクトストレージはAWSのS3のようにファイルをどかどかアップロードできる便利なストレージサービスなのですが、OpenStackベースのサービスのためアップロードできる1ファイルサイズは5GB(正確には5,368,709,120バイト)までという制限があります。
5GBを超えるための2つの方法
もちろんこれでは5GB以上のファイルをストレージサービスに格納できなくなるため、使い物になりません。これを回避するためにLargeObjectという概念で5GB以上のファイルを扱えるようになっています。
DLOとSLOという2つの方法がありますが、基本的には5GB以上のファイルを分割し5GB以下にしてからバラバラにアップロードするという単純なアプローチになります。
Dynamic Large Object (DLO)
DLOはオブジェクトストレージに最初に実装されていたモードで、SLOに比べて信頼性が落ちるため今回は取り扱いません。
Static Large Object (SLO)
SLOは分割したファイルの情報をマニフェストファイルというJSONに格納し、分割したファイルと共にアップロードします。
ダウンロードする時はマニフェストファイルのパスをダウンロードすれば自動的に結合されたファイルになります。
Static Large Object 概念図
SLOの概念図を7GBのファイル(test.zip)を/testコンテナにアップロードするケースを考えて記載します
SLO処理フロー
- 7GB のファイル test.zipを5GB以下に分割する、この時分割したファイル名は昇順で結合できるようなファイル名にしておく。 (test.zip_001, test.zip_002)
- test.zip_001 をアップロードする
- test.zip_002 をアップロードする
- test.zip_001 の MD5SUMを計算する
- test.zip_002 の MD5SUMを計算する
- (★1) 算出したMD5SUMをetagとしてmanifest.jsonを作成する
- (★2) 作成したmanifest.jsonを/testコンテナにtest.zipとしてアップロードする
(★1) manifest.json
manifest.jsonの例は以下
[
{
"path": "https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0001",
"etag": "ab30f25f120fead24c4eaea63f5fe096",
"size_bytes": 104857600
},
{
"path": "https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0002",
"etag": "81160d082d081cb92fae1047f37f274b",
"size_bytes": 77011881
}
]
- etagに分割したファイルのMD5sumを格納
- sizeに分割したファイルサイズをそれぞれ格納
(★2) manifest.jsonをアップロードする際の必須HTTPヘッダー値
ヘッダー名 | 値 | 備考 |
---|---|---|
X-Auth-Token | token値 | オブジェクトストレージとの通信では常に必要 |
X-Static-Large-Object | true | |
X-Object-Manifest | test/test.zip_ | /格納先のコンテナ/オリジナルのファイル名 + _ をつける。ここをつけ間違うとエラーになる、結構重要。 |
Content-Type | application/json | たぶん必須 |
(*2) manifest.jsonをアップロードする際のHTTP URLパラメーター
対象のURLにmultipart-manifest=putのクエリーをつける必要があります。
例
https://objectstore-r1nd1001.cnode.jp/v1/hoge/test/test.zip?multipart-manifest=put
・・・・・・・・・・・・・・・・
・・・・・・・・・・・・・・・・
・・と結構大変なフローを組まないと行けません。
アップロードする時は面倒ですがダウンロードする時はマニフェストファイルのパスをダウンロードすると自動的にOpenStackオブジェクトストレージがファイルを結合して返してくれます。
slo_client.rb でアップロードする場合
rabbit_swiftをgit cloneしbundle installするとbinフォルダのslo_client.rbが使えます。
- -s で送るファイルを指定
- -d で送り先のコンテナを指定
- -c でオブジェクトストレージ接続情報が記載されているjsonを指定
- -l で分割するファイルサイズを指定 バイト指定か(KB/MB/GB)単位
bundle exec ruby -I./lib bin/slo_client.rb -s ~/Downloads/test.zip -d /test -c ../chino/conf/conf.json -l 100MB
/Users/Siori/Downloads/test.zip_0001
/Users/Siori/Downloads/test.zip_0002
[{"path":"https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0001","etag":"ab30f25f120fead24c4eaea63f5fe096","size_bytes":104857600},{"path":"https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0002","etag":"81160d082d081cb92fae1047f37f274b","size_bytes":77011881}]
upload_url -> https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0001
/Users/Siori/Downloads/test.zip_0001
http_status -> 201
upload OK
upload_url -> https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0002
/Users/Siori/Downloads/test.zip_0002
http_status -> 201
upload OK
dest_path->https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test
"https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test"
"test.zip"
"/test/test.zip"
"test/test.zip"
#<URI::HTTPS:0x007feadb823c50 URL:https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip?multipart-manifest=put>
201
実際にConoHaオブジェクトストレージにSLOをアップロードしたファイルの図
ConoHaコントロールパネル
実際に5GB以上のファイルがアップロードされているのがわかる。
CyberDuck
※CyberDuckはSLOに対応していないのか、マニフェストファイルのサイズがJSON自体のサイズになっている点に注意。
Static Large Objectで分割したファイルをダウンロードする
SLOで分割したファイルをダウンロードする際は別段特別なアプローチは必要じゃありません。
ただ単にマニフェストファイルのパスをダウンロードすればオブジェクトストレージがファイルを結合して返してくれます。
rabbit_swiftのbin/get_object.rbの場合は以下
- -t で対象のファイルパスを指定
- -d で保存ディレクトリを指定
- -c でオブジェクトストレージ接続情報のJSONを指定
$ bundle exec ruby -I./lib bin/get_object.rb -t /test/test.zip -d ./private -c ../chino/conf/conf.json
X-Object-Meta-Original-File-Md5sum = efe1sfeda2fdcbkije17ea1e8ea7366
Content-Length = 181869341
Etag = "4c2b17bf693a16448c44028980fb30b0"
Accept-Ranges = bytes
Last-Modified = Sun, 22 Feb 2015 16:40:38 GMT
X-Object-Manifest = test/test.zip_
X-Timestamp = 1424623237.62124
X-Static-Large-Object = true
Content-Type = application/json
X-Trans-Id = txb07ccbdc4a1840498e137-0054ea1b2c
Date = Sun, 22 Feb 2015 18:08:44 GMT
./private/test.zip
181869341 --> original_file_size
181869341 --> save_file_size
OK! file size
efe1sfeda2fdcbkije17ea1e8ea7366 --> original_file_md5
efe1sfeda2fdcbkije17ea1e8ea7366 --> save_file_md5
OK! MD5 checksum
ConoHaでSLOを使う時のいくつかの問題
ConoHaコントロールパネルからファイル落とせない!
SLOに限った問題ではないですがある一定のファイルサイズになるとConoHaのコントロールパネルからファイルをダウンロードできないので、CyberDuckやswiftコマンドや自作のソフトを使わざるを得なくなります。
アヒルが対応してないっぽい!
ConoHaでオブジェクトストレージを使うときのGUIソフトの定番はCyberduckなのですが、SLOでアップロードしたファイルをダウンロードするとタイムアウトエラーが発生しダウンロードが完遂できません。
コンテナのファイルリストの表示がそもそもSLOに対応してないような表示なので現状は期待できない感じです。
ファイルダウンロード後のMD5チェックができない!
ファイル分割していない通常のオブジェクトの場合、HTTPヘッダーのetagにMD5値が格納されているため、オブジェクトストレージからファイルをダウンロードした際はその値とダウンロードしたファイルのMD5チェックサムを比較すればいいのですが、SLOの場合は結合したファイルのMD5SUM値をどうも保持していないようです。マニフェストファイルのEtagはOpenStackの公式サイトやConoHaのRESTマニュアルには**「MD5ハッシュ値とETagを連結した値が表示されます。」**と書かれていますが返ってくる値はダブルクウォートがついたMD5と同じ長さの謎の値のためこの値が何を意味しているのかは不明です。
公式のpython-swiftクライアントもこの値でのチェックは諦めており、SLOの場合はMD5チェックをSKIPするという挙動に見えます。(その変わりファイルサイズが一致するかだけ確認している)
https://github.com/openstack/python-swiftclient/blob/06c73c6020e5af873e3ce245a27035da3448de7b/swiftclient/service.py#L330
Static Large Objectのダウンロード後のMD5チェックサム一致を確認する方法
・・とは言え分割したファイルを結合した際にファイルサイズだけで一致性を確認するのもナンセンスなため、私が作成したrabbit_swiftモジュールではマニフェストファイルのメタデータにオリジナルファイルののMD5SUMを埋め込んで、ダウンロード後にそれを確認するというアプローチにしています。
(ソフトはchinoコマンド)
$ bundle exec ruby chino.rb -g /test/test.zip
https://objectstore-r1nd1001.cnode.jp/v1/hogehoge/test/test.zip
X-Object-Meta-Original-File-Md5sum: ff71367da2fdcv4c59e1ffa1e8ea7399
// ↑ このメタデータをアップロードする時のHTTPヘッダーに埋め込んでおく
Etag: "4c2b17bf693a16448c44028980fb30b0"
// ↑ これが何の値かはマジで不明 サーバーのコードを読みとくしか無いだろう。
X-Object-Manifest: test/test.zip_
X-Static-Large-Object: true
Content-Type: application/json