JSONのようなテキストファイルをBLOBストレージに保存したい。単純な処理に見えるが、実装する上で決めておきたいことが少なくとも2つある。 ファイルをどのような形式で保存するかと、オブジェクトのキーをどのように設計するかだ。
ファイルの実態はBLOBストレージに置き、そのメタデータはDatabaseに管理する構成を前提に置く。 また、一度保存したファイルは不変なものと扱い、内容が異なる場合は別のオブジェクトとして扱いたいものとする。
まずファイルの保存形式について考えてみる。ストレージ効率を高めるために、圧縮することができるだろう。 例えばgzipを使うことができる。gzipの圧縮率は70-90%程度になる。繰り返しのキーなどが多いか少ないかなどが影響する。 圧縮形式には、2015年にMetaが公開したzstd (Zstandard) というツールもある。gzipと同程度の圧縮率で、展開・圧縮速度がより高速 になるみたいだ。すでにDatabaseやファイルシステムなどで広く利用されている。 今回のシステムのようにJSONをBLOBから読み書きするケースだと、zstdも検討する余地は大いにある。
次にオブジェクトのキー設計について考えてみる。キーには、ファイルを管理するDBレコードの識別子を含めておくと運用しやすい。
files/{record_id}/state.json.gzip
この形式なら、キーをみるだけで対応するDBレコードを特定できる。さらに、レコードの識別子をprefixとして関連するオブジェクトを 列挙することも可能だ。オブジェクトを削除するときも、DB上の参照と突き合わせることで、参照されてないオブジェクトを見つけやすくなる。
ファイル名は今のままで良いだろうか。固定されたファイル名だと、同じキーに内容の異なるオブジェクトを保存できてしまう。 オブジェクトの内容が保存時と変わってないことを確認するための手段の一つとして、ハッシュ値を利用できる。保存時に内容からハッシュ値を計算し、DBのメタデータ として記録しておく。読み取り時にもう一度ハッシュ値を計算して比較すれば、内容の変更を検知できる。ここでは、圧縮前のJSONバイト列から SHA-256を計算する。
さらに、ハッシュ値をオブジェクトのキーそのものに含めることもできる。
files/{record_id}/{sha256}.json.gzip
内容が異なればハッシュ値も変わるため、別のオブジェクトとして保存される。一方、リトライによって同じオブジェクトを再度保存する場合はキーが 同じになるので、処理を冪等にしやすい。
読み取り時には、キーに含まれるハッシュ値とオブジェクトから計算したハッシュ値を比較することで、意図しない上書きを検知することができる。
この設計では、オブジェクトキー自体を期待するハッシュ値として利用することができるため、DBにハッシュ値専用のカラムを持たせる必要もない。
SHA-256には理論上ハッシュ衝突の可能性があるが、100京(10¹⁸)の異なるオブジェクトに対するハッシュを計算した場合の衝突確率が約 4.3 × 10⁻⁴² なので、実際のシステム上ではほぼ無視できるケースが多い。