- SQLiteの単一ライター構造と組み込み型という特性が、むしろスケーラビリティと性能を高める要因として働くことを実験で実証
- 同一条件で Postgresはネットワーク遅延時に348 TPSまで低下した一方、SQLiteはネットワークを排除することで 44,096 TPS を達成
- 単一ライターモデルを活用した バッチ処理とSAVEPOINTベースの細粒度トランザクション により、最大 186,157 TPS、安定構成では 102,545 TPS を記録
- アムダールの法則 がネットワーク型データベースのボトルネックを説明し、SQLiteはそれを回避することで高い効率を維持
- この結果は ローカル環境におけるSQLite活用の可能性 と ネットワークボトルネック排除の重要性 を強調している
SQLiteの構造と実験環境
- SQLiteは MVCCを持たず、単一ライターのみを許可 するが、この構造がむしろ高いスケーラビリティを可能にする
- 組み込みデータベースであるため、ネットワークオーバーヘッドがない
- ベンチマークは Apple M1 Pro、メモリ16GBのMacBook Pro(2021) 環境で実施
- 実験は完全な最適化ではなく、一般的な条件でも高い書き込みスループットを達成できることを示す目的 で行われた
TPSの定義とトランザクション例
- TPSは単純な書き込み速度ではなく、対話型トランザクション(Interactive Transaction) を意味する
- 例:口座間送金では、複数のクエリとアプリケーションコードが1つのトランザクション内で実行される
- トランザクションはエラー発生時に状態をロールバックできるため、一貫性維持において中核的な役割 を果たす
ベンチマーク構成
- Clojureベースの仮想スレッド(virtual threads) を使って大規模な同時リクエストをシミュレーション
- Postgresは HikariCPベースのコネクションプール で構成し、SQLiteは 単一ライターとコア数分の読み取り接続 を使用
- 両データベースとも id, balance フィールドを持つ単純な account テーブルを使い、10億行 を挿入
- ユーザー活動は べき乗則分布(0.9995) に従い、約 10万人のアクティブユーザー が存在
ネットワーク型データベース(Postgres)の性能
- 同一サーバー内でPostgresは 13,756 TPS を達成
- ネットワーク遅延を5ms追加すると 1,214 TPS、10msでは 702 TPS へ急減
- シリアライズド分離レベルを適用後は 660 TPS、追加クエリを含めると 348 TPS まで低下
- これは アムダールの法則 により、ネットワークボトルネックが全体性能を制限することを示している
- ネットワーク遅延が増えると、トランザクションロック競合が激化してスケールしなくなる
SQLiteの組み込み型としての利点
- ネットワークを排除した後、SQLiteは 44,096 TPS を達成
- ネットワークボトルネックが消えることで、アムダールの法則の影響を最小化
- 単一ライター構造を活用して バッチ処理(batch processing) を適用すると、186,157 TPS まで向上
- 動的なバッチサイズ調整によって レイテンシ(latency)とスループット(throughput) を自動最適化
SAVEPOINTによる細粒度トランザクション
- バッチ内の個別トランザクション失敗を防ぐため、SAVEPOINTを用いたネストされたトランザクション を適用
- 失敗時はそのトランザクションだけをロールバックし、バッチ全体は維持
- この方式でも 121,922 TPS を維持
読み取り/書き込み混在負荷テスト
- 全リクエストの75%を読み取り、25%を書き込みで構成
- 別個の読み取りスレッドプールを使い、読み取りリクエストが書き込みを妨げないよう分離
- 結果として 102,545 TPS を達成
性能比較まとめ
| 条件 |
Postgres |
SQLite |
| ネットワークなし |
13,756 |
44,096 |
| 5ms遅延 |
1,214 |
n/a |
| 10ms遅延 |
702 |
n/a |
| 10ms + シリアライズド |
660 |
n/a |
| バッチ処理 |
n/a |
186,157 |
| バッチ + SAVEPOINT |
n/a |
121,922 |
| バッチ + SAVEPOINT + 読み取り |
n/a |
102,545 |
結論
- SQLiteは 単一ライターモデルと組み込み型構造 により、ネットワーク型データベースよりはるかに高いTPSを達成
- アムダールの法則が示すネットワークボトルネックの限界 を回避することで、効率を最大化
- 全コードはGitHubで公開されており、関連テーマとして アムダールの法則、べき乗則、SQLiteのスケーリング事例 などの資料もあわせて示されている
- SQLiteは ローカル環境での高性能トランザクション処理 に非常に有効な選択肢である
2件のコメント
外部サーバーを介さずローカル環境だけで使うなら、ネットワークという税金を払う必要があるのか、ということですね。(VFS vs Socket)
Hacker Newsのコメント
私は SQLite ベースのハイブリッド protobuf ORM/CRUD サーバーを作っています。
コードと説明は GitHub - accretional/collector にあります。
リアルタイムバックアップ時でも 5〜15ms のダウンタイム、数百件の読み書きリクエストのキューイング、CRUD 全体のレイテンシは 1ms 前後で、WAL ベースの ストリーミングバックアップ まで可能です。
以前は Postgres と Spanner しか使っていませんでしたが、Collector にパーティション機能さえ追加されれば、もう Postgres は使わないと思います。
欠点は、すべてのデータと処理を 単一マシン に収める必要がある点です。
AWS の u-24tb1.112xlarge インスタンス(448 vcore、24TB RAM、64TB EBS)を使えば、かなり余裕があります。
この記事は SQLite の効率性を強調していますが、比較基準が不明確 だと感じました。
もともと分離されたサーバー構成を前提にしていたのに、ローカルな組み込み DB の性能を測定していたからです。
同じ条件なら、ローカル Postgres をチューニングしても似たような性能は出せます。
Postgres の接続数を 8 に制限したのは ボトルネック かもしれません。
CPU とスレッド使用量もあわせて公開し、より大きいコネクションプールで再テストするとよいでしょう。
64 接続まで増やせばスループットが 8 倍になるかもしれません。限界に達するまで クライアント設定を拡張 すべきです。
要点は、ネットワーク遅延がボトルネックかどうかを認識すること です。
多くのワークロードでは、平凡なローカル DB のほうが優れたリモート DB より速いです。
重要なのは「どの DB が最高か」ではなく、「ネットワーク境界をまたぐ必要があるのか」です。
ネットワーク型 DB には、アプリの再デプロイが容易という利点があります。
新しいインスタンスを立ち上げて既存のものを停止すれば、ほぼ 無停止デプロイ が可能です。
SQLite を同じインスタンスに置くと、置き換え時に DB も再起動しなければならず、より複雑になります。実運用でこうした問題を経験したことがあるのか気になります。
移行時にはダウンタイムが発生する可能性があります。Litestream のおかげで、今ではレプリケーションとバックアップが簡単になりました。
筆者は
PRAGMA synchronous="normal"に設定していましたが、これは 毎回 fsync を実行しない ということです。公平な比較のためには
"full"に設定すべきです。"normal"でも問題ありません。電源断時には耐久性は失われますが、トランザクションの整合性 は保たれます。SQLite の HA(高可用性) 構成はどうなるのか気になります。
少なくとも自動フェイルオーバーが可能な程度にはなっていてほしいです。
私はいま Postgres と SQLite(litestream 含む)のどちらにするか悩んでいます。
私のアプリは多少のダウンタイムを許容できるので、単一ボックスで垂直スケールするほうがシンプルで安価です。
Marmot GitHub に gossip ベースのレプリケーション機構 が新たに追加されました。
実際に SQLite を 本番で限界まで使い込んだ事例 があるのか気になります。
一般的な Web アプリやコマース環境で、SQLite vs Postgres の ユーザー数の限界 がどの程度なのか気になります。
SQLite は最近のアップデートで 同時読み取り は可能ですが、単一書き込み しか許可されません。
これがどのような場合に問題になるのか、そして将来的なスケールを考えるなら Postgres から始めたほうがよいのか、意見を聞きたいです。