個人SaaSでSQLiteを選んだ理由 (PostgreSQLは過剰だった)
個人SaaSのDB選択はシンプル原則が優れた、SQLiteで困らないケースが多い

SQLite か PostgreSQL か。個人開発での判断軸は次の3点に尽きます。
- 個人 SaaS のリリース初期から中期は、SQLite で十分すぎる (Apple / Google / Adobe / WhatsApp / Expensify など世界中の大規模サービスも本番採用している)
- 「いつ PostgreSQL に移行すべきか」の閾値を運用前に決めておく — 同時書き込み SQLITE_BUSY が日次10件以上 / VPS iowait 20% 超 / 同時接続常時500超、のどれか1つでも 1ヶ月続いたら移行検討
- 移行先は PostgreSQL より libSQL / Turso が段差小さい、SQLite 互換性で既存コードがほぼそのまま動く
以下、GramShift を SQLite で1年運用した実体験ベースで、判断軸と運用ノウハウを共有します。
あなたが個人SaaSのDBに何を選ぶか悩んでいるなら、最初のリリースは SQLite で十分なケースが多いです。私の GramShift Desktop は本番運用に入ってから今も SQLite だけで動いており、利用が増えても困る場面に遭遇していません。この記事では SQLite を選んだ判断、PostgreSQL との比較、見落としやすい運用罠、そして「ここを超えたら PostgreSQL に移行する」というスケール限界の見極め基準を共有します。
SQLite を選んだ3つの理由
個人開発SaaSのDBに SQLite を選んだ理由は、以下の3つに集約されます。
- 運用コストがゼロ: 別途DBサーバーを立てる必要がなく、ファイル1個で完結。バックアップは
cp gramshift.db /backup/で済む - 読み書き性能が想像以上に高い: 単一プロセスからの読み書きなら 10,000 QPS 以上を普通にさばける
- VPS の構成がシンプル: アプリとDBが同一ファイルシステム上にあるため、ネットワーク越しのDB接続のレイテンシがゼロ
GramShift Desktop の場合、Web ダッシュボード (gramshift.com) とデスクトップアプリの双方が同じ SQLite ファイルを参照しています。書き込みも読み込みもボトルネックにはならず、VPS の CPU 使用率は通常 10% 以下で推移しています。少人数のベータ運用フェーズでも、将来的にユーザーが順次増えても、初期段階は SQLite で困らないというのが私の実感です。
PostgreSQL ではなく SQLite が向いている条件
個人SaaSで SQLite が PostgreSQL より向いている条件は、以下のパターンです。
- 単一サーバー (VPS 1台) でアプリが動く構成
- 同時接続数が数十〜数百程度
- 書き込み頻度が秒間数十回まで
- 複雑なクエリ (JOIN 6個以上、ウィンドウ関数多用など) を多用しない
- レプリケーションやマルチマスター構成が不要
逆に、PostgreSQL が必要なケースは、複数サーバーで並行運用する構成、秒間数百回以上の書き込みがある、複雑な分析クエリを頻繁に走らせる、地理的に分散したマスター/レプリカ構成、などです。個人SaaSの初期-中期では、これらの要件はほぼ発生しません。
SQLite を「ちゃんと使う」ための初期設定
SQLite はデフォルト設定でもそれなりに動きますが、本番運用に入る前に必ず以下の設定を入れています。これだけで体感性能と安定性が一段上がります。
// better-sqlite3 の初期化時に WAL モード + 推奨 PRAGMA を入れる
import Database from 'better-sqlite3';
const db = new Database('./data/gramshift.db');
db.pragma('journal_mode = WAL'); // 同時読み書きの性能改善
db.pragma('synchronous = NORMAL'); // WAL と組み合わせで安全 + 高速
db.pragma('busy_timeout = 5000'); // SQLITE_BUSY のリトライを 5秒待つ
db.pragma('foreign_keys = ON'); // 外部キー制約を有効化 (デフォルト OFF)
db.pragma('cache_size = -64000'); // 64MB のページキャッシュ
特に journal_mode = WAL は効果が大きく、デフォルトの rollback journal モードと比べて読み込みが書き込みをブロックしなくなります。複数の読み手と1人の書き手が並行する典型的な Web アプリのパターンに最適化されます。busy_timeout を入れておくと、瞬間的な競合時に即エラーにせず数秒待ってリトライしてくれるので、SQLITE_BUSY の発生件数が体感で1桁減ります。
SQLite運用での注意点 — sql.js キャッシュ罠
GramShift で実際に踏み抜いた SQLite運用の罠を1つ共有します。Web ダッシュボード側では better-sqlite3 を使い、デスクトップアプリ内では sql.js (WebAssembly版) を使っていました。両者は同じ DBファイル を参照しますが、sql.js はメモリ内にデータを保持してキャッシュする仕様です。
あるとき、私がメンテのため VPS で sqlite3 コマンドで直接 DBファイルを更新したところ、デスクトップアプリ側で変更が見えない、という現象が発生しました。原因は sql.js のメモリキャッシュです。pm2 で Webサーバーを stop してから DB 更新、再 start することで反映されます。直接 sqlite3 で DB更新する場合は pm2 stop が必須、というのが教訓です。
このトラップは、SQLite のせいではなく、複数の SQLite クライアントを跨いで同じファイルを操作したことから生まれます。本番運用での DB 直接編集は基本的に避けるべきで、必要な時は必ず関連プロセスを停止してから、というルールに今は徹底しています。
バックアップとリストアの実運用
SQLite のもう1つの強みはバックアップとリストアの単純さです。ファイル1個なので、cp でコピーするだけで論理上はバックアップが完了します。ただし「アプリが書き込み中にコピーすると壊れたバックアップになる」という落とし穴があるので、本番では VACUUM INTO を使うのが安全です。
// 整合性の取れたスナップショットを作る
db.exec(`VACUUM INTO '/backup/gramshift-${Date.now()}.db'`);
これは SQLite 3.27 以降で使える機能で、アプリが書き込み中でも整合性の取れた完全なコピーを別ファイルに作ってくれます。私は毎日深夜3時にこのコマンドを cron で叩き、生成された .db ファイルを別ストレージ (Google Drive) に転送する運用にしています。リストアは破損した DB を停止して、最新スナップショットをファイル名差し替えで復帰、という1〜2分の作業です。PostgreSQL の pg_dump + restore 比べると、運用の単純さは桁違いです。
検討して採用しなかった選択肢 — DuckDB / libSQL / Turso
SQLite の周辺には興味深い派生プロジェクトがいくつかあり、私も初期検討で目を通しました。結果的に採用しなかった理由を残しておきます。
DuckDB: 分析クエリ (OLAP) 向きの組み込みDBで、複雑な集計クエリで PostgreSQL より速いケースがあります。ただし GramShift の主要ワークロードはトランザクション系 (CRUD) なので、SQLite との性能差は誤差レベル。「分析専用に別途持つ」価値は感じましたが、現状の単一 DB 構成のシンプルさを崩してまで導入する理由がありませんでした。
libSQL / Turso: SQLite と互換性を持ちつつ、エッジ分散・レプリケーション・SQL over HTTP を追加したマネージドサービスです。マルチリージョン展開を視野に入れるなら強力な選択肢ですが、私の現状の構成 (VPS 1台) ではメリットが薄く、追加の運用学習コストとマネージド料金 (将来課金が発生する) を考えると不採用としました。サービスが地理的に分散する段階で再評価する候補として頭に置いています。
これらを「将来必要になったら移行できる」と認識しておくだけで、技術選定の心理的な負担が大きく下がります。SQLite からの移行先は SQLite 系の派生に閉じることができるので、PostgreSQL に飛ぶよりも段差が小さいです。
SQLite を本番採用している有名サービス事例
「SQLite は組み込み専用、本番Webサービスでは PostgreSQL」という思い込みを持つ人が多いですが、一次情報を確認すると現実は逆です。
SQLite 公式の "Well-Known Users of SQLite" ページ (sqlite.org/famous.html) には、以下のような大規模サービスが SQLite 本番採用していると明記されています。
- Apple: iOS の Mail / iMessage / iCloud Music Library / macOS Calendar
- Google: Chrome (履歴+設定+Cookie) / Android (システムDB) / Gmail オフライン
- Microsoft: Windows 10/11 (内部DB) / Office 365 デスクトップ / Edge ブラウザ
- Adobe: Lightroom (カタログDB) / Reader (注釈DB)
- WhatsApp: メッセージ履歴 (端末側)
- Dropbox: 同期クライアントのメタデータ
- Expensify: 経費精算 SaaS のサーバー側 ("BedrockDB" と呼ぶ SQLite 拡張、月数百万ユーザー対応)
- Mozilla / Firefox: ブックマーク / 履歴 / Cookie / フォーム履歴
- Airbus: A350 のフライトソフトウェア (Mission Critical 用途)
- Bloomberg / SAP / Tencent / Sony PlayStation: 各種内部システム
特に注目すべきは Expensify です。世界中で月数百万ユーザーが使う SaaS が、PostgreSQL ではなく独自 SQLite 拡張 (BedrockDB、オープンソース化済 / bedrockdb.com) で本番運用しています。理由は「シンプル、高速、信頼性が異常に高い」 (Expensify CEO のカンファレンス講演より)。
個人開発者層でも SQLite 採用は加速しています。Pieces.app (開発者向けスニペット管理 SaaS) は SQLite WASM をブラウザ側で動かし、Litestream (litestream.io) は SQLite を S3 互換ストレージに継続レプリケーションする OSS として人気を集めています。2023-2026年は「SQLite を Web 本番で使う」流れが明確に拡大した期間です。
「PostgreSQL でないと不安」という心理的バリアは、こうした事例を知ることで合理的に解除できます。スケール要件で必要になってから PostgreSQL に移行すればいい、というのが現実的な判断です。
SQLite を選んでよかった具体瞬間 — 個人開発1年で3つ
SQLite で1年運用して「PostgreSQL じゃなくて本当に良かった」と感じた具体瞬間を3つ共有します。
1つ目: VPS 引っ越し。GramShift Web 側を別 VPS に引っ越しした時、SQLite ファイル1個を scp で転送するだけで DB 移行が完了しました。所要時間は数十秒。PostgreSQL なら旧 VPS で pg_dump、新 VPS で pg_restore、接続文字列の更新、ロール権限の再設定、と最低 30-60 分の作業が必要です。引っ越しのストレスが桁違いに少ない。
2つ目: リストアの月次テスト。バックアップは取っているだけでは不十分で、リストアできることを定期的に確認しないと意味がありません (3-2-1 戦略の「テスト」工程)。SQLite なら、月1回バックアップ .db を別ディレクトリにコピーしてアプリを DB_PATH 環境変数で切替起動するだけ。所要時間 5分。PostgreSQL ならテスト用 DB を作る、リストアする、接続切替、と最低 30 分かかります。リストアテストを「面倒だから半年やってない」状態にせず、月次で確実に回せています。
3つ目: 開発環境の即起動。ローカルで開発するとき、git clone → npm install → npm start でアプリが即起動します。SQLite ファイルは .gitignore せず、サンプルデータ込みで Git 管理 (本番DB は別 VPS 上、ローカルはサンプルDB のみ)。Docker Compose で PostgreSQL コンテナを立てる手順が不要で、新マシンへの環境構築が 5分で終わります。将来チームを組む場合も、新メンバーの初日生産性が桁違いになるはずです。
これらは PostgreSQL を選んでいたら全部失われる利便性です。「単純なものから始めて、必要になったら複雑なものに移行する」原則は、個人開発 SaaS では特に強く効きます。
スケール限界の見極め基準
SQLite から PostgreSQL (または libSQL) への移行を判断する基準は、以下のシグナルです。
- 同時書き込みエラー (SQLITE_BUSY) が日次で 10件以上発生する
- VPS の I/O 待ち時間 (iowait) が CPU時間の 20% を超える
- 同時接続数が常時 500 を超える
- 複雑な分析クエリを daily に走らせたい要件が発生する
- マルチサーバー構成 (負荷分散、地理的冗長化) を採用したい
GramShift では、現状これらのシグナルは1つも発生していません。早すぎる最適化は個人開発の敵で、必要になってから移行すれば良い、というのが私の運用方針です。事前に決めた閾値を超えた瞬間に移行検討フェーズに入る、という運用ルールを書いておくのが大事です。閾値がないと「なんとなく不安だから PostgreSQL にしておこう」という判断になりがちで、これは大抵オーバーエンジニアリングになります。
「PostgreSQL から始めるべき」は思い込み
SaaS開発のブログ記事を読むと、「本番運用なら PostgreSQL 一択」のような書き方が多いです。これは大手SaaSや高負荷サービスの前提で書かれているため、個人開発SaaSにはオーバースペックです。「とりあえず PostgreSQL」を選ぶと、VPS構成が複雑になり、運用工数が増え、コードもDB接続周りで複雑化します。
私自身、最初の個人プロジェクトでは PostgreSQL を使っていて、毎月の VPS 料金、DB 接続プールの管理、バックアップスクリプトの保守、と「DB のために時間を使う日々」を送っていました。SQLite に切り替えて以降は DB のことを考える時間がほぼゼロになり、その時間をプロダクト機能の開発に回せています。個人開発の時間配分という意味でも、シンプルなものから始めるのは合理的です。
個人開発の初期段階こそ、SQLite のシンプルさが活きます。後から PostgreSQL に移行する場合も、Prisma などの ORM を使っておけばコード変更は最小限です。技術選定はトレンドではなく、自分のスケール要件で判断するのが合理的です。
個人開発のDB運用は技術ログ、技術選定の判断基準はビジネスカテゴリに記録しています。実際のメモリリーク事例 (sqlite 接続解放漏れ) は こちら もぜひ。
筆者: GRAMSHIFT — GramShift / saas-diary 開発者。本業 + 副業個人開発を 1 年継続中、SQLite + Fastify + Electron Desktop + Playwright のスタックで SaaS をフルスタック 1 人運用。SQLite は GramShift Web/Desktop の両方で本番採用。
同カテゴリの記事
個人開発SaaSの周辺トピックとして以下も参考になります。
よくある質問
SQLite の WAL モードは本当に必要か?
はい、Web アプリの本番運用なら必須レベルです。デフォルトの rollback journal モードだと、書き込み中の transaction が読み込みもブロックします。WAL モードに切り替えるだけで「読み込みは書き込みをブロックしない」「書き込み性能も若干向上」の両方が得られます。一度設定すれば DBファイル 単位で永続化されます。
SQLite で本当にユーザー数 100人を超えても大丈夫か?
ユーザー数というよりは「同時書き込み頻度」と「クエリの複雑さ」が指標です。Web アプリの典型的なアクセスパターン (読み多め、書き込みは数件/秒) なら、数千ユーザー規模でも SQLite で十分動きます。書き込み頻度が秒間 100 回を恒常的に超えるならボトルネックを意識し始める段階です。
PostgreSQL に後から移行するのは大変か?
スキーマと SQL がシンプルなら数日で移行可能です。Prisma などの ORM を使っていれば、provider を sqlite から postgresql に変えてマイグレーションを再生成するだけ。難しいのは SQLite 固有機能 (TEXT 型の柔軟さ、暗黙の型変換等) に依存していた場合で、ここを最小化するコーディング規約があると後で楽です。
バックアップは cp で十分か?
アプリ停止中の cp なら十分です。稼働中の場合は VACUUM INTO か SQLite 公式の online backup API を使ってください。cp だと書き込みの最中に取ると壊れたファイルになる可能性があります。VACUUM INTO は SQLite 3.27 以降で利用可能で、Linux のディストロパッケージなら基本的に新しい版が入っています。
better-sqlite3 と sqlite3 (node-sqlite3) はどちらが良いか?
個人開発の典型用途では better-sqlite3 を推奨します。同期 API なので Promise 地獄にならず、性能も sqlite3 (非同期 callback) より高速です。Node.js のシングルスレッドモデルで「DB 操作中はブロックされる」のは Web アプリだと問題に見えますが、SQLite のクエリは数 ms 以内に終わるのでブロック時間は実用上気にならないことが多いです。
libSQL / Turso への移行はどんなタイミングが目安か?
マルチリージョン対応が要件化したとき、または高可用性 (HA) が必要になったときが目安です。SQLite が動くアプリならコード変更は最小限で libSQL に移れますが、運用モデルがクラウドサービス前提になるので、課金とベンダーロックインのトレードオフを意識する必要があります。「将来 Turso に行く可能性がある」程度の温度感なら現時点で意識する必要は薄いです。