Skip to content
Published on

SQLiteはどこにでもある

Authors

はじめに — 世界で最も静かな支配者

データベースの話をすると、人はたいていPostgreSQL、MySQL、Oracle、最近ならクラウドデータウェアハウスを思い浮かべます。ところが地球上で最も広くデプロイされているデータベースエンジンは、そのどれでもありません。答えはSQLiteです。

SQLiteはおそらく世界で最も多く実行されているデータベースです。控えめに見積もっても、稼働中のインスタンスは兆(trillion)単位と推定されます。なぜなら、どこにでもあるからです。あなたのポケットの中のAndroidスマホとiPhoneに入っていて、Chrome・Safari・Firefoxといったすべての主要ブラウザの中で動き、デスクトップアプリの設定やキャッシュを保存し、さらには旅客機のアビオニクスにも使われています。それでもSQLiteは静かです。サーバープロセスも、管理者も、ポート番号もありません。ただライブラリとしてアプリの中にリンクされ、黙々と仕事をします。

この記事は、この静かな支配者がどう設計されているからこれほど広まったのか、いつSQLiteが重いクライアントサーバーDBに実際に勝つのか、そしてプロダクションでSQLiteをきちんと使うための実務知識を整理します。

サーバーがない — 単一ファイルという急進的な設計

SQLiteを理解する第一の鍵は、それが**サーバーレス(serverless)**であることです。最近流行の「サーバーレスクラウド」とは別の意味です。ここでのサーバーレスとは、SQLiteには別途立ち上がるデータベースサーバープロセスがそもそもない、という意味です。

典型的なデータベースを思い浮かべてみましょう。PostgreSQLを使うなら、どこかでpostgresサーバープロセスが動いていて、アプリケーションはネットワークソケット(通常TCP 5432ポート)を通じてそのサーバーに接続し、SQLをやり取りします。サーバーは接続を管理し、認証を処理し、同時アクセスを調整します。この構造は強力ですが重いです。サーバーをインストールし、実行し、設定し、セキュリティを固め、死なないよう監視しなければなりません。

SQLiteはこの層全体をなくします。

  典型的なクライアントサーバーDB
  +-------------+     ネットワーク   +------------------+     +----------+
  | アプリケーション | <- ソケット -> | DBサーバープロセス  | --> | ディスク  |
  +-------------+                 +------------------+     +----------+

  SQLite
  +-----------------------------------+     +------------+
  | アプリケーション (SQLiteがライブラリ | --> | 単一ファイル |
  |  としてリンクされ、関数呼び出しで直接) |     | (.sqlite)  |
  +-----------------------------------+     +------------+

SQLiteではデータベース全体がディスク上のファイル一つです。テーブルも、インデックスも、ビューも、トリガーも全部その一つのファイルの中に入っています。アプリケーションはSQLiteをライブラリとしてリンクし、SQLの実行はネットワーク往復ではなく、ただの関数呼び出しです。データベースに「接続」するというのは、単にそのファイルを開くことです。

この単純さがSQLiteがどこにでもある根本的な理由です。デプロイするサーバーがないので、アプリをインストールすればデータベースも一緒にインストールされたことになります。バックアップはただのファイルコピーで、別のマシンへ移すのもファイルをコピーするだけです。運用負担が実質的にゼロです。

SQLiteがクライアントサーバーDBに勝つとき

SQLiteの公式ドキュメントには有名な一節があります。「SQLiteはPostgreSQLのようなものと競争するのではなく、fopen()と競争する」。つまりSQLiteの本当の代替は他のデータベースではなく、直接ファイルを開いて読み書きすることです。この視点から見ると、SQLiteがいつ最善かがはっきりします。

SQLiteが特に強いケースです。

  • アプリケーションローカルなデータ: デスクトップ・モバイルアプリの設定、状態、キャッシュ、オフラインデータ。ユーザーごとに自分のファイルを持ちます。ここにネットワークDBは過剰です。
  • 読み込みが圧倒的に多いワークロード: SQLiteは読み込みに非常に速いです。コンテンツがほとんど読まれるだけのウェブサイトなら、SQLite一つで驚くほどのトラフィックをさばきます。
  • エッジと組み込み: IoT機器、車両、アビオニクスのようにDBサーバーを立てられない環境。ライブラリとしてリンクされるのでぴったりです。
  • ファイル形式の代替: 複雑な独自バイナリ形式を作る代わりに、SQLiteファイルをアプリケーションの保存形式として使います。スキーマ・トランザクション・クエリをタダで得ます。
  • テストとプロトタイプ: 後で詳しく扱いますが、インメモリSQLiteはテストに理想的です。

逆にSQLiteが不向きなケースもはっきりしています。

  • 高い同時書き込み: SQLiteは書き込みを直列化します。複数のクライアントが毎秒数千回ずつ同時に書き込むワークロードなら、クライアントサーバーDBのほうがよいです。
  • 複数マシンからのアクセス: 複数のサーバーが一つのデータベースにネットワークでつながる必要があるなら、ファイルベースのモデルは合いません。
  • 超大規模+複雑な管理機能: きめ細かな権限管理、レプリケーショントポロジ、大規模並列分析などが必要なら、専用DBのほうがよいです。

核心的な洞察はこれです。多くのアプリケーションが習慣的にクライアントサーバーDBを使いますが、実際のワークロードを見れば、SQLiteで十分でむしろより単純な場合が非常に多いのです。「デフォルトで重いDB」ではなく「必要なときだけ重いDB」へと考え方を変えると、システムがずっと軽くなります。

同時実行の核心 — WALモード

SQLiteに対する最もよくある誤解は「同時実行が弱い」ことです。半分は正しいですが、その認識はたいてい古いデフォルトモードに基づいています。WAL(Write-Ahead Logging)モードをオンにすると話が大きく変わります。

伝統的なロールバックジャーナルモードでは、書き込みが進む間は読み込みが止まり、読み込みが進む間は書き込みが止まる傾向がありました。データベースファイル全体に対するロックが粗く働いていたからです。

WALモードはこの構造を変えます。変更をデータベース本体ファイルに直接書く代わりに、別のWALファイルにまず追加(append)します。この方式の核心的な利点は次の通りです。

  • 読み込みと書き込みが互いを止めない。 一つの書き込みが進む間にも複数の読み込みが同時に進みます。読む側は最後にコミットされた一貫したスナップショットを見て、書く側はWALに追加します。
  • コミットが速い。 変更をWALの末尾に順次追加するのは、ファイルのあちこちをランダムに直すよりずっと速いです。

ただしSQLiteの書き込みは依然として一度に一つです。WALモードでも同時に書く書き手は一人だけで、残りの書き込みは順番を待ちます。しかし読み込みはそれとは無関係に並列に流れます。ウェブアプリケーションのほとんどが「読み込みが多く書き込みが少ない」パターンだと考えると、WALモードのこの特性は実務で非常によく合います。

WALモードはコマンド一行でオンにします。一度設定するとデータベースに永続的に残ります。

-- WALモードを有効化(データベースファイルに永続保存される)
PRAGMA journal_mode = WAL;

-- 耐久性と性能のバランスのためのよくある設定
PRAGMA synchronous = NORMAL;

synchronous = NORMALはWALモードでよく使われる組み合わせです。完全なFULLよりディスク同期を少ない頻度で行うのでずっと速く、それでいて停電時にデータベースが破損しない水準の安全性を保ちます(最悪の場合、最後のいくつかのトランザクションだけを失う可能性があります)。プロダクションでSQLiteを使うなら、この二つのPRAGMAはほぼ標準設定に近いです。

このブラウザの中で本物のSQLiteを触ってみたいなら、SQLプレイグラウンドでWALでもウィンドウ関数でも直接実行できます。分析特化のSQLを試したいなら、DuckDBデータ分析プレイグラウンドも一緒に開いて比べてみてください。

テストの秘密兵器 — インメモリデータベース

SQLiteの最も愛される実務用途の一つがテストです。ファイルパスの代わりに特別な名前を与えると、SQLiteはディスクではなくメモリ上にデータベース全体を作ります。

import sqlite3

# ディスクではなく純粋にメモリ上のデータベース
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
conn.execute("INSERT INTO users (name) VALUES ('Alice')")
row = conn.execute("SELECT name FROM users WHERE id = 1").fetchone()
print(row)  # ('Alice',)

これがテストで強力な理由はいくつもあります。

  • 極めて速い。 ディスクI/Oが一切ないのでテストが一瞬で回ります。数千のテストがそれぞれきれいなDBを作っても負担になりません。
  • 完璧な隔離。 各テストが自分だけのインメモリDBを新しく作れば、テスト間の状態汚染が原理的にありません。テストが終わればメモリとともに消えます。
  • セットアップが不要。 テストを回すために別途データベースサーバーを立てたり片付けたりする必要がありません。CI環境で特に価値があります。

一つ注意点は、プロダクションとテストのデータベースが異なるときに生じる微妙な差です。プロダクションでPostgreSQLを使いつつテストだけインメモリSQLiteにすると、SQL方言の差のせいでテストは通るのにプロダクションで壊れる場合があります。そのため最も安全な組み合わせはプロダクションもSQLiteを使うことで、そうでなければ少なくとも統合テストだけは本番と同じDBで回すことです。

ブラウザの中のSQLite — WASMという新地平

SQLiteの移植性は元々すごかったのですが、WebAssemblyはそれをまったく新しい場所へ連れて行きました。ブラウザの中です。

SQLiteはよく書かれたCコードベースなので、WASMにコンパイルするのに理想的です。その結果、今やブラウザタブの中で、サーバーなしで、本物のSQLiteエンジンが動きます。真似ではなく実際のSQLiteです。先にリンクしたSQLプレイグラウンドがまさにこの方式で動作します。

この組み合わせが開く可能性はかなりのものです。

  • 完全なクライアントサイドアプリ: データがブラウザを離れないオフラインファーストなウェブアプリケーションを作れます。ユーザーデータをブラウザの永続ストレージにSQLiteファイルとして保管します。
  • プライバシー: 機微なデータをサーバーに送らず、ブラウザで直接SQLで分析します。
  • 教育とツール: SQLを教えたりデータを探索したりするツールを、バックエンドインフラなしの純粋な静的サイトとしてデプロイできます。

PostgreSQL系もWASMに来る流れがありますが、SQLiteはそもそも組み込みライブラリとして設計されているため、この移行が最も自然です。「データベースをアプリの中に入れる」というSQLiteの元々の哲学が、ブラウザという新しいアプリ環境でそのまま受け継がれたわけです。

耐久性とレプリケーション — litestreamという優雅な解

SQLiteをプロダクションのウェブサーバーで使うとき、まず出てくる心配はこれです。「データベースがサーバーのローカルファイル一つなのに、そのサーバーが死んだらデータはどうなるのか?」

伝統的な答えはクライアントサーバーDBのレプリケーション機能を使うことでした。しかしSQLiteのエコシステムにはより優雅なアプローチがあります。litestreamのようなツールです。

litestreamのアイデアは巧妙です。先に説明したWALファイルを覚えていますか? litestreamはこのWALに追加される変更をリアルタイムで観察しながら、それをS3のようなオブジェクトストレージへ連続ストリーミングします。つまりデータベースに起こるすべての変更が、ほぼリアルタイムで外部にバックアップされるのです。

  アプリケーション
      |
      v (書き込み)
  SQLite (ローカルファイル + WAL)
      |
      v (litestreamがWALの変更を連続観察)
  オブジェクトストレージ (S3など) へストリーミングバックアップ
      |
      v (障害時)
  別のサーバーで最新状態に復元

この方式の利点は次の通りです。

  • ほぼリアルタイムのバックアップ。 定期スナップショットと違い、変更が発生した瞬間に外部へ流れます。サーバーが死んでも失うデータが最小化されます。
  • 簡単な復元。 新しいサーバーでオブジェクトストレージからデータベースを復元すれば、最後の状態に近いところまで蘇ります。
  • 安価で単純。 別途レプリカサーバーのクラスタを運用する必要がなく、安価なオブジェクトストレージ一つで済みます。

このアプローチは「SQLiteは耐久性が弱い」という通念をひっくり返します。単一ファイルの単純さを保ちつつ、クラウドオブジェクトストレージをバックアップ先にして堅牢な耐久性を得ます。最近では複数のサーバーがSQLiteを共有したり読み込みレプリカを置いたりする方向にエコシステムがさらに発展していて、「SQLiteは単一サーバー用」という認識さえ少しずつ変わっています。

SQLiteをきちんと使うための実務チェックリスト

ここまでの内容をプロダクション目線の実務指針に圧縮すると次の通りです。

  • WALモードをオンにせよ。 ウェブアプリケーションならほぼ常に正しいです。PRAGMA journal_mode = WALPRAGMA synchronous = NORMALは事実上の標準設定です。
  • 書き込みは直列化されることを覚えよ。 SQLiteは同時書き込みを一つに直列化します。書き込みトランザクションは短く保ち、長いトランザクションの中で長く握り続けないでください。
  • ビジータイムアウトを設定せよ。 書き込みロックがかかっているとき即座に失敗する代わりに少し待つようbusy_timeoutを設定すると、瞬間的な競合をなめらかに乗り越えます。
  • テストはインメモリで。 速く隔離されたテストのために:memory:データベースを活用してください。ただしプロダクションDBとの方言差に注意を。
  • 耐久性はlitestreamのようなツールで。 プロダクションのウェブで使うなら、WALストリーミングバックアップでデータ損失リスクを下げてください。
  • バックアップはファイルコピーではなく正しい方法で。 実行中のデータベースを単純なcpでコピーすると破損しうます。SQLiteのバックアップAPIやVACUUM INTO、あるいはlitestreamを使ってください。

おわりに

SQLiteはソフトウェア工学の稀有な成功例です。派手ではなく、マーケティングも静かですが、地球上で最も広く使われるデータベースになりました。その秘訣は急進的な単純さです。サーバーをなくしデータベースをファイル一つにした決断が、SQLiteをスマホとブラウザと飛行機の中へ連れて行きました。

そしてその単純さは弱点ではありません。WALモードが同時実行の問題を相当部分解き、インメモリモードがテストを革新し、WASMがブラウザという新しい舞台を開き、litestreamが耐久性の心配を解消することで、SQLiteは「おもちゃのDB」という古い認識を完全に脱ぎ捨てました。真剣なプロダクションシステムの真剣な選択肢になったのです。

次に新しいプロジェクトで「とりあえずPostgreSQLを立てよう」と反射的に考えたら、少し立ち止まって問うてみてください。「このワークロード、SQLiteで十分ではないか?」。驚くほど頻繁に、答えは「そうだ」です。そしてその選択は、あなたのシステムをずっと単純で堅牢にしてくれるでしょう。

参考資料