1. 「分散病」に蝕まれる個人開発の現実

個人開発における最大の敵は「サーバーが落ちること」でも「アクセス殺到でダウンすること」でもありません。最も恐ろしい死因は、「最初のユーザーが付く前に、運用インフラの複雑さに開発者自身が疲れ果てて開発を放棄すること」です。

近年のクラウドサービスやサーバーレスプラットフォームの進化によって、個人でも手軽にマルチリージョン分散DBやマイクロサービス基盤をプロビジョニングできるようになりました。しかし、その甘美な誘惑に乗って「将来のスケールに備えて」と以下の構成を組んだ結果、何が起きるでしょうか。

これらをセットアップした瞬間、開発者はローカル開発環境の再現性に苦しみ、毎月の固定費と従量課金アラートに怯え、ネットワークレイテンシの遅延と分散トランザクションの例外処理コードで実装の大半を埋め尽くされることになります。

「推測するな、計測せよ。そして早すぎる最適化はすべての悪の根源である」
—— ドナルド・クヌース(Donald Knuth)

まだトラフィックの兆候すらない初期プロダクトに必要なのは、「1秒間に数万件のWriteを捌く分散データベース」ではありません。「仕様変更に数分で追従でき、月500円〜1,000円の固定費で無限に放置できる圧倒的な開発速度と運用の身軽さ」です。

2. 現代のハードウェアとSQLiteのパラダイムシフト

「でも、SQLiteはファイルベースの組み込みDBだから、Webアプリのような同時アクセスがある環境には向かないのでは?」

そうした疑問を持つ方は少なくありません。しかし、その認識の多くは「HDD時代」「デフォルト設定(Rollback Journalモード)」の記憶で止まっています。

NVMe SSDと「ローカルIPC」の暴力的な速さ

外部のマネージドデータベース(PostgreSQLやMySQL)に接続する場合、同一リージョン・同一ネットワーク内であってもTCP/IP接続やTLSハンドシェイク、パケット伝送により 1ms 〜 5ms 程度のラウンドトリップタイム(RTT)が必ず発生します。1つのHTTPリクエスト内でN+1クエリや複数のSELECTを実行すると、通信オーバーヘッドだけで数十ミリ秒が吹き飛びます。

対して、アプリケーションプロセスと同じメモリ空間・同一サーバーの高速NVMe SSD上で動作するSQLiteは、ネットワークスタックを一切介しません。

WAL(Write-Ahead Logging)モードの威力

SQLiteが「Webに向かない」と言われた最大の原因は、書き込み時にデータベースファイル全体をロックし、読込トランザクションすらブロックしていた過去の仕様(Rollback Journal)にあります。

しかし、WAL(Write-Ahead Logging)モードを有効化することで、この制約は劇的に解消されました:

唯一の制約は「同時に書き込めるスレッドは1つ(Single Writer)」である点ですが、現代のSSD上でのSQLiteのトランザクション完了時間は通常 1ミリ秒未満 です。1トランザクションが1msで完了するなら、理論上は1秒間に1,000回以上の書き込み(TPS)を単一スレッドで直列に捌ききれる計算になります。大半の個人開発サービスにおいて、秒間1,000件の書き込みトラフィックに達する日はまず来ません。

3. 実践:Go + SQLite による本番向けチューニング

それでは、プロダクション環境でSQLiteを運用する際の実践的な接続設計を見てみましょう。ここではCGO依存を排除してピュアGoでクロスコンパイル可能な modernc.org/sqlite(または mattn/go-sqlite3)を想定した、ベストプラクティスとなる接続初期化コードです。

package database

import (
    "context"
    "database/sql"
    "fmt"
    "time"

    _ "modernc.org/sqlite"
)

// NewProductionDB は本番運用に耐えうる最適化設定を施した *sql.DB を生成します
func NewProductionDB(dbPath string) (*sql.DB, error) {
    // 接続文字列にPRAGMAパラメータを埋め込む(ドライバ仕様に応じる)
    // busy_timeout: 書き込み競合時に即座にBUSYエラーを返さず5秒間リトライ待機
    dsn := fmt.Sprintf("%s?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)", dbPath)

    db, err := sql.Open("sqlite", dsn)
    if err != nil {
        return nil, fmt.Errorf("open sqlite db failed: %w", err)
    }

    // コネクションプールの設定
    // WALモード下ではReaderを並行実行できるため、接続プールを解放してスループットを向上
    db.SetMaxOpenConns(20)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(1 * time.Hour)
    db.SetConnMaxIdleTime(15 * time.Minute)

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    if err := db.PingContext(ctx); err != nil {
        return nil, fmt.Errorf("ping sqlite db failed: %w", err)
    }

    // 接続確立後の追加PRAGMAチューニング
    pragmas := []string{
        "PRAGMA cache_size = -64000;",      // 約64MBのメモリキャッシュを確保(負の値はKiB指定)
        "PRAGMA temp_store = MEMORY;",      // 一時テーブルやインデックスをメモリ上に配置
        "PRAGMA mmap_size = 30000000000;",  // メモリマップドI/Oを活用(OSページキャッシュを共有)
    }

    for _, p := range pragmas {
        if _, err := db.ExecContext(ctx, p); err != nil {
            return nil, fmt.Errorf("exec pragma '%s' failed: %w", p, err)
        }
    }

    return db, nil
}

チューニングの急所

4. 「単一サーバー運用の弱点」を解決するバックアップ戦略

1サーバー完結型の最大の懸念点は「ハードウェア障害やインスタンス消失時にデータが吹き飛ぶのではないか」という点です。単一サーバーであるがゆえに、単一障害点(SPOF)への恐怖が付きまといます。

これを解決するのが、モダンなSQLiteエコシステムの代名詞である Litestream(ライトストリーム) です。

Litestream によるRPOほぼゼロのS3レプリケーション

Litestreamは、バックグラウンドプロセスとして常駐し、SQLiteのWALファイルの変更フレームを検知してミリ秒単位でCloudflare R2やAWS S3などのオブジェクトストレージへ非同期ストリーミングします。

[ Go / Node.js App ] 
        │ (Read/Write)
        ▼
[ /data/app.db (WAL) ] ── (Litestream監視) ──▶ [ Cloudflare R2 / AWS S3 ] (RPO: 数秒以内)

これにより、「万が一VPSが物理的にクラッシュしても、新しいVPSを立ち上げてS3からDBファイルをpullするだけで数分で完全復旧できる」耐障害性を確保できます。

5. 1サーバー完結型がもたらす「精神的平穏」と開発速度

この構成の真の価値は、インフラコストの低さ以上に、「開発者の認知的負荷(Cognitive Load)が劇的に下がる」 点にあります。

すべてが手元にある快適さ

6. まとめ:複雑さを飼いならすな、最初から捨てるのだ

個人開発における成功の要諦は、アーキテクチャの華やかさではありません。「ユーザーが真に欲しがるものを、どれだけ速く作り、どれだけ長く生き残り続けられるか」です。

個人開発において「スケールしないこと」を恐れる必要はありません。
本当に恐れるべきは、「スケールを気にするあまり、誰にも使われないプロダクトの維持費と複雑さに押し潰されること」です。

月額数百円のVPS 1台 + Go / TypeScript の単一バイナリ + 最適化されたSQLite(WALモード) + Litestream によるS3レプリケーション。

この極小スタックは、数万〜数十万人のアクティブユーザーを抱えるまでびくともしません。次に新しいアイデアを形にするとき、巨大なクラウド構成図を描く前に、ぜひ「1つのサーバー、1つのプロセス、1つのファイル」から始めてみてください。その驚異的な身軽さに、きっと開発の原初の喜びを思い出すはずです。