問題
これまでは問題なかったが、ある日突然 Chrome で動画ファイルをアップロードしようとすると以下のように Out of Memory が画面に表示されるようになった。
調査(環境)
ここ最近、ということでちょうど最近 Chrome の CVE 対策でブラウザのバージョンアップがあったことを思い出した。 そのため、記事執筆時点の最新版(Chrome 136)と、ちょうど手元にあったひとつ前のバージョン(Chrome 135)で比較したところ、前者のみこのエラーが発生した。
また、Chrome 135 を最新化して同様のオペレーションを行うと、同様に問題が発生したので、問題は最新版の Chrome であると突き止めた。
同時に、Edge で実施しても、Edge 136 で同様の問題が発生したため、この問題は Chronium の問題かと考えられる。
調査(ソースコード)
今回はローカルのファイルを読み込み、それを S3 に分割アップロードする仕組みで問題が発生した。
その時のコードは以下のようになっていた。
export async function readPartData(index, fileBlob) {
const offset = index * FILE_CHUNK_SIZE;
const slice = fileBlob.slice(offset, offset + FILE_CHUNK_SIZE, fileBlob.type);
const buffer = await slice.arrayBuffer();
const data = new Uint8Array(buffer);
return {
index,
data,
};
}
このコードの内、new Uint8Array(buffer)
で問題が発生することまでを突き止めた。
更にいろいろなケースを探っていくと、動画ファイルのサイズが小さければこの状況でも正常にアップロードできる。 ということは、分割ファイル数に問題があるのか? と捉えてみていくと、FILE_CHUNK_SIZE = 47 * 1024 * 1024;
(47MB) の時は失敗して、46MB の時は成功することが分かった。 もともとは FILE_CHUNK を 100MB にしていたので、一定以上のファイルサイズの動画をアップロードする際にエラーが発生していた。
Chronium 側の対応
これは以下で報告されている問題が Chromium 136 で顕現していると考えられる。
処理の最適化のために Uint8Array で利用可能な最大サイズが 46,505,916 バイト(変更前は104,638,348バイト)になったことが原因らしい。
問題の対処方法
2種類の方法で対処可能。
- そもそも Uint8Array を使わない:ファイルのアップロード(特に s3 への put)であれば、Uint8Array を使わずに buffer をそのまま返してもアップロードができる。 そのため、無駄な処理を行わないように Uint8Array を使わないようにすれば良い
- 何らかの理由で Uint8Array を使う必要がある場合: チャンクを十分に小さく (例えば 32MB程度)にすれば利用できるので、チャンクサイズを制限して利用する
まとめ
今回調査した結果、Uint8Array をソースコードで利用しないで処理の効率化ができることは分かったが、インスタンスの生成にそこそこ小さいメモリサイズでも制約があることを初めて知った。
同じようなタイミングで同じような問題に引っかかる人も多いかと思うので、この記事が修正の助けになれば幸いである。