- OpenDocument Presentation(ODP)をZIPアーカイブの代わりにSQLiteコンテナに格納すれば、文書の保存・起動・復旧の仕組みをより安全かつ高速に設計できる
- 現在のODPはXMLと画像ファイルをZIPアーカイブにまとめる構造で、49枚のサンプルプレゼンファイルは
content.xml、styles.xml、meta.xml、settings.xml と画像など合計78項目で構成される
- ZIPベースの構造では小さな変更でもアーカイブ全体を書き直しやすく、増分更新が難しいため、File/Saveの遅延やSSDへの書き込み量増加につながる
- SQLiteに変えるとファイルをテーブル行として保存でき、さらにスライドごとにコンテンツとバージョンを分けて、最初のスライドだけを読んだり変更されたスライドだけを保存したりする方式が可能になる
- これはOpenDocument自体を批判したり変更を提案したりするものではなく、SQLiteがアプリケーションのファイル形式においてアトミックな保存、アクセス性、バージョン管理、復旧機能を簡単に実現できることを示す事例である
思考実験の範囲と対象
- 対象は OpenDocument の中でもプレゼンテーション文書形式である ODP(OpenDocument Presentation)
- 目的はOpenDocumentを実際に変更することではなく、今後のファイル形式設計でSQLiteをコンテナとして使う方式を検討することにある
- 期待される効果は、より小さな文書、より高速なFile/Save、より高速な起動、より少ないメモリ使用量、文書のバージョン管理、より良いユーザー体験である
現在のODPファイル構造
- ODPファイルはXMLファイルと画像リソースを含むZIPアーカイブである
- 例として、2014年のSouthEast LinuxFestにおけるSQLite関連の49枚の発表ファイルは、
zip -l の出力で合計78項目を持つ
content.xml、styles.xml、meta.xml、settings.xml の4つのXMLファイルがスライドレイアウト、テキスト内容、スタイルを定義する
- 発表ファイルには全画面写真から小さなアイコンまで62個の画像が別ファイルとして保存されている
mimetype ファイルには application/vnd.oasis.opendocument.presentation という1行が入っている
- OpenDocumentのワープロ文書やスプレッドシートファイルも似た構造だが、分析対象はODPである
ZIPベースODPの限界
- ZIPアーカイブは1回の書き込みと多数回の読み出しに最適化されたキー/値データベースに近く、少数のキーが大きなBLOB値を持つ構造に向いている
- 個々の項目の更新が難しいため、ユーザーが
File/Save を選ぶと通常はZIPアーカイブ全体が再作成される
- 停電やクラッシュの最中でも文書全体を壊さないよう個別項目を更新することは可能だが、十分に難しいため実際にはほとんど使われない
- 50MBのプレゼンファイルで文字を1つ変えただけでも、50MB全体を書き直す状況が起こる
- 起動時間が遅くなりうる
- ODPはすべてのスライド内容を
content.xml という1つの大きなXMLファイルに保存する
- LibreOfficeは最初のスライドを表示するためにこのファイル全体を読み込み、解析する
- 画像もすべてメモリに読み込まれているようで、その結果ファイルをダブルクリックすると最初のスライドではなく進行バーが表示される
- メモリ使用量が大きくなる
- ZIP構造は起動時に文書全体をメモリへ読み込み、編集をメモリ上で行い、保存時に文書全体をディスクへ書き出す実装を招きやすい
- 50MBのプレゼンファイルが200MBを超えるRAMを使用することがある
- 複数のプレゼンファイルを同時に開き、ブラウザやデスクトップアプリも併用するとスワップが発生しうる
- クラッシュ復旧が煩雑になる
- OpenOffice系アプリはクラッシュに備えて、メモリ上の文書を定期的にバックアップする
- バックアップ中にアプリが数秒間停止することがあり、再起動後には別個の復旧ダイアログを経る必要がある
- コンテンツのアクセス性が低い
- ZIPツールで画像を取り出すことはできるが、スライドテキストを一般的なツールで抽出したり修正したりするのは難しい
- サンプルファイルの
content.xml は1行目がXML宣言で、2行目には211,792文字のXMLが1行で入っている
最初の改善: ZIPをSQLiteに置き換える
- 第1段階は、ZIPの項目をSQLiteテーブルの行に置き換える単純な構造である
CREATE TABLE OpenDocTree(
filename TEXT PRIMARY KEY,
filesize BIGINT,
content BLOB
);
- この段階ではファイル形式の残りの構造は変えない
- 依然として「ファイルの寄せ集め」構造だが、各ファイルはZIPエントリではなくSQLiteデータベースの行になる
- NeoOfficeが作成した
self2014.odp と同じ内容を SQLAR ユーティリティで再パッケージ化したSQLiteファイルのサイズ比較は次の通り
self2014.odp: 10,514,994バイト
self2014.sqlar: 10,464,256バイト
- コマンドライン
zip で再圧縮した zip.odp: 10,416,644バイト
- SQLiteファイルはNeoOfficeが生成したODPより約0.5%小さかった
- コマンドライン
zip でうまく圧縮したZIPファイルは、SQLiteよりさらに約0.5%小さかった
- SQLiteデータベースはサイズ面でZIPアーカイブと十分に競争できる
- SQLiteは アトミック書き込み を提供するため、クラッシュや停電の最中でも文書を壊す危険なしに増分変更を保存できる
content.xml 全体を書き直さなければならない制約は残る
- それでも残りの77ファイルはそのままにできるので、File/Saveが高速化し、SSDへの書き込み量も減る
2つ目の改善: コンテンツを小さな断片に分割する
- SQLiteは大きな塊だけでなく多数の小さな断片も効率よく保存できるため、スライドごとのコンテンツテーブルを持てる
CREATE TABLE slide(
pageNumber INTEGER,
slideContent TEXT
);
CREATE INDEX slide_pgnum ON slide(pageNumber);
- 最初の画面を表示するとき、アプリケーションは最初のスライドだけを読めばよい
SELECT slideContent FROM slide WHERE pageNumber=1;
- この構造では最初のスライド内容だけをすばやく取得・解析・表示できるため、起動時に
content.xml 全体を読む必要がない
- 実装上の選択肢も増える
- 最初のスライドを表示した後、バックグラウンドスレッドで残りのページを読み込める
- 現在のスライド1枚だけをメモリに置くこともできる
- 高速な切り替えのため、現在のスライドと次のスライドだけをメモリに置くこともできる
- 保存も変更されたページだけを書き直せばよいため、File/Saveはさらに高速になる
- 短いテキスト断片では圧縮効率が下がり、文書サイズが大きくなる可能性がある
- ただし文書容量の大半は画像が占めるため、テキスト圧縮効率の低下はユーザー体験の改善に比べれば小さなコストと見なせる
3つ目の改善: バージョン管理
- スライドを個別のオブジェクトとして保存すれば、同じ文書内にバージョン履歴を持たせられる
CREATE TABLE slide(
slideId INTEGER PRIMARY KEY,
derivedFrom INTEGER REFERENCES slide,
content TEXT
);
CREATE TABLE version(
versionId INTEGER PRIMARY KEY,
priorVersion INTEGER REFERENCES version,
checkinTime DATETIME,
comment TEXT,
manifest TEXT
);
- 各スライドはページ番号ではなく固有の
slideId を持ち、順序は version テーブルの manifest に保存された slideId の一覧で決まる
- 起動時にアプリケーションはまず表示するバージョンを選び、通常は最新バージョンを取得できる
SELECT manifest, versionId FROM version ORDER BY versionId DESC LIMIT 1;
checkinTime を基準に最新バージョンを取得するクエリも可能である
SELECT manifest, versionId, max(checkinTime) FROM version;
- SQLiteでは上記の
max(checkinTime) クエリは定義済みの結果を返すが、他の多くのSQLデータベースでは未定義の結果になったりエラーになったりする可能性がある
- ユーザーが
File/Save を実行すると、変更されたスライドだけを slide テーブルへ新しい行として追加し、変更済み manifest を持つ新しい version 行を作成できる
version テーブルはチェックイン時刻、ユーザーコメント、親バージョンを記録して変更履歴を保持する
- 同じ文書内に複数のプレゼン資料を保存することも可能である
- 別個のバックアップファイルの代わりに特別な
pending バージョンを置けば、未保存の変更を頻繁かつ静かに記録できる
- 文書全体ではなく差分だけを書けばよいので、数MBではなく数KBの書き込みで済む
- 保存時間は数秒ではなくミリ秒単位になりうる
- クラッシュ後に再起動しても、ユーザーの作業の大半、あるいはほぼすべてを保持できる
- ユーザーが未保存の変更を破棄したい場合は、以前のバージョンへ戻ればよい
SQLiteファイル形式でさらに可能な機能
- SQLiteコンテナは3つのテーブルだけでも、アプリケーションのファイル形式に重要な機能を追加できる
- さらにスキーマ、インデックス、トリガー、ビュー、制約を活用すれば、性能、利便性、一貫性を高められる
- 拡張アイデアは次の通り
- データベーステーブルに 自動 undo/redo スタック を保存し、以前の編集セッションまで巻き戻し可能にする
- スライドデッキ、または複数のスライドデッキに 全文検索 機能を追加する
settings.xml をSQLテーブルへ分解し、他のアプリケーションからより簡単に閲覧・編集できるようにする
- 各スライドの発表者ノートを別テーブルに分離し、サードパーティアプリやスクリプトから簡単にアクセスできるようにする
- 単純な直線的スライド順を超え、聴衆の反応に応じて異なる経路や迂回フローを持つプレゼン構造をサポートする
SQLiteへのよくある抵抗感と反論
- エンタープライズSQLデータベースの経験から、SQLiteをアプリケーションのファイル形式として使うことに抵抗を感じるかもしれない
- 多くのエンタープライズデータベースは、大きな文字列やBLOBをデータベースに入れず別ファイルとして保存するよう勧めるが、SQLiteは異なる
- SQLiteのどのカラムでも約1GBまでの文字列やBLOBを保存できる
- 100KB以下の文字列やBLOBでは、別ファイルより I/O性能が高い
- すべてのSQLスキーマは 第3正規形(3NF) でなければならず、小さなプリミティブ型だけを保存すべきだという考えも制約になりうる
- 関係理論は重要だが、実際のファイル形式ではXMLやJSONのような複雑な情報をテキストフィールドに保存するのも許容できる選択である
アプリケーションのファイル形式としてのSQLite
- SQLiteデータベースファイルは、同じ情報を含むZIPアーカイブとサイズがほぼ同じで、場合によってはより小さくなる
- アトミック更新により、小さな変更を安全に文書へ記録してディスクI/Oを減らし、File/Save性能を高められる
- アプリケーションは最初の画面に必要なコンテンツだけを読み込み、起動時間を短縮できる
- 現在表示に関係するコンテンツだけをメモリに置き、残りはディスク上に残せるため、メモリ使用量を大幅に減らせる
- SQLスキーマは、ZIPのようなキー/値構造よりも情報をより直接的かつ簡潔に表現できる
- サードパーティアプリやスクリプトからのアクセス性が高まる
- 組み込みの文書バージョン管理やクラッシュ後の作業復旧といった高度な機能の実装が容易になる
- OpenDocumentはすでに確立され、よく設計された形式であり、SQLiteはOpenDocumentより後に登場したため、これは既存の選択を批判する内容ではない
- Application File Format 文書は、SQLiteをアプリケーションのファイル形式として使う追加のアイデアを提供している
1件のコメント
Hacker News のコメント
SQLite をファイル形式として使うアプリを作っている
ユーザーが文書を編集して保存するときだけファイルが変わる、一般的な流れを保ちたいので、ファイルを開くときは
:memory:データベースにコピーしている: https://www.sqlite.org/inmemorydb.htmlユーザーは自由に操作し、アプリは別個のドキュメントモデルなしでデータベース形式に直接反映する。保存時には
VACUUMで再びデータベースファイルに書き出して処理する: https://www.sqlite.org/lang_vacuum.html適度なサイズのファイルではうまく動作し、自分のアプリでは常にその範囲内に収まっている
むしろデータベースに直接自動保存して、保存ボタンをなくすほうがよい。衝突に強く、データベースが1つなのでコードとバグが減り、SQLite の書き込みは成功するか失敗するだけで中間状態がない。一方、文書で引用されているとおり、
VACUUM INTOは予期しない終了や電源断の際に出力データベースが不完全または破損する可能性があるSQLite が本来意図した方法で使えば、SQLite の寿命が続く限りこの部分を悩まずに済む
各操作の後に一時的な場所、たとえば XDG ディレクトリ基準で
~/.local/share/application/yourappのような場所に保存し、ユーザーが保存を押したら希望する場所へファイルをコピーするほうがずっとよい。電源断後にアプリを再び開いても、ほぼ同じ地点から復旧でき、失うとしても最後の数秒程度で済むユーザーが保存するときにチェックポイントを実行し、WAL の内容をメインデータベースへマージすればよい
VACUUMは内容を一時データベースファイルへコピーしたうえで元のファイルを上書きし、上書き時には通常のトランザクションと同じようにロールバックジャーナルまたは WAL を使う。そのため、元の最大2倍程度の空き容量が必要になるVACUUM INTOは一時データベースの代わりにINTOで指定したファイルを使い、再び元のファイル上へコピーする段階は省略する。実際に使っているのが電源断にも耐えるVACUUMなのか、書き込み中の電源断に弱く、既存のファイル名なら破損させる可能性がありそうなVACUUM INTOなのかが重要だSQLite の問題は、標準化されたファイル形式ではないという点だ
文書化はよくされていて理解も広く共有されているが、SQLite ファイルを解釈する方法を ISO 標準が細かく定義しているわけではない。代替実装についても同じだ
Zip と XML は SQLite より API 表面がはるかに小さい。SQLite の API はいくつかの C 関数にとどまらず SQL 言語そのものであり、SQL パーサー、クエリオプティマイザー、コンパイラー、バイトコード仮想マシン、全文検索エンジンなどをデータ破損なしに実装するのは、XML パーサーよりはるかに大きな仕事だ
相互運用性や ISO 標準化が重要ではないドメイン特化のクローズドなアプリなら SQLite は良いファイル形式だが、OpenOffice にはそうした懸念が実際にあったのだと理解している
SQLite C ライブラリもパブリックドメインなのでソースは完全に公開されており、ファイル形式を扱い、たいていの ISO 標準より文書化の水準が高い。主要言語のバインディングもほぼそろっている
SQLite ファイル内に保存される何らかの OpenDocument 形式が、まだ作成され文書化されるのを待っているという問題なら話は別だ。ISO 標準は良いものだが、ISO がファイル形式を定義するまで待たなければならなかったとしたら、使えるものはあまりにも少なかったはずだ
LibreOffice のスプレッドシートを読むために、すべてのスプレッドシート機能を実装する必要がないのと同じだ。必要なのはテーブルを再構成できる能力であり、その後に欲しい情報は、選んだ言語で書いた命令型コードで巡回して取得すればよい
そのライブラリを再実装するのは大仕事だろうが、OpenDocument ファイル形式を使うコードを再実装するのと同じ種類の作業だ。ファイル形式自体はかなり単純だ
互換性が心配なら、文書を MySQL のような別のデータベースからもアクセス可能にすればよい
Audacity が SQLite を採用すればファイル保存機能が大きく改善すると思っていたが、実際には落とし穴が多かった。
Linux で
/etc/fstabによって作成した、root 所有だが全員が書き込み可能な NTFS マウントに新規ファイルとして保存すると、権限エラーのような理由で失敗し、既存ファイルに保存すると正常に動作した。プロジェクトを編集した瞬間にディスク上のファイルが変更されるため、Audacity プロジェクトを Git にバイナリの塊として入れると不要な Git 差分が生じる。保存しても古いデータや削除済みデータがプロジェクトウィンドウを閉じるまで SQLite ファイルに残るので、コミット前にウィンドウを閉じないとリポジトリに入ってしまう可能性がある。以前は
.aup3ファイルを直接VACUUMする必要があったと記憶しているが、今はウィンドウを閉じれば十分だ。Word 2003 の 高速保存 を思わせる感じが残る。数百 MB のはずのプロジェクトが数 GB になり、ディスク容量を節約する必要があったとき、単純な単一トラック作業では Mix and Render が解決策だった。音声は変えないが、保存と終了時に残骸を片付けられた。
これは SQLite 自体の問題ではなく、明らかにアプリケーション層の問題だ。Audacity 2 には一時作業領域の概念があったように思うが、Audacity 3 は
.aup3ファイル自体を作業領域として使っているようだ。Audacity 3 の形式を覗いてみたが、以前の
.aupファイルに相当するプロジェクトデータを単一行テーブルに XML として保存しつつ、テキストをそのまま使わず単純な辞書コーダでエンコードしていて、非常に不可解だった。相互運用性と検査をはるかに難しくし、性能にもごくわずかながら悪影響があり、容量削減は数百 MB の音声ファイルに対して数 KB 程度の丸め誤差だろう。Git の観点では、差分とマージが容易なテキスト形式を使うほうが有利だ。SQLite ダンプがこの点でどれほど扱いやすいかは分からない。
重要なら手作業で直すことはできるが、たいていはファイルを捨てればまた動く。
良い記事だ。ただ、OpenDocument が Zip アーカイブ内の XML ファイル群である点は気に入っている。
文書形式を理解する重いライブラリなしでも、スプレッドシートのような文書をかなり簡単に生成できる。
Web サービスのユーザーが、テーブル行としてエクスポートしたデータを複数のツールで使いたがる場合がある。UTF-8 CSV はオープンで慣習的で実用的だが、エンドユーザーに CSV を提供したことがある人なら、スプレッドシートアプリで詰まる苦痛を知っているはずだ。
サンプルのスプレッドシートを OpenDocument の ODS と、Microsoft の XML 怪物である OOXML の XLSX として保存したうえで、XML 形式の基本だけを把握した。Zip アーカイブを必須要素だけに削り、コンテンツが入る場所を印付けしておき、リクエスト時に新しいスプレッドシートファイルを作る。今では同じデータを CSV、ODS、XLSX、JSON で出力できる。
SQLite でも可能だが、少し複雑になり、開発速度も落ちるだろう。オフィススイートでテンプレート文書を作り、保存ファイルの XML を掘れるというのは、ニッチだが良い機能だ。
特に
nl_NLのようなロケールの Excel は、CSV ファイルの列区切り文字がセミコロンだとハードコードされているかのように動作するので問題だ。Microsoft が、オランダ人は comma separated values ファイルでカンマを使わない、と悪名高く決めたためだ。localeconv()->decimal_pointの値によって変わる。値が,なら、Excel は CSV ファイルと数式表現言語の両方でセミコロンを使う。以前は Excel で CSV/TXT を開くときに設定でき、LibreOffice では今でも可能だが、全体的な UI 簡素化の過程で
Dataメニュー/リボンタブのどこかへ移された。新しいブックを開いて正しいオプションを探す必要があり、時間を節約したいなら LibreOffice を使うほうがよい。この部分は本当に驚いた。ネストしたクエリが不要だとは信じがたい。
SELECT manifest, versionId, max(checkinTime) FROM version;SQLite では
max(checkinTime)を使うこの2つ目のクエリが実際にうまく動作し、定義済みの答えを返すという。他の SQL データベースエンジンでは未定義の答えを返すかエラーになるが、SQLite では最大のcheckinTimeを持つ項目のmanifestとversionIdを返す。この場合、ネストしたクエリは不要で、
checkinTimeで並べ替えて1件だけに制限すればよい:select manifest, versionId, checkinTime from version order by checkinTime desc limit 1少なくとも SQLite と PostgreSQL では動作するはずだ。Oracle では
where rownum=1を使う必要があり、ネストしたクエリが必要だったと記憶している。GROUP BY manifest, versionId ORDER BY 3 DESC LIMIT 1や、最大のcheckinTimeを求めてから結合する CTE の短縮形のように見なせる。ただし最大の
checkinTimeが同じ行が複数あるとランダム性があり、足元の銃になり得るので、SQLite3 特有の機能はあまり使わない。決定的に最良の行を選ぶには、上のような明示的な方法が必要だ。Postgres では
DISTINCT ONクエリで似たことができる。SQL では単純に見えるのに、自分には最も難しく感じた作業の一つだ。manifestとversionIdがmax(checkinTime)に関数従属していないためだ。たとえば同じ
checkinTime値を持つ行が2つあり、その値が最大である可能性がある。SQLiteとXMLファイルの両方を使う製品をリリースしたことがある
改善した点の1つは、データ量の少ないいくつかのテーブルをXMLファイルに移したことだった。ファイルが小さく、ほとんど使われないため、データアクセス層と診断が単純になり、複数行のタブインデント付きXMLにした
製品を診断しなければならない技術担当者にSQLiteデータベースを開いて見てくれと言うのは負担が大きかった。しかし製品の主要部分では、SQLiteはXMLファイルより圧倒的に優れていた。以前のバージョンはXMLファイルを使っていたが、XMLファイルには良い差分更新の方法がなく、スケーラビリティの問題があった
XMLの利点である人間が読みやすい形式は、ファイルが小さく、スキーマ設計が読みやすいXMLに合っている場合にだけうまく機能する。毎回XMLファイル全体を書き直さなければならないことと、機能が増えるにつれて生じる複雑さが、XMLの最大の利点を急速に削っていく
一般ユーザーがオフィス文書の内部を直接いじる必要がある場面は十分にまれなので、SQLiteリーダーの使い方を覚える程度なら、受け入れられる参入障壁だ。ファイル途中への任意書き込みにおけるXML+Zipの限界は、ムーアの法則でも克服できない
TEXTやBLOBフィールドが圧縮されるのか、それとも呼び出し側が書き込む前にBLOBを圧縮すると想定しているのかが気になるODTは標準化を念頭に置いて設計された。以前の形式もかなり似ていたが、XHTML、SVG、CSSのような既存標準に大きく依存している
既存標準を参照できなければ、ODT仕様そのものが急に巨大になるだろう。標準を更新する労力も相当なものに見えるし、ここ数年はあまり進展がなかった
現実的にはSQLite形式を選択肢として提供することはできるだろうが、オフィス文書形式という船はすでに出てしまったように思う。ただし、SQLite仕様を公式標準としてまとめるべきだという主張には、十分な根拠になる
いくつかの欠点、たとえば
ooo:rsid属性のせいでローカルスタイルとテキスト範囲が爆発的に増える問題、疎でないスプレッドシート、テーブルスタイリングの奇妙な仕組みなどを除けば、この種の文書データ向けに非常によく設計されたマークアップだ。意味論的マークアップと、ユーザーが実際にやりたい表現とのバランスが良い一方、Office OpenXMLには状態を持つ書式用の空タグがあり、DOCXでは後続のテキストが太字表示になるかどうかを切り替える
ファイル形式を SQLite に結び付けるのは、どこか間違っている気がする
SQLite は優れているが、この領域ではかなり独特だ。多くのことをこなすため、そのまま複製するのが難しいからだ
ただ、この場合それほど多くの機能が必要かというと、そうではない。基本的で安全なトランザクション意味論と、単純なテーブル構造を保存できる能力があればよく、SQL 標準全体やクエリ最適化器までは必要ない
よりよいファイル形式はあり得るが、SQLite とは分離された形式のほうが望ましい
サイズは 1MB 未満で https://sqlite.org/footprint.html、全機能を有効にしても 750KB だ: https://www.sqlite.org/about.html
コンパイル時にかなりの機能を省けるし、クエリプランナーを調整したり縮小したりするオプションもあるように見える: https://www.sqlite.org/compile.html
さらに「SQLite はクライアント/サーバーデータベースと競合しない。SQLite は
fopen()と競合する」という言葉もある: https://www.sqlite.org/whentouse.html結局必要なのはデータベースそのものではなく、データベース API と挙動を提供するライブラリだ
SQLITE_BUSYの処理がかなり大変だったトランザクション処理でシリアライズ失敗が想定されることは分かっているが、SQLite では自己デッドロックのような継続的失敗と、一時的な同時更新の問題を区別するのが難しかった。一時的な失敗なら、トランザクション作業を定義したクロージャを再実行すればよいが、継続的失敗なら無意味だ
問題の一部は、
sqlite3_stmtが準備済み文と結果セットの性格をどちらも兼ねている点にある。コンパイル済みバイトコードをキャッシュするため長く保持しがちだが、反復の途中で止まると、その時点でロックを保持している可能性がある。これにより、予期しないロック昇格失敗が起こり得る最終的に
sqlite3_next_stmt、sqlite3_stmt_busy、sqlite3_sqlで詳細なエラー報告を作り、問題を取り除いた。個人用途だったにもかかわらず、トランザクション再試行コードは任意のロギングとコメントだらけだった。PostgreSQL 向けのトランザクション再試行ロジックのほうがずっと簡単だったもう一つ驚いたのは、
synchronous=NORMALの WAL モードでは、コミット済みトランザクションが電源喪失やシステムクラッシュ後にロールバックされ得るというドキュメントの記述だった: https://sqlite.org/pragma.html#pragma_synchronous私のアプリケーションには関係なかった
Richard Hipp とその会社が絶対に逸脱しない ISO/IEC/ANSI/ETSI の SQLite 標準、影響する特許がないという法的レビュー、そして利点がすべて維持される複数の互換 SQLite 実装を示してくれるなら、そのときファイル形式として推奨する議論ができる。そうでなければ、単一ソースの実装に強く依存し、それをユーザーにも押し付けろと言っていることになる
XML、ASN.1、JFIF は公式標準であり、ZIP も OpenDocument の標準化過程で ISO/IEC 21320-1:2015 として採用された公式標準だ
文書で最も重要なのは、他の誰もが読めるべきだという点だ。ディスク更新時間を減らすことは副次的な話だ。Microsoft が標準化機関を歪めて囲い込みを維持しようとしたことから、何も学ばないわけにはいかない: https://arstechnica.com/uncategorized/2008/10/norwegian-standards-body-implodes-over-ooxml-controversy/
それほど間違った選択ではあり得ない
別の例として、ラスター地図タイルがある。実質的には、数百万個にもなり得る小さな正方形画像だ
Zip、tar、ファイルシステム、SQLite をすべて試したところ、SQLite が最も速く最も小さく、オーバーヘッドのない通常のアーカイブよりも優れていた
SQLite には大きな欠点がある。データベースから得た BLOB は
mmapできないため、別の場所へコピーしなければならない。Zip ファイルは、圧縮されていない場合や PVRTC のような特殊なエンコーディングで圧縮されている場合なら、そのままmmapできるOpenDocumentは圧縮された画像とXMLです。つまり結局は形式全体をパースしてメモリに載せるということです
SQLiteがこれをどう改善するのか、よく分かりません。XMLが理想的ではないとはいえ、Zipで圧縮されているのでサイズ上のペナルティも大きくありません
SQLiteの記事で挙げられている利点はすべて、SQLiteを文書のランタイムモデルとして使えば実現できます。ディスク上でもメモリ上でも可能ですが、SQLiteが転送形式である必要はありません
むしろSQLiteは現在の形式より大きくなる可能性があります。変更を重ねると未使用領域が生じ、断片化し、スパースになることがあります。毎回最適化しなければならないなら、高速保存のような利点も失われます
デルタ更新と高速なインデックス検索が必要で、ファイル全体をメモリに載せるべきでない形式では、実際にSQLiteをファイル形式として多く使っています。ただしOpenDocumentは、この仮定のシナリオでSQLiteの対象として選ぶには悪い例だったと感じます
SQLiteをディスク形式として使い、アプリケーションを適切に実装すれば、破損した状態で終わらずに済む可能性があります
XML/Zipでもリネームの小技で同様のことは実現できるかもしれませんが、SQLiteはそれを単一のディスクファイルで提供します。すでにSQLiteをメモリモデルとして使っているなら、ディスク/転送形式としても使わない理由はありません。その時点ではほぼ無料です
ファイルサイズの問題は
VACUUMで対処できそうです