latest log

酩酊状態で書いたエンジニアポエムです。酩酊状態で読んでください。

HTML5, WebStorage, WebSQL memo

WebStorage, WebSQL の実装寄りの脳内ダンプ。乱文。あとで gist に移動して消す。

WebStorage(LocalStorage) は、IE8+, Chrome, Safari, Opera, Firefox で利用可能。
WebSQL は、MobileSafari, Chrome, Safari で利用可能。iOS 4.3.x では一部問題がある。

WebStorage の内部文字コードUTF-16 *1
WebSQLの内部文字コードUTF-16 *2

UTF-16で保存されている場合は1文字で2byte使用する。JavaScript文字コードも内部は UTF-16

WebStorage で保存する文字列にNULL文字(0x0000)が含まれていると、一見保存できているように見えるが、実は保存できていない。サスペンド/レジューム, キャッシュアウト, 電源OFF/ON などのタイミングで取り出せなくなる。

WebSQL で NULL文字を保存するとどうなるかはまだ調べていないが、BLOB型があるのでそのまま保存できるとおもわれる。

MobileSafari で openDatabaseすると自己のスキーマ情報を含んだ24KBのファイル(SQLite.db)が生成される。

SQLite.db は WebStorage, WebSQL, Application Cache, Indexed DB で利用される。
SQLite.db のファイルパスは https://gist.github.com/4682065 を参照。

ファイルシステム上で5MBを超えるとWebStorageならエラーが表示される。
WebSQLなら「容量を10MBに拡張しますか? (YES/NO)」のダイアログが表示される。
ダイアログは、5MB→10MB, 10MB→25MB, 25MB→50MBの三段階で表示される。最大は50MBまででそれ以上はエラーになる。
iOS ではダイアログが表示されるが、Android では自動で50MBまで拡張される。

png画像をXHRで取得し、Base64に変換して保存し、文字列の先頭に"data:image/png;base64,"をつけて <img src="..."> にDataURIとして代入することで、画像が表示される。
この仕組みにより様々なものをストレージに保存する事ができる。

Base64化により、画像を保存するために必要なサイズは 1.33倍になる。
最大5MB のストレージで、内部文字コードUTF-16の場合は、論理上2.5MBしかデータを保存できない。
さらに自己のスキーマ情報を格納するためのサイズも必要になり、レコード数が多いとレコード分の情報を格納する領域も必要になる(SQLiteは1つのDBファイルに全ての情報を記録する)。このため実質的に保存可能な領域は更に縮小される

容量オーバーのエラーや「拡張しますか?」 ダイアログはファイルシステム上のサイズから判断される。ストレージとしてブラウザから認識できるサイズではない

つまり、細かいレコードを沢山生成するとその分保存できる容量は目減りする。大きなファイルを一つだけ保存すると容量を限界まで利用できる。
と思うのは早合点で、実際はそうではない事もわかっている。

MobileSafari の WebStorage に安全に保存可能な画像サイズは、 2.4MB * 0.75 = 1.8MB になる。
2.4MB は 5MB / 2 (UTF-16) で 2.5MB, そこからさらに スキーマ情報格納用に -24KB, 各レコードの情報を保存するために -70KB 程度必要と考えてのこと。
0.75 は Base64 化でデータ量が1.33倍になるため。

Base64化した画像をUTF-16でそのまま保存せず、上位byteと下位byteにpackして保存することにより、バイナリデータ保存時に必要な容量を1/2にする事が可能になるが、pack/unpack変換で余計なコストも必要になる。
便宜上名前が必要なため、これをダブラーと呼ぶ(映画JMから)。

ダブラー。
Base64 は 0-9,a-z,A-Z,+,/ を利用する。Base64のコードをASCIIコードで見た場合の最小値は +(0x2b), 最大値は z(0x7a)。
UTF-16Base64化し、上位byteと下位byte にpackしたあとのUTF-16の値の範囲は、0x2b2b 〜 0x7a7a。

ダブラーの問題と、さらに効率の良いエンコード方式。
データの保存方法としてUTF-16Base64化しpackするダブラーではだいぶ無駄があるので、特定の値の分布を調べ、その値をエスケープするだけで他の値をそのまま通すエンコード方式にすれば、エンコード速度とストレージ効率を更に改善することが可能となる。このエンコード方式の損失を0.01程度と仮定。
Uint16Array等を使うことで、エンコード/デコード時の処理コストを最小化することができそう。
今回のケースでは0x0000を固定でエスケープする方式で良いが、0x0000が連続しているスパースファイルでの損失が大きいため、ちょっと工夫が必要。
近代的な画像フォーマットでは、画像は何らかの方式により圧縮されており、BMPのように0x00が連続するケースは少ない。

MobileSafari の WebStorage にダブラーを使い安全に保存可能な画像サイズは、4.8MB * 0.75 = 3.6MB になる。
ダブラーではなく0x0000をエスケープする方式を使えば、4.8MB * 0.99 = 4.75MB になる。

SQLite.db の特徴として、一度拡張したDBのファイルシステム上のサイズは自動で縮小しない。
例えば、一度3MBになったら全てのレコードを削除してもファイルシステム上のサイズは3MBのまま。

WebStorage には ストレージミューテックスと呼ばれている排他の仕組みがあり、
navigator.getStorageUpdates (最新の仕様では navigator.yieldForStorageUpdates) により解放することができる。
WebStorageは複数のwindowからlocalStorageに同時にアクセスすると、
ユーザが知らない間にストレージの内容が書き換わっていたり、破壊されるといった状況になりえる。

navigator.getStorageUpdates はWebKit系のブラウザに実装されているがFirefoxやIE10,Operaには実装されていない。
navigator.getStorageUpdates を使用すると、意図的にミューテックスを解放することができる。

*1:Chrome(PC)は不明

*2:Chrome(PC)のみUTF-8