世界を動かす技術を、日本語で。

OpenDocumentがSQLiteを使用したらどうなるか?

概要

  • OpenDocument プレゼンテーション(ODP)形式の課題と設計思想の考察
  • ZIPアーカイブの代替として SQLite データベース利用の提案
  • ファイルサイズ・速度・メモリ使用量・ユーザー体験の改善案
  • 将来のファイルフォーマット設計 への示唆
  • 技術的検証と実験的改善案の紹介

Small. Fast. Reliable.Choose any three. SQLiteを活用したファイルフォーマット設計の思考実験

  • OpenDocument ファイル形式(特にODP)は、ZIPアーカイブ内にXMLや画像ファイルを格納する構造
  • 本稿は現行仕様の批判ではなく、 将来のファイルフォーマット設計 の改善案を示す思考実験
  • ZIPの代わりに SQLiteデータベース をコンテナとして採用する利点を検討

OpenDocumentとODPファイルの現状

  • OpenDocumentは ワープロ・表計算・プレゼン資料 用の標準ファイル形式
  • ODPファイルはZIPアーカイブで、XML(content.xml, styles.xml, meta.xml, settings.xml)と画像ファイル群を格納
  • 例:49ページのプレゼン資料では62枚の画像と複数のXMLファイルを含む
  • mimetypeファイルでファイル種別を明示

ODP形式の制約

  • ZIPは カスタムバイナリ形式より優れている が、SQLiteなら更なる利点
  • ZIPは ファイル単位の更新が困難 で、部分更新やクラッシュ耐性に難
  • 全体書き換え が必要なため、保存・起動が遅い。SSDの書き換え寿命にも影響
  • 全データをメモリ展開 する設計で、複数資料編集時に大量メモリ消費
  • クラッシュリカバリが煩雑で、ユーザー体験が悪化
  • XMLはテキストだが、 巨大な1行ファイル で可読性・編集性が低い

改善案1:ZIPをSQLiteに置換

  • ZIPの代わりに SQLiteデータベース を使うことで、ドキュメントの 部分更新原子的保存 が可能
  • 単純なテーブル設計例:
    CREATE TABLE OpenDocTree(
      filename TEXT PRIMARY KEY,
      filesize BIGINT,
      content BLOB
    );
    
  • 実験結果:NeoOfficeで作成したODPをSQLite(SQLAR)に変換した場合、 ファイルサイズはほぼ同等かやや小さい
    • ZIP生成ロジックによってはZIPのほうが小さい場合もあるが、差は僅少
  • SQLite利用時、変更ファイルのみ更新でき、 保存速度向上SSD負荷低減 が期待

改善案2:コンテンツの細分化

  • SQLiteは 多数の小さなデータ管理 にも適している
  • content.xmlのような 巨大なXMLファイル を、スライドごとなど小さな単位に分割して格納
    • 例:スライドごとに1レコードとして保存
    • テーブル設計例:
      CREATE TABLE slide(
        pageNumber INTEGER,
        slideContent TEXT
      );
      CREATE INDEX slide_pgnum ON slide(pageNumber);
      
  • メリット:
    • 起動時に必要なスライドのみ読み込み 可能
    • メモリ使用量の大幅削減
    • 編集・保存処理の効率化
    • クラッシュ時の復旧容易化

今後のファイルフォーマット設計への示唆

  • SQLiteベースの設計 は、可搬性・信頼性・効率性を兼ね備える
  • 部分更新高速起動低メモリ消費堅牢なクラッシュリカバリ が実現可能
  • 既存のZIPベース設計の課題を踏まえ、 次世代ファイルフォーマット への応用余地
  • ユーザー体験の向上開発者の負担軽減 を両立するアーキテクチャ提案

Hackerたちの意見

面白い内容だけど、テキストドキュメントやスプレッドシートについても触れて、"バージョン管理"以外にどんな機能が追加されるのかを示してくれたらもっと意味があったと思う。私だけかもしれないけど、プレゼンテーション機能はOpenOfficeファミリーの中でもあまり使われてない部分だと思うんだよね。

読んでる間に、テキストを扱う方法の一つとして、リンクリスト形式でストレージするのはどうかなって考えてたんだ。そうするには、エディタがブロック概念で動く必要があると思うけど、ドキュメントエディタはそんな風には動かない気がする。スプレッドシートの方がシートや行・列レベルで分けられるから、ちょっと楽かもしれないね。今すぐ試してみたくなっちゃうな…

彼が最初の改善点として挙げた「ZIPをSQLiteに置き換える」は、他のODFフォーマットにも当てはまるだろうね。彼はXMLをSQLiteで小さなパーツに分けることを提唱してる。各スライドを新しいXMLレコードにするのは理にかなってるかも。スプレッドシートに移ると、今のODFがどうなってるかは分からないけど、各シートを別々のXMLにするのも良いかもしれない。Writeドキュメントについて考えると、良い小さな単位は何だろう?ページごとに1つのXMLは細かすぎると思うけど、章ごとに1レコードにするのはありかも。段落ごとに1レコードは意味がないと思うけど、いろんなアイデアを試すのは面白そうだね。

ヒップ博士は時々、sqliteデータベースのアプリケーションファイルフォーマットとしての利点を熱く語ることがあるよね。GitよりもFossilの方が優れているとも言ってるし。彼の主張は大体納得できるものだし、彼は本当に素晴らしいソフトウェア開発者の一人だから、私は彼の説教を我慢して聞いてる。彼は私の個人的なヒーローでもあるんだ。

これらはSQLiteの動作をよりよく理解するための思考実験だね。サポート文書はこうやって書かれるべきだと思う。他の人が読むためにね。彼は注意書きもかなり気を使ってたし。

sqliteベースのファイルフォーマットを作るのは比較的簡単そうだし、もし古いバージョンのLibreOfficeをアップグレードできないユーザーがいたら、プラグインを追加すればいいと思う。テキストやスプレッドシートファイルの利点についてもっと説明が必要だという他のコメントに同意するよ。でも、LibreOfficeの作業グループがもっと深く研究する価値があるアイデアだと思う。もしメモリ削減が本当に実現できて、それがクラッシュの減少につながるなら、他に利点がなくても大きなプラスになると思う。

私はサイドプロジェクトであるバーチャルホワイトボードのためにSQLiteを試してるんだけど、まだ完全には理解できてないけど、ファイルシステムAPIとやり取りするよりはずっと楽な感じがする。まだ解決できてない問題は、同期やコラボレーションがどう絡むかなんだ。今のところ考えてるのは:1. プレーンテキスト形式(JSONとか)またはgitでバージョン管理されたSQLiteダンプファイル 2. 何か現代的なローカルファーストのCRDT(Turso、libsql、Electric SQL) 3. ローカルでも動かせるサーバー/クライアントアーキテクチャ。これに成功した人いる?

SQLiteには、変更のグループを記録して再生するためのセッション拡張が組み込まれてるよ。必要な処理も全部含まれてる。セッションを解決策として推奨するわけではないけど、他のものと比較するのは良いアイデアだと思う。https://sqlite.org/sessionintro.html これにはCレベルのAPIがあるよ。Pythonが分かっていて、プロトタイピングや探索をしたいなら、私のSQLiteラッパーが役立つかもしれない。セッション拡張をサポートしてるからね。これが使い方の例だよ: https://rogerbinns.github.io/apsw/example-session.html

CRDTは、オフライン作業が多い場合にとても頑丈な選択肢だよ。

SQLiteをアプリケーションフォーマットとして使うなら、注意してほしいことがある。BLOBタイプはサイズが2GiBに制限されてる(int32)。使い方によってはそれが高いと感じるかもしれないし、そうでないかもしれない。SQLiteデータベースにそんなにバイナリデータを保存するのは適切じゃないって人もいるけど、アプリケーションフォーマットは通常、大きなバイナリデータを一つのファイルにまとめる必要があるから、複数のファイルを一緒にコピーして動かすよりはいいと思う。

データを複数のBLOBに分けることができるよ。

SQLiteをアプリケーションファイルフォーマットとして使うなら、以下のことをすべきだよ:1. secure_deleteプラグマを有効にすること。https://antonz.org/sqlite-secure-delete/> これでユーザーが何かを削除したとき、データが実際に消えるようになる。そうしないと、ユーザーがアプリのファイルを他の人と共有したとき、受取人が送信者が削除したと思っていた情報を復元できちゃうかもしれない。2. https://www.sqlite.org/security.html#untrusted_sqlite_databa...> にある「信頼できないSQLiteデータベースファイル」のオプションを有効にして、信頼できないソースからのファイルを開くときに安全性を高めること。メールの添付ファイルを開いたときにハッキングされたくないよね。3. セキュリティの脆弱性を扱う際、SQLiteの開発者はこのユースケースをニッチだと考えていることに注意してほしい(「実際のアプリケーションで信頼できないソースからSQLiteデータベースファイルを開くことは少ない」と言ってるし)。彼らはSQLiteに対してファズテストを行う人々にイライラしているみたいだけど、アプリケーションファイルフォーマットは絶対にファズテストすべきだと思う。https://www.sqlite.org/cves.html 彼らはSQLiteをアプリケーションファイルフォーマットとして使う方法についてのマーケティングページでこれらのことを全く言及していない。

「ほとんどのアプリケーションは、マイナーなSQL入力のバグを心配せずにSQLiteを使える。」それなのに、ドキュメントのやり取りフォーマットとしてSQLiteを推奨してるんだよね。

うーん、アプリケーションフォーマットとしてSQLiteを使うのは、Limboにとっていい使い方かも。

そして、彼らは人々がSQLiteに対してファジングを行うことにイライラしているように見えるけど、アプリケーションファイルフォーマットは絶対にファジングすべきだと思う。これは不公平な読み方だと思う。SQLite自体もファジングを行っていて、外部のファジングで見つかったバグにはすぐに対応してるよ。彼らのドキュメントには、自分たちのファジングについてのセクションがあって、サードパーティのファジングにも感謝してるし、個々のエンジニアへのクレジットもあるんだ。 https://www.sqlite.org/testing.html CVEの文書のトーンは、自動ツールによってフラグが立てられたCVEに対して人々がパニックになるからなんだ。SQLiteの典型的な使用に対してセキュリティに影響を与えない問題や、すでに何らかの形で妥協が生じている前提条件がある問題に関してね。

SQLiteは、ネットワークファイルシステムでは信頼性が低いんだ。ロックが正しく実装されることに依存してるからね。最近、アプリにそのファイルシステム用のチェックを追加したんだけど、実際に関連するデータ破損を目の当たりにしたから。もっとシンプルなファイルフォーマットなら、そんな要件は必要ないんだよね。SQLiteは確かに良いけど、この用途には向いてないかな。[1] https://github.com/lifthrasiir/angel/commit/50a15e703ef2c1af...

簡単な解決策は、本物のロックファイルの隣に空のロックファイルを置くことだね。

その場合、アプリケーションは一時ファイルを保持して、保存時に上書きすることになるね。

それはかなり広範で一般化しすぎてるね。ロックサポートが不十分なネットワークファイルシステムは、ほぼいつも管理者の悪い設定だよ。NFSもCIFSも、ネットワーク全体のロックでちゃんと動くからね。SQLiteは潜在的な問題を避けるためにネットワークファイルシステムの使用を避けるように勧めてるけど、実際には成功することもあるよ。

この記事の文脈では、それはほとんど関係ないね。ZIPはマルチユーザーシナリオでは全く使えないから、たとえSQLiteが完璧じゃなくても、この思考実験で置き換えるZIPフォーマットよりはずっと良いよ。

50メガバイトのプレゼンテーションで1文字を変更するだけで、SSDの有限な書き込み寿命を50メガバイトも消費するのは面倒だよね。昔はこれをすごく気にしてたけど、実際には一度も問題になったことはないよ。50メガバイトはかなり極端な例だけど、それでもこのドキュメントを数百万回編集しない限り、気にしなくていいと思う。オブジェクトグラフを再シリアライズするのは、タブularモデルにマッピングするよりもずっと速いことがあるよ。コアごとに数ギガバイトを処理できるJSONシリアライザーもあるし、SSDコントローラーの特性を考慮すると、タブularの更新が一度に大きなJSONストリームをダンプするよりも多くのブロックを書き込むことになるかもしれないね。

AnkiのストレージフォーマットはSQLite(数年前はそうだった)だよ。それのおかげで、10年間使ってたAnkiデッキの内容(ビューのログも含めて)を自分が設計してたカスタムシステムにインポートするのがすごく楽だった。sqlite3のREPLを立ち上げて、ちょっと探ってみて、データを取り出すために標準のSQLクエリを書けばいいだけだからね。

面白い読み物だね!SQLクエリを使って関連データだけを取得するアイデアはかなり説得力があると思う。ただ、実際にはどうなるのか気になるな。ユーザーが行った変更はSQLで挿入しないといけないから、新しいデータがSQLクエリに含まれるようにする必要があるけど、ユーザーは変更を加えて保存しない(または別のファイルに保存する)ことも期待してるよね。保存するときにだけコミットされる大きなトランザクションを作るべきなのかな?「名前を付けて保存」を使うときに、そんなトランザクションを別のファイルにコミットすることは可能?それとも、編集のためにファイルを別の一時的な場所にコピーして、そのファイルに常にコミットして、保存するときに一時ファイルを元のファイルに移動する必要があるのかな?(こうすればSQLiteが提供する破損に対する耐性を失わないし)。それとも、もっと良い方法があるのかな?保留中の変更を元のファイルに保存するのは、ユーザーがファイルの動作を期待していることに反するからあまり好きじゃないし、データが漏れちゃう可能性もあるからね。

変更を挿入して、現在保存されている行をマークすることもできるよ。これもかなり粗いアンドゥツリーとして機能するだろうし、実際にユーザーの期待に反するかどうかは分からないけど、Officeは一応「保存」してくれるし、忘れたときのために一時的なバージョンとして保存してくれるからね。