2 ポイント 投稿者 GN⁺ 2023-09-19 | 1件のコメント | WhatsAppで共有
  • OpenDocument Presentation(ODP)をZIPアーカイブの代わりにSQLiteコンテナに格納すれば、文書の保存・起動・復旧の仕組みをより安全かつ高速に設計できる
  • 現在のODPはXMLと画像ファイルをZIPアーカイブにまとめる構造で、49枚のサンプルプレゼンファイルは content.xmlstyles.xmlmeta.xmlsettings.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.xmlstyles.xmlmeta.xmlsettings.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件のコメント

 
GN⁺ 2023-09-19
Hacker News のコメント
  • SQLite をファイル形式として使うアプリを作っている
    ユーザーが文書を編集して保存するときだけファイルが変わる、一般的な流れを保ちたいので、ファイルを開くときは :memory: データベースにコピーしている: https://www.sqlite.org/inmemorydb.html
    ユーザーは自由に操作し、アプリは別個のドキュメントモデルなしでデータベース形式に直接反映する。保存時には VACUUM で再びデータベースファイルに書き出して処理する: https://www.sqlite.org/lang_vacuum.html
    適度なサイズのファイルではうまく動作し、自分のアプリでは常にその範囲内に収まっている

    • なぜ補助的な揮発性データベースを使うのか分からない。ユーザーがファイルを編集している状況なら、1秒に1回も書き込まないだろうから、性能上の利点も大きくない
      むしろデータベースに直接自動保存して、保存ボタンをなくすほうがよい。衝突に強く、データベースが1つなのでコードとバグが減り、SQLite の書き込みは成功するか失敗するだけで中間状態がない。一方、文書で引用されているとおり、VACUUM INTO は予期しない終了や電源断の際に出力データベースが不完全または破損する可能性がある
      SQLite が本来意図した方法で使えば、SQLite の寿命が続く限りこの部分を悩まずに済む
    • 一般的なアプリのように動くということは、アプリが落ちたり電源が切れたりすると未保存データを失うという意味だ
      各操作の後に一時的な場所、たとえば XDG ディレクトリ基準で ~/.local/share/application/yourapp のような場所に保存し、ユーザーが保存を押したら希望する場所へファイルをコピーするほうがずっとよい。電源断後にアプリを再び開いても、ほぼ同じ地点から復旧でき、失うとしても最後の数秒程度で済む
    • もっと単純には、データベースを開くときにWAL モードへ切り替え、自動チェックポイントを無効にすればよいかもしれない: https://www.sqlite.org/pragma.html#pragma_wal_autocheckpoint
      ユーザーが保存するときにチェックポイントを実行し、WAL の内容をメインデータベースへマージすればよい
    • ドキュメントによると、VACUUM は内容を一時データベースファイルへコピーしたうえで元のファイルを上書きし、上書き時には通常のトランザクションと同じようにロールバックジャーナルまたは WAL を使う。そのため、元の最大2倍程度の空き容量が必要になる
      VACUUM INTO は一時データベースの代わりに INTO で指定したファイルを使い、再び元のファイル上へコピーする段階は省略する。実際に使っているのが電源断にも耐える VACUUM なのか、書き込み中の電源断に弱く、既存のファイル名なら破損させる可能性がありそうな VACUUM INTO なのかが重要だ
    • 似た方式を使ったことがあり、メモリ上でデータベースをキャッシュとして動かし、定期的にディスクへ保存していて、backup API を使っていた: https://www.sqlite.org/backup.html
  • SQLite の問題は、標準化されたファイル形式ではないという点だ
    文書化はよくされていて理解も広く共有されているが、SQLite ファイルを解釈する方法を ISO 標準が細かく定義しているわけではない。代替実装についても同じだ
    Zip と XML は SQLite より API 表面がはるかに小さい。SQLite の API はいくつかの C 関数にとどまらず SQL 言語そのものであり、SQL パーサー、クエリオプティマイザー、コンパイラー、バイトコード仮想マシン、全文検索エンジンなどをデータ破損なしに実装するのは、XML パーサーよりはるかに大きな仕事だ
    相互運用性や ISO 標準化が重要ではないドメイン特化のクローズドなアプリなら SQLite は良いファイル形式だが、OpenOffice にはそうした懸念が実際にあったのだと理解している

    • この問題が何を指しているのか曖昧だ。SQLite のファイル形式はパブリックドメインで、よく文書化されており、複数の言語向けパーサーが存在する
      SQLite C ライブラリもパブリックドメインなのでソースは完全に公開されており、ファイル形式を扱い、たいていの ISO 標準より文書化の水準が高い。主要言語のバインディングもほぼそろっている
      SQLite ファイル内に保存される何らかの OpenDocument 形式が、まだ作成され文書化されるのを待っているという問題なら話は別だ。ISO 標準は良いものだが、ISO がファイル形式を定義するまで待たなければならなかったとしたら、使えるものはあまりにも少なかったはずだ
    • 標準ファイル形式になるために、SQL パーサー、クエリオプティマイザー、コンパイラー、バイトコード仮想マシン、全文検索エンジンをすべて実装する必要はない
      LibreOffice のスプレッドシートを読むために、すべてのスプレッドシート機能を実装する必要がないのと同じだ。必要なのはテーブルを再構成できる能力であり、その後に欲しい情報は、選んだ言語で書いた命令型コードで巡回して取得すればよい
    • これは米国議会図書館にとっても問題ではない。議会図書館は SQLite を CSV、XML、JSON と並ぶデータセットの推奨保存形式として定義している
    • ファイル形式とその使い方を混同しているように見える。SQLite ファイル形式を使うアプリは、SQLite ライブラリをアプリの一部として使えばよい
      そのライブラリを再実装するのは大仕事だろうが、OpenDocument ファイル形式を使うコードを再実装するのと同じ種類の作業だ。ファイル形式自体はかなり単純だ
    • 標準が必ずしも必要なわけではない。アプリケーションと文書の間のすべての相互作用は SQL を通じて行われ、SQL は少なくとも重要な部分では標準化されている
      互換性が心配なら、文書を MySQL のような別のデータベースからもアクセス可能にすればよい
  • Audacity が SQLite を採用すればファイル保存機能が大きく改善すると思っていたが、実際には落とし穴が多かった。
    Linux で /etc/fstab によって作成した、root 所有だが全員が書き込み可能な NTFS マウントに新規ファイルとして保存すると、権限エラーのような理由で失敗し、既存ファイルに保存すると正常に動作した。
    プロジェクトを編集した瞬間にディスク上のファイルが変更されるため、Audacity プロジェクトを Git にバイナリの塊として入れると不要な Git 差分が生じる。保存しても古いデータや削除済みデータがプロジェクトウィンドウを閉じるまで SQLite ファイルに残るので、コミット前にウィンドウを閉じないとリポジトリに入ってしまう可能性がある。以前は .aup3 ファイルを直接 VACUUM する必要があったと記憶しているが、今はウィンドウを閉じれば十分だ。Word 2003 の 高速保存 を思わせる感じが残る。

    • Audacity が落ちたり異常終了したりすると、クリーンアップがまったく行われず面倒。以前は復旧プロセスで孤立ブロックがあると知らせてくれて、保持するか削除するか選べた。
      数百 MB のはずのプロジェクトが数 GB になり、ディスク容量を節約する必要があったとき、単純な単一トラック作業では Mix and Render が解決策だった。音声は変えないが、保存と終了時に残骸を片付けられた。
      これは SQLite 自体の問題ではなく、明らかにアプリケーション層の問題だ。Audacity 2 には一時作業領域の概念があったように思うが、Audacity 3 は .aup3 ファイル自体を作業領域として使っているようだ。
      Audacity 3 の形式を覗いてみたが、以前の .aup ファイルに相当するプロジェクトデータを単一行テーブルに XML として保存しつつ、テキストをそのまま使わず単純な辞書コーダでエンコードしていて、非常に不可解だった。相互運用性と検査をはるかに難しくし、性能にもごくわずかながら悪影響があり、容量削減は数百 MB の音声ファイルに対して数 KB 程度の丸め誤差だろう。
    • ユーザーが期待する機能をそのまま再現すべきだ。すべては一時ファイルに保存し、明示的な保存操作があったときだけ元ファイルを上書きすべきだ。
      Git の観点では、差分とマージが容易なテキスト形式を使うほうが有利だ。SQLite ダンプがこの点でどれほど扱いやすいかは分からない。
    • 妻が Audacity を一日中使っているが、数日おきに破損した SQLite ファイルができる。重複キーエラーが出て、Audacity では修復したり再インポートしたりする方法が分からない。
      重要なら手作業で直すことはできるが、たいていはファイルを捨てればまた動く。
  • 良い記事だ。ただ、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 を持つ項目の manifestversionId を返す。

    • 便利な機能ではあり得るが、正直なところ、そのようなクエリがそう返るとは期待しない。
      この場合、ネストしたクエリは不要で、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 特有の機能はあまり使わない。決定的に最良の行を選ぶには、上のような明示的な方法が必要だ。
    • SQL で一般に期待される動作ではないが、SQLite はしばしば期待を外れる。この場合は便利だが非標準だ。
    • 実装上の便利な副作用が、後に公式の動作になったように思える。Python の辞書キー順に似ている。
      Postgres では DISTINCT ON クエリで似たことができる。SQL では単純に見えるのに、自分には最も難しく感じた作業の一つだ。
    • これを定義済みの動作だと主張できるのはかなり驚きだ。manifestversionIdmax(checkinTime)関数従属していないためだ。
      たとえば同じ checkinTime 値を持つ行が2つあり、その値が最大である可能性がある。
  • SQLiteとXMLファイルの両方を使う製品をリリースしたことがある
    改善した点の1つは、データ量の少ないいくつかのテーブルをXMLファイルに移したことだった。ファイルが小さく、ほとんど使われないため、データアクセス層と診断が単純になり、複数行のタブインデント付きXMLにした
    製品を診断しなければならない技術担当者にSQLiteデータベースを開いて見てくれと言うのは負担が大きかった。しかし製品の主要部分では、SQLiteはXMLファイルより圧倒的に優れていた。以前のバージョンはXMLファイルを使っていたが、XMLファイルには良い差分更新の方法がなく、スケーラビリティの問題があった
    XMLの利点である人間が読みやすい形式は、ファイルが小さく、スキーマ設計が読みやすいXMLに合っている場合にだけうまく機能する。毎回XMLファイル全体を書き直さなければならないことと、機能が増えるにつれて生じる複雑さが、XMLの最大の利点を急速に削っていく
    一般ユーザーがオフィス文書の内部を直接いじる必要がある場面は十分にまれなので、SQLiteリーダーの使い方を覚える程度なら、受け入れられる参入障壁だ。ファイル途中への任意書き込みにおけるXML+Zipの限界は、ムーアの法則でも克服できない

    • SQLiteのネイティブ形式が、Zipなしの状態でXML+Zipと同程度のサイズをどう実現しているのかはよく分からない。SQLiteのTEXTBLOBフィールドが圧縮されるのか、それとも呼び出し側が書き込む前にBLOBを圧縮すると想定しているのかが気になる
  • ODTは標準化を念頭に置いて設計された。以前の形式もかなり似ていたが、XHTML、SVG、CSSのような既存標準に大きく依存している
    既存標準を参照できなければ、ODT仕様そのものが急に巨大になるだろう。標準を更新する労力も相当なものに見えるし、ここ数年はあまり進展がなかった
    現実的にはSQLite形式を選択肢として提供することはできるだろうが、オフィス文書形式という船はすでに出てしまったように思う。ただし、SQLite仕様を公式標準としてまとめるべきだという主張には、十分な根拠になる

    • 仕様は非常に簡潔に書かれており、効果や動作より主に構文だけを規定しているにもかかわらず、840ページにも及ぶ
      いくつかの欠点、たとえばooo:rsid属性のせいでローカルスタイルとテキスト範囲が爆発的に増える問題、疎でないスプレッドシート、テーブルスタイリングの奇妙な仕組みなどを除けば、この種の文書データ向けに非常によく設計されたマークアップだ。意味論的マークアップと、ユーザーが実際にやりたい表現とのバランスが良い
      一方、Office OpenXMLには状態を持つ書式用の空タグがあり、DOCXでは後続のテキストが太字表示になるかどうかを切り替える
  • ファイル形式を SQLite に結び付けるのは、どこか間違っている気がする
    SQLite は優れているが、この領域ではかなり独特だ。多くのことをこなすため、そのまま複製するのが難しいからだ
    ただ、この場合それほど多くの機能が必要かというと、そうではない。基本的で安全なトランザクション意味論と、単純なテーブル構造を保存できる能力があればよく、SQL 標準全体やクエリ最適化器までは必要ない
    よりよいファイル形式はあり得るが、SQLite とは分離された形式のほうが望ましい

    • なぜ駄目なのか分からない: https://www.sqlite.org/appfileformat.html
      サイズは 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_stmtsqlite3_stmt_busysqlite3_sql で詳細なエラー報告を作り、問題を取り除いた。個人用途だったにもかかわらず、トランザクション再試行コードは任意のロギングとコメントだらけだった。PostgreSQL 向けのトランザクション再試行ロジックのほうがずっと簡単だった
      もう一つ驚いたのは、synchronous=NORMAL の WAL モードでは、コミット済みトランザクションが電源喪失やシステムクラッシュ後にロールバックされ得るというドキュメントの記述だった: https://sqlite.org/pragma.html#pragma_synchronous
      私のアプリケーションには関係なかった
    • SQLite はすでにまさにこの目的で使われている。OGC GeoPackage として使われており、Mapbox/Maptiler のデータセットも使っている
    • ある種の形式は、何よりも交換のために設計される。SQLite 側の主張は、アプリ所有者が SQLite 形式をユーザーに強制して事実上の標準にせよというもので、法的な標準にする作業は抜けている
      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/
    • Apple のアプリを見ると、多くが SQLite を保存形式として使っている。iMovie、iPhoto、Voice recording などがそうで、Docker も同じだ
      それほど間違った選択ではあり得ない
  • 別の例として、ラスター地図タイルがある。実質的には、数百万個にもなり得る小さな正方形画像だ
    Zip、tar、ファイルシステム、SQLite をすべて試したところ、SQLite が最も速く最も小さく、オーバーヘッドのない通常のアーカイブよりも優れていた

    • 多くのファイルシステムは、単一ディレクトリに数万個以上のファイルがあると問題が起きるが、地図タイルではまさにそういう状況になる。SQLite のほうが速いのは驚くことではない
    • SQLite のほうが速いなら、問題は使っている Zip ライブラリにある
      SQLite には大きな欠点がある。データベースから得た BLOB は mmap できないため、別の場所へコピーしなければならない。Zip ファイルは、圧縮されていない場合や PVRTC のような特殊なエンコーディングで圧縮されている場合なら、そのまま mmap できる
  • OpenDocumentは圧縮された画像とXMLです。つまり結局は形式全体をパースしてメモリに載せるということです
    SQLiteがこれをどう改善するのか、よく分かりません。XMLが理想的ではないとはいえ、Zipで圧縮されているのでサイズ上のペナルティも大きくありません
    SQLiteの記事で挙げられている利点はすべて、SQLiteを文書のランタイムモデルとして使えば実現できます。ディスク上でもメモリ上でも可能ですが、SQLiteが転送形式である必要はありません
    むしろSQLiteは現在の形式より大きくなる可能性があります。変更を重ねると未使用領域が生じ、断片化し、スパースになることがあります。毎回最適化しなければならないなら、高速保存のような利点も失われます
    デルタ更新と高速なインデックス検索が必要で、ファイル全体をメモリに載せるべきでない形式では、実際にSQLiteをファイル形式として多く使っています。ただしOpenDocumentは、この仮定のシナリオでSQLiteの対象として選ぶには悪い例だったと感じます

    • XMLとZipはインクリメンタル更新をうまく扱えません。保存時にアプリケーションファイル全体を書き込む必要があり、書き込み中に問題が起きると破損が発生する可能性があります
      SQLiteをディスク形式として使い、アプリケーションを適切に実装すれば、破損した状態で終わらずに済む可能性があります
      XML/Zipでもリネームの小技で同様のことは実現できるかもしれませんが、SQLiteはそれを単一のディスクファイルで提供します。すでにSQLiteをメモリモデルとして使っているなら、ディスク/転送形式としても使わない理由はありません。その時点ではほぼ無料です
      ファイルサイズの問題はVACUUMで対処できそうです