SQLite再評価の背景
2025年、SQLiteは単なる「組み込みデータベース」から「分散エッジデータベースの基盤」へと大きく進化しました。従来、SQLiteはモバイルアプリやデスクトップアプリケーションのローカルストレージとして使われてきましたが、エッジコンピューティングとサーバーレスの台頭により、その軽量性と信頼性が再評価されています。
なぜSQLiteが注目されているのか
SQLiteの強み:
├── 単一ファイル構成(設定不要)
├── ゼロ設定・ゼロメンテナンス
├── 高い信頼性(航空機・スマホで実績)
├── 読み取り性能が極めて高速
├── フットプリントが小さい(数MB)
└── SQL標準準拠
PostgreSQLやMySQLなどのクライアントサーバー型データベースと異なり、SQLiteはアプリケーションに直接埋め込まれるため、ネットワークレイテンシが発生しません。この特性がエッジ環境で非常に有効であることが認識され始めました。
2025年のSQLite利用状況
推定デバイス数: 1兆台以上(スマートフォン、IoT、ブラウザ等)
採用企業・プロダクト:
- すべてのiPhone/iPadアプリ
- すべてのAndroidアプリ
- Chrome/Firefox/Safari(履歴・キャッシュ)
- WhatsApp、Skype、iTunes
- Airbus A350のフライトシステム
Turso/libSQLの特徴
libSQLとは
libSQLは、SQLiteの公式フォークとして2022年に誕生したオープンソースプロジェクトです。SQLiteはパブリックドメインですが、外部コントリビューションを受け付けないポリシーがあります。libSQLはこの制限を取り払い、コミュニティドリブンな開発を可能にしました。
libSQLの追加機能:
├── ネイティブレプリケーション
├── WebSocket/HTTPアクセス
├── ALTER TABLE拡張
├── 暗号化(at-rest)
├── ベクトル検索(実験的)
└── WAL(Write-Ahead Logging)改善
Tursoプラットフォーム
Tursoは、libSQLをベースにしたマネージドエッジデータベースサービスです。世界中のエッジロケーションにデータをレプリケートし、ユーザーに最も近い場所からデータを提供します。
// Tursoクライアントの基本設定
import { createClient } from '@libsql/client';
const db = createClient({
url: 'libsql://my-database-org.turso.io',
authToken: process.env.TURSO_AUTH_TOKEN,
});
// シンプルなクエリ実行
const users = await db.execute('SELECT * FROM users WHERE active = 1');
console.log(users.rows);
// プリペアドステートメント
const result = await db.execute({
sql: 'INSERT INTO posts (title, content, author_id) VALUES (?, ?, ?)',
args: ['SQLite 2025', 'エッジDBの新時代', 42]
});
console.log(`Inserted row with ID: ${result.lastInsertRowid}`);
Embedded Replicas(組み込みレプリカ)
Tursoの最も革新的な機能が「Embedded Replicas」です。アプリケーション内にSQLiteのローカルコピーを持ち、読み取りはローカルから、書き込みはリモートへという構成を実現します。
import { createClient } from '@libsql/client';
// Embedded Replicasの設定
const client = createClient({
url: 'file:./local-replica.db', // ローカルSQLiteファイル
syncUrl: 'libsql://db-org.turso.io', // リモートプライマリ
authToken: process.env.TURSO_AUTH_TOKEN,
syncInterval: 30, // 30秒ごとに自動同期
});
// 読み取り: ローカルレプリカから(<1ms)
const products = await client.execute(
'SELECT * FROM products WHERE category = ?',
['electronics']
);
// 書き込み: リモートへ自動転送
await client.execute({
sql: 'UPDATE products SET stock = stock - 1 WHERE id = ?',
args: [productId]
});
// 明示的な同期
await client.sync();
レイテンシ比較:
| 操作 | 従来のリモートDB | Embedded Replicas |
|---|---|---|
| 読み取り | 50-200ms | 0.1-1ms |
| 書き込み | 50-200ms | 50-200ms |
| ローカルキャッシュヒット | N/A | 0.01ms |
LiteFSによるレプリケーション
LiteFSとは
LiteFSは、Fly.ioが開発したSQLite向けの分散ファイルシステムです。FUSEを使用してSQLiteファイルへのアクセスを透過的にインターセプトし、複数ノード間でデータをレプリケートします。
graph TB
subgraph Primary["Primary Node"]
SQLite["SQLite DB"] <--> LiteFS["LiteFS<br/>(FUSE Mount)"]
end
LiteFS -->|WAL Stream| R1["Replica #1<br/>(Tokyo)"]
LiteFS -->|WAL Stream| R2["Replica #2<br/>(Singapore)"]
LiteFS -->|WAL Stream| R3["Replica #3<br/>(Frankfurt)"]
LiteFSの設定
# litefs.yml
fuse:
dir: "/litefs" # マウントポイント
allow-other: true
data:
dir: "/var/lib/litefs" # 内部データ
exec:
- cmd: "npm run start" # アプリケーション起動
lease:
type: "consul"
advertise-url: "http://${HOSTNAME}.internal:20202"
consul:
url: "${CONSUL_URL}"
key: "litefs/primary"
proxy:
addr: ":8080"
target: "localhost:3000"
db: "my_app.db"
passthrough:
- "*.css"
- "*.js"
Fly.ioでのデプロイ
# fly.tomlの設定
# [mounts]
# source = "litefs"
# destination = "/var/lib/litefs"
# Dockerfileでの設定
FROM node:20-alpine
# LiteFSのインストール
COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs
# アプリケーションのコピー
WORKDIR /app
COPY . .
RUN npm install
# LiteFS経由で起動
ENTRYPOINT ["litefs", "mount"]
LiteFSの利点と制限
利点:
├── アプリケーションコードの変更不要
├── 既存SQLiteアプリをそのまま分散化
├── 自動フェイルオーバー
├── 強い一貫性(プライマリ書き込み)
└── WALベースの効率的レプリケーション
制限:
├── 単一ライター(プライマリのみ書き込み可)
├── Linux FUSE依存
├── 書き込みはプライマリへのプロキシ必要
└── 大規模書き込みには不向き
Cloudflare D1
D1の概要
Cloudflare D1は、Cloudflareのエッジネットワーク上で動作するサーバーレスSQLiteデータベースです。Workers KVやDurable Objectsと並ぶCloudflareのデータストレージオプションとして、2023年にGA(一般提供)されました。
// Cloudflare Workers + D1の例
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/api/users') {
// D1クエリ
const { results } = await env.DB.prepare(
'SELECT id, name, email FROM users WHERE active = ?'
).bind(1).all();
return Response.json(results);
}
if (url.pathname === '/api/users' && request.method === 'POST') {
const { name, email } = await request.json();
const result = await env.DB.prepare(
'INSERT INTO users (name, email, active) VALUES (?, ?, 1) RETURNING id'
).bind(name, email).first();
return Response.json({ id: result.id }, { status: 201 });
}
return new Response('Not Found', { status: 404 });
}
};
wrangler.tomlの設定
name = "my-d1-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "production-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
D1のマイグレーション
# マイグレーションファイルの作成
mkdir -p migrations
# migrations/0001_initial.sql
cat << 'EOF' > migrations/0001_initial.sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
active INTEGER DEFAULT 1,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_active ON users(active);
EOF
# マイグレーション実行(ローカル)
npx wrangler d1 migrations apply my-db --local
# マイグレーション実行(本番)
npx wrangler d1 migrations apply my-db --remote
D1 vs Turso比較
| 項目 | Cloudflare D1 | Turso |
|---|---|---|
| 基盤 | SQLite | libSQL (SQLite fork) |
| エッジロケーション | Cloudflareネットワーク | 35+ロケーション |
| Embedded Replicas | なし | あり |
| ORM統合 | Drizzle, Prisma | Drizzle, Prisma, Kysely |
| ベクトル検索 | なし | 実験的サポート |
| 無料枠 | 5GB/月 | 9GB, 5億読み取り |
| ユースケース | Cloudflare Workers専用 | マルチプラットフォーム |
エッジデータベースとしての活用
ユースケース別アーキテクチャ
1. グローバルEコマース
// Next.js + Turso + Embedded Replicas
// lib/db.ts
import { createClient } from '@libsql/client';
const isEdge = process.env.VERCEL_ENV === 'production';
export const db = createClient(isEdge ? {
url: 'file:./replica.db',
syncUrl: process.env.TURSO_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
syncInterval: 60,
} : {
url: process.env.TURSO_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
});
// app/api/products/route.ts
import { db } from '@/lib/db';
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const category = searchParams.get('category');
// ローカルレプリカから高速読み取り
const products = await db.execute({
sql: `
SELECT p.*, c.name as category_name
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE c.slug = ?
ORDER BY p.popularity DESC
LIMIT 50
`,
args: [category]
});
return Response.json(products.rows);
}
2. リアルタイムアナリティクス
// Hono + D1 アナリティクスAPI
import { Hono } from 'hono';
type Bindings = {
DB: D1Database;
ANALYTICS: AnalyticsEngineDataset;
};
const app = new Hono<{ Bindings: Bindings }>();
// ページビュー記録
app.post('/track', async (c) => {
const { page, referrer, userAgent } = await c.req.json();
await c.env.DB.prepare(`
INSERT INTO page_views (page, referrer, user_agent, timestamp)
VALUES (?, ?, ?, datetime('now'))
`).bind(page, referrer, userAgent).run();
return c.json({ success: true });
});
// 日別統計取得
app.get('/stats/daily', async (c) => {
const { results } = await c.env.DB.prepare(`
SELECT
date(timestamp) as date,
COUNT(*) as views,
COUNT(DISTINCT page) as unique_pages
FROM page_views
WHERE timestamp >= date('now', '-30 days')
GROUP BY date(timestamp)
ORDER BY date DESC
`).all();
return c.json(results);
});
export default app;
3. マルチテナントSaaS
// テナントごとに分離されたデータベース
import { createClient, Client } from '@libsql/client';
class TenantDatabase {
private clients: Map<string, Client> = new Map();
async getClient(tenantId: string): Promise<Client> {
if (this.clients.has(tenantId)) {
return this.clients.get(tenantId)!;
}
// テナントごとに個別のTursoデータベース
const client = createClient({
url: `libsql://${tenantId}-myapp.turso.io`,
authToken: process.env.TURSO_AUTH_TOKEN,
});
this.clients.set(tenantId, client);
return client;
}
async query(tenantId: string, sql: string, args?: any[]) {
const client = await this.getClient(tenantId);
return client.execute({ sql, args: args || [] });
}
}
// 使用例
const tenantDb = new TenantDatabase();
export async function getTenantUsers(tenantId: string) {
return tenantDb.query(
tenantId,
'SELECT * FROM users WHERE deleted_at IS NULL'
);
}
実践的なコード例
Drizzle ORM + Turso
// schema.ts
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
email: text('email').notNull().unique(),
name: text('name').notNull(),
role: text('role', { enum: ['user', 'admin'] }).default('user'),
createdAt: integer('created_at', { mode: 'timestamp' })
.$defaultFn(() => new Date()),
});
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
content: text('content').notNull(),
authorId: integer('author_id').references(() => users.id),
publishedAt: integer('published_at', { mode: 'timestamp' }),
});
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
// db.ts
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import * as schema from './schema';
const client = createClient({
url: process.env.TURSO_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
});
export const db = drizzle(client, { schema });
// 使用例: queries.ts
import { eq, desc, and, isNotNull } from 'drizzle-orm';
import { db } from './db';
import { users, posts } from './schema';
// ユーザーと投稿を一括取得
export async function getUserWithPosts(userId: number) {
return db.query.users.findFirst({
where: eq(users.id, userId),
with: {
posts: {
where: isNotNull(posts.publishedAt),
orderBy: desc(posts.publishedAt),
limit: 10,
},
},
});
}
// トランザクション
export async function createUserWithFirstPost(
userData: typeof users.$inferInsert,
postData: Omit<typeof posts.$inferInsert, 'authorId'>
) {
return db.transaction(async (tx) => {
const [user] = await tx.insert(users).values(userData).returning();
const [post] = await tx.insert(posts).values({
...postData,
authorId: user.id,
}).returning();
return { user, post };
});
}
バッチ処理とトランザクション
// Tursoのバッチ実行
import { createClient } from '@libsql/client';
const client = createClient({
url: process.env.TURSO_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
});
// 複数クエリをバッチ実行(1回のラウンドトリップ)
async function batchInsertProducts(products: Product[]) {
const statements = products.map(product => ({
sql: 'INSERT INTO products (name, price, stock) VALUES (?, ?, ?)',
args: [product.name, product.price, product.stock]
}));
// バッチ実行
const results = await client.batch(statements, 'write');
return results.map((r, i) => ({
...products[i],
id: r.lastInsertRowid
}));
}
// インタラクティブトランザクション
async function transferStock(
fromProductId: number,
toProductId: number,
quantity: number
) {
const tx = await client.transaction('write');
try {
// 在庫確認
const [fromProduct] = (await tx.execute({
sql: 'SELECT stock FROM products WHERE id = ?',
args: [fromProductId]
})).rows;
if (fromProduct.stock < quantity) {
await tx.rollback();
throw new Error('Insufficient stock');
}
// 在庫移動
await tx.execute({
sql: 'UPDATE products SET stock = stock - ? WHERE id = ?',
args: [quantity, fromProductId]
});
await tx.execute({
sql: 'UPDATE products SET stock = stock + ? WHERE id = ?',
args: [quantity, toProductId]
});
await tx.commit();
return { success: true };
} catch (error) {
await tx.rollback();
throw error;
}
}
2025年のエコシステム
主要プレイヤー比較
| サービス | 特徴 | 適したユースケース |
|---|---|---|
| Turso | libSQLベース、Embedded Replicas | マルチプラットフォーム、低レイテンシ |
| Cloudflare D1 | Workers統合、サーバーレス | Cloudflareエコシステム |
| LiteFS | FUSE、既存アプリ対応 | Fly.io、既存SQLiteアプリ |
| PlanetScale | MySQL互換、ブランチ | スケーラブルなMySQL |
| Neon | PostgreSQL、ブランチ | PostgreSQL互換が必要 |
2025年のトレンド
分散SQLiteエコシステムの成熟:
├── ベクトル検索統合(sqlite-vss, libSQL vectors)
├── マルチライター対応への進化
├── より多くのエッジロケーション
├── ORM/フレームワーク統合の充実
└── コスト効率の向上
採用企業の拡大:
├── Vercelでの公式サポート
├── Supabaseの実験的対応
├── Railway、Render等での対応
└── エンタープライズ採用の増加
選定ガイドライン
// プロジェクトに最適なSQLiteソリューション選定
function selectSQLiteSolution(requirements: Requirements): string {
if (requirements.platform === 'cloudflare-workers') {
return 'Cloudflare D1';
}
if (requirements.needsEmbeddedReplicas) {
return 'Turso';
}
if (requirements.existingSQLiteApp && requirements.platform === 'fly-io') {
return 'LiteFS';
}
if (requirements.needsVectorSearch) {
return 'Turso (with libSQL vectors)';
}
if (requirements.multiRegion && requirements.lowLatencyReads) {
return 'Turso with Embedded Replicas';
}
return 'Standard SQLite (local)';
}
まとめ
2025年、SQLiteは「ローカル専用の軽量データベース」から「分散エッジデータベースの基盤」へと進化しました。Turso/libSQL、LiteFS、Cloudflare D1といったソリューションにより、SQLiteの軽量性・信頼性を維持しながら、グローバル規模のアプリケーションを構築できるようになっています。
SQLiteを選ぶべき理由:
- シンプルさ: 設定不要、単一ファイル
- パフォーマンス: ネットワークレイテンシなしの読み取り
- 信頼性: 航空機からスマートフォンまで実績
- コスト効率: 軽量で運用コストが低い
- エコシステム: Drizzle、Prismaなど主要ORMが対応
エッジコンピューティングとサーバーレスが主流となる中、SQLiteベースの分散データベースは今後さらに重要性を増していくでしょう。プロジェクトの要件に応じて、Turso、D1、LiteFSを使い分け、最適なアーキテクチャを構築することが推奨されます。
← 一覧に戻る