一般社団法人 全国個人事業主支援協会

COLUMN コラム

  • SQLiteの再評価:サーバーレス時代に輝く組み込みデータベース

なぜ今SQLiteなのか

SQLiteはリリースから20年以上が経過した枯れた技術だが、近年になって再評価の機運が高まっている。その背景にはいくつかの要因がある。

まず、サーバーレスアーキテクチャの普及だ。AWS LambdaやCloudflare Workersのようなエッジコンピューティング環境では、外部のデータベースサーバーへの接続がレイテンシのボトルネックになる。SQLiteなら単一ファイルとしてデプロイでき、ネットワークレイテンシがゼロだ。

次に、LitestreamLiteFSといったレプリケーションツールの登場がある。これまでSQLiteの最大の弱点とされてきた「単一ノードでしか使えない」という制約が、これらのツールにより実質的に解消されつつある。

さらに、モバイルアプリやIoTデバイスの増加により、組み込みデータベースの需要そのものが拡大している。Android・iOSの両プラットフォームでSQLiteはデフォルトのデータベースエンジンとして採用されており、その信頼性は折り紙付きだ。

基本的な使い方とPythonでの統合

PythonにはSQLiteのバインディングが標準ライブラリとして組み込まれている。追加のインストールは不要だ。

import sqlite3
from contextlib import contextmanager
from pathlib import Path

DB_PATH = Path("app_data.db")

@contextmanager
def get_connection():
"""コネクション管理のコンテキストマネージャ"""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()

# テーブル作成
with get_connection() as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_articles_created
ON articles(created_at DESC)
""")

ポイントはPRAGMA journal_mode=WALの設定だ。WAL(Write-Ahead Logging)モードを有効にすることで、読み取りと書き込みの同時実行が可能になり、パフォーマンスが大幅に向上する。本番利用では必須の設定と言ってよい。

CRUD操作の実装

実務で使いやすいリポジトリパターンで実装してみよう。

class ArticleRepository:
def create(self, title: str, body: str) -> int:
with get_connection() as conn:
cursor = conn.execute(
"INSERT INTO articles (title, body) VALUES (?, ?)",
(title, body)
)
return cursor.lastrowid

def find_by_id(self, article_id: int) -> dict | None:
with get_connection() as conn:
row = conn.execute(
"SELECT * FROM articles WHERE id = ?",
(article_id,)
).fetchone()
return dict(row) if row else None

def find_recent(self, limit: int = 20) -> list[dict]:
with get_connection() as conn:
rows = conn.execute(
"SELECT * FROM articles ORDER BY created_at DESC LIMIT ?",
(limit,)
).fetchall()
return [dict(row) for row in rows]

def update(self, article_id: int, title: str, body: str) -> bool:
with get_connection() as conn:
cursor = conn.execute(
"""UPDATE articles
SET title = ?, body = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?""",
(title, body, article_id)
)
return cursor.rowcount > 0

パフォーマンスチューニング

SQLiteは適切にチューニングすれば、驚くほどのパフォーマンスを発揮する。筆者が実務で得た知見をいくつか共有する。

PRAGMAによる最適化は最も手軽で効果的なチューニング手法だ。

# 本番環境向けのPRAGMA設定
def configure_for_production(conn):
pragmas = {
"journal_mode": "WAL",
"synchronous": "NORMAL",
"cache_size": -64000, # 64MBのキャッシュ
"temp_store": "MEMORY",
"mmap_size": 268435456, # 256MBのメモリマップ
"page_size": 4096,
}
for key, value in pragmas.items():
conn.execute(f"PRAGMA {key}={value}")

synchronous=NORMALは、WALモードとの組み合わせでデータの安全性を保ちながら書き込み速度を向上させる。cache_sizeを負の値で指定するとキロバイト単位になる。頻繁に読み取られるデータがメモリに載るため、クエリのレスポンスが劇的に改善される。

全文検索の活用

SQLiteにはFTS5(Full-Text Search 5)という全文検索エンジンが組み込まれている。日本語の場合はトークナイザの工夫が必要だが、英数字のデータなら非常に簡単に利用できる。

# FTS5テーブルの作成と検索
conn.execute("""
CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts
USING fts5(title, body, content=articles, content_rowid=id)
""")

# 検索クエリ
results = conn.execute("""
SELECT a.* FROM articles_fts fts
JOIN articles a ON a.id = fts.rowid
WHERE articles_fts MATCH ?
ORDER BY rank
LIMIT 20
""", (search_query,)).fetchall()

Litestreamによるレプリケーション

Litestreamは、SQLiteのWALをS3互換ストレージに継続的にレプリケートするツールだ。これにより、SQLiteをプロダクション環境で安心して使えるようになる。設定は驚くほどシンプルだ。

バックアップの頻度、リストアの手順、障害時のフェイルオーバー戦略をあらかじめ設計しておくことが重要である。筆者のプロジェクトでは、S3へのレプリケーションに加えて、定期的なスナップショットバックアップも併用している。

SQLiteを選ぶべき場面と避けるべき場面

SQLiteが適しているのは以下のようなケースだ。

  • 月間アクティブユーザーが数万人程度のWebアプリケーション
  • 読み取り主体のワークロード
  • エッジコンピューティングやサーバーレス環境
  • プロトタイプや小〜中規模のプロダクション環境
  • 組み込みシステムやモバイルアプリ

一方、避けるべきケースもある。

  • 高頻度の同時書き込みが発生するシステム
  • 複数サーバーからの同時アクセスが必要な場合
  • テラバイト級のデータを扱う場合

重要なのは、SQLiteの制約を正しく理解したうえで選択することだ。「とりあえずPostgreSQL」という思考停止を脱し、要件に合致するならSQLiteを積極的に検討する価値がある。運用コストの削減効果は非常に大きい。

この記事をシェアする

  • Twitterでシェア
  • Facebookでシェア
  • LINEでシェア