開発はSQLite、本番はTurso ― 小中規模Webアプリのデータベース選定ガイド

#SQLite#Laravel#PHP#Web開発#受託開発
開発はSQLite、本番はTurso ― 小中規模Webアプリのデータベース選定ガイド

Webアプリケーションのデータベース選定において、SQLiteは長年にわたり「テスト環境用」や「モバイルアプリ向け」として扱われてきました。独立したサーバープロセスを持たず、単一のファイルとしてデータを管理する仕組みであり、本番環境のWebサービスで利用するには機能や性能が不足しているとみなされやすかったためです。

しかし、近年のハードウェア性能の向上や周辺ツールの充実により、小中規模のWebアプリケーションであれば本番環境でも十分に実用に耐えうるケースが増えています。事実、PHPの代表的なフレームワークであるLaravelは、バージョン11において新規プロジェクトのデフォルトデータベースをSQLiteに変更しました。これは、追加ツールの導入を不要にし、初期セットアップの摩擦を取り除いて動作検証を速やかに開始できる環境を標準で提供する設計判断の一環といえます。さらに、SQLite互換のマネージドデータベースであるTursoのようなサービスも登場し、「SQLiteで書いたアプリケーションを、そのままの形で本番運用に載せる」ための選択肢は着実に広がっています。

パーティーハード株式会社では、受託開発やAIソリューションの立ち上げにおいて、初期の開発スピードと検証サイクルの回転数を重視しています。新規事業のPoC(概念実証)やプロトタイプ開発では、最初から複雑なインフラを構築するよりも、検証に必要な最小限の構成で素早く市場の反応を確かめることが事業リスクの軽減につながるためです。その中でSQLiteは、インフラ構築と運用の初期投資を抑えつつ、検証速度を最大化するデータベースの選択肢として成立します。

SQLiteを採用する具体的なメリット

SQLiteを採用する第一の利点は、インフラの構築コストと運用管理コストを低く抑えられる点にあります。MySQLやPostgreSQLといった外部RDBMSを運用する場合、専用の仮想サーバーを用意するか、クラウド事業者が提供するマネージドサービスを契約する必要があります。これらは初期の固定費となるだけでなく、ネットワーク設定やアクセス権限、パッチ適用といった日々の保守運用のための工数が発生します。SQLiteであればアプリケーションサーバー内のディスクにファイルを配置するだけで動作するため、初期のインフラ費用と運用の手番を最小限に留められます。

第二の利点は、ファイルベース管理によるバックアップと環境運用の簡潔さです。すべてのデータとスキーマが単一のファイルに集約されているため、環境の複製やバックアップはファイルをコピーする操作だけで完了します。開発環境で作成した検証用データをステージング環境に持ち込む作業や、不具合調査のために本番データを安全な隔離環境へ退避させる作業も、外部ツールによるダンプやリストアの手順を経ずに実行できます。

第三の利点は、プロセス間通信やネットワーク通信に伴うオーバーヘッドが存在しないことです。外部のデータベースサーバーを参照する場合、どんなに高速なローカルネットワークであっても、TCP接続の確立やネットワークパケットの往復による遅延がミリ秒単位で加算されます。これに対してSQLiteは、アプリケーションの実行プロセスがディスク上のファイル(あるいはOSのページキャッシュ)へ直接アクセスするため、読み込み処理を高速に完了できます。フロントエンドにNext.jsやVueを採用したSPA(シングルページアプリケーション)構成において、画面の初期描画時に複数のコンポーネントから個別のAPIリクエストがサーバーへ並行して届き、バックエンドで集計クエリが同時に実行される場合でも、ローカルファイルへの直接アクセスによりサーバー内部の処理遅延を小さく抑えられます。

同時実行性の向上と障害復旧の設計

SQLiteを本番環境で安全に稼働させるためには、デフォルト設定のまま運用するのではなく、同時実行性を高めるための適切な設定が必要です。標準の動作モードでは、書き込み処理が実行されているあいだデータベース全体がロックされ、他の読み込み処理もブロックされてしまいます。この制約を緩和するために、WAL(Write-Ahead Logging)モードを明示的に有効化します。WALモードを設定すると、変更内容が別の一時ログファイルへ先に書き出される仕組みに切り替わるため、書き込み処理の最中であっても読み込み処理を並行して実行できます。あわせて同期レベル(synchronous)をNORMALに設定することで、OSクラッシュ時でもデータベースの整合性を保ちながら、ディスクへの同期書き込み頻度を減らして処理性能を向上させられます。

PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;

WALモード下であっても、書き込みトランザクションそのものは1件ずつ直列に実行される制約が残るため、更新が重複した際の待機時間を調整する必要があります。並行処理を安定させるためには、ビジータイムアウト(busy_timeout)の適切な調整が必要になります。デフォルトのタイムアウト設定が短すぎると、わずかな書き込みの重複でも「database is locked」エラーがクライアントに返ってしまいます。フレームワークの接続設定でタイムアウト時間を数秒程度確保し、待機によって書き込みを順次成立させる設計にしておく必要があります。

データの耐障害性を確保する手段としては、Litestreamのような継続的バックアップツールの導入が有効です。LitestreamはSQLiteのWALログを常時監視し、変更が発生するたびにAmazon S3などのオブジェクトストレージへ差分を非同期でレプリケーションします。平常時の同期遅延は1秒未満(サブセカンド)に抑えられており、万が一サーバーインスタンスが消失した場合でも最小限のデータ損失で復旧できます。ネットワークの一時的な瞬断が生じた場合でも、再接続時に未送信ログを順次転送するため、数秒から数十秒程度の遅延許容範囲内で安全に同期を継続できます。

開発はSQLite、本番はTursoという選択肢

ここまでは、本番サーバー上にSQLiteファイルを直接配置して運用する構成を前提としてきました。この構成はシンプルである反面、バックアップの仕組みを自前で用意する必要があり、アプリケーションサーバーを複数台に増やせないという制約も抱えています。こうした課題に対する中間的な解として、「開発環境ではローカルのSQLiteファイルを使い、本番環境ではTursoを使う」という構成が選択肢に加わります。

Tursoは、SQLiteをフォークしたオープンソースのデータベースであるlibSQLを基盤とするマネージドサービスです。libSQLはSQLiteとの互換性を保ちながら、HTTPプロトコルによるリモート接続やレプリケーション機能を追加しています。アプリケーション側にlibSQL対応のドライバやクライアントを導入した上で、開発環境ではローカルのSQLiteファイルを参照し、本番環境ではTurso上のデータベースへ接続先を切り替える構成を採れば、同じSQL方言・同じスキーマのまま稼働させられます。開発環境と本番環境でデータベースの種類が異なる「SQLiteで開発してMySQLで本番」という構成で起こりがちな、型の扱いや構文の差異による不具合を避けやすい点が大きな利点です。

運用面では、バックアップやストレージ管理をサービス側に委ねられるため、Litestreamのようなレプリケーションの仕組みを自前で構築・監視する必要がなくなります。また、データベースがアプリケーションサーバーの外に置かれることで、複数台のアプリケーションサーバーから同一のデータベースへ接続できるようになり、前述した「水平スケーリングできない」という制約を、外部RDBMSへ移行することなく緩和できます。

特徴的な機能として、Embedded Replicas(組み込みレプリカ)があります。これはアプリケーションサーバー内にローカルのSQLiteファイルをレプリカとして保持し、Turso上のプライマリデータベースと同期させる仕組みです。読み込みはローカルファイルに対して行われるため、SQLiteの強みであるネットワークを介さない高速な読み込みを維持しつつ、書き込みはプライマリへ送られて各レプリカへ反映されます。

ただし、この構成にもトレードオフがあります。第一に、Embedded Replicasを使わずにリモートのTursoへ直接クエリを発行する場合、ローカルSQLiteの利点であった「ネットワーク通信のオーバーヘッドがない」という性質は失われます。第二に、Embedded Replicasを使う場合も、他のレプリカへの反映は同期のタイミングに依存するため、サーバー間で厳密に最新のデータを参照する必要がある処理では整合性の扱いを設計段階で検討しておく必要があります。また、ファイルシステムを持たないサーバーレス環境ではEmbedded Replicasを利用できません。第三に、書き込みトランザクションはプライマリに集約されるため、書き込みのスループットは単一ノードの制限を受けます。さらに、PHPから利用する場合はlibSQL対応のクライアントライブラリやフレームワーク向けドライバの成熟度を事前に確認しておく必要があり、特定のマネージドサービスへの依存が生じる点も選定時に考慮すべき要素です。

外部RDBMS(MySQL/PostgreSQL)へ移行すべき境界線

SQLiteは単一ノードのローカルファイルとファイルロックに依存して動作する仕組みであるため、アーキテクチャの拡張やデータアクセスの性質によって技術的な限界を迎えます。具体的には、次の3つの条件のいずれかに直面した段階で、Tursoのような選択肢を含めて構成の見直しを検討する必要があります。

  1. 複数のアプリケーションサーバーによる水平スケーリングが必要になった場合

アクセス増加に対応するためWebサーバーを複数台に増やして負荷分散を行う場合、SQLiteのファイルを複数インスタンス間で安全に共有することは困難です。ネットワークファイルシステム(NFSなど)を経由したファイル配置は、ファイルロックの不整合や性能劣化を引き起こすため推奨されません。単一サーバーの垂直スケール(CPUやメモリの増強)で処理しきれなくなった段階が構成を切り替える節目となりますが、読み込み中心のワークロードであれば、外部RDBMSへ移行する前にTursoへ切り替えることでSQLite互換のまま複数台構成へ移行する道もあります。

  1. 書き込み頻度が高いワークロードへ移行した場合

読み込み処理の大半はキャッシュやWALモードで捌けるものの、書き込みトランザクションは直列に処理されます。目安として、秒間数十回を超える書き込みが持続的に発生するようなサービスでは、待機キューが滞留してレスポンス遅延やタイムアウトが発生しやすくなります。この課題はTursoに切り替えても書き込みがプライマリに集約されるため、制約は解消されません。予約の集中やリアルタイムチャットのように、短い時間に更新処理が集中する設計では最初から外部RDBMSを選択する判断が妥当です。

  1. 厳密な権限管理や、外部データ基盤との直接接続が求められる場合

SQLiteはファイル単位のアクセス権限しか持たないため、データベースユーザーごとに参照・更新権限を細かく分ける運用には対応していません。また、BIツールや外部の分析基盤から直接データベースへコネクションを張り、本番稼働中のサービスに負荷をかけずにレプリカを参照させるといった構成を組む場合も、MySQLやPostgreSQLが備えるレプリケーション機能や接続管理機能、そして周辺ツールとの豊富な連携実績が必要になります。

移行容易性を確保する設計と選定基準

サービスの立ち上げ期においては、過剰なインフラ構成を組むこと自体が開発スピードを鈍らせる要因になり得ます。SQLiteを採用してインフラ管理の負担を削ぎ落とし、まずは機能の実装とユーザー検証に集中する判断は、小中規模の開発において実用的なアプローチです。

将来のデータベース移行に対する懸念は、フレームワークの機能を活用することで軽減できます。PHPのLaravelやSymfonyが備えるマイグレーション機能やORM(Object-Relational Mapping)を利用してテーブル定義とデータアクセスを抽象化しておけば、特定のデータベース固有のSQL構文に依存しないコードベースを維持できます。ただし、実際の移行時にはアプリケーション層の修正を接続設定とスキーマ適用の変更に留められる一方で、稼働中の既存データを安全に抽出・変換して新環境へ流し込むデータ移行手順と、型の非互換性やインデックス動作の差分を検証する工数は別途発生します。この点で、SQLiteからTursoへの移行はSQL方言が共通しているため、外部RDBMSへの移行と比べて型変換やクエリ書き換えの負担を小さく抑えやすいといえます。

したがって初期フェーズでは、「ローカルのSQLiteで開発・検証を始める」「単一サーバーの限界や運用負荷が見えてきた段階でTursoへ切り替える」「書き込み負荷や権限管理の要件が高まった段階で外部RDBMSへ移行する」という段階的な道筋をあらかじめ描いておく形が合理的です。それぞれの段階で、トラフィック規模、書き込み頻度、整合性要件、運用体制を判断基準とし、次の段階へ進む条件を事前に織り込んで設計を進めることで、初期の開発スピードと将来の拡張性を両立できます。

望月 涼太 / polidog

望月 涼太 / polidog

Co-Founder / Web Engineer