SQLite 2025 - Turso・LiteFSで分散データベースへ進化

2026.01.12

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();

レイテンシ比較:

操作従来のリモートDBEmbedded Replicas
読み取り50-200ms0.1-1ms
書き込み50-200ms50-200ms
ローカルキャッシュヒットN/A0.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 D1Turso
基盤SQLitelibSQL (SQLite fork)
エッジロケーションCloudflareネットワーク35+ロケーション
Embedded Replicasなしあり
ORM統合Drizzle, PrismaDrizzle, 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年のエコシステム

主要プレイヤー比較

サービス特徴適したユースケース
TursolibSQLベース、Embedded Replicasマルチプラットフォーム、低レイテンシ
Cloudflare D1Workers統合、サーバーレスCloudflareエコシステム
LiteFSFUSE、既存アプリ対応Fly.io、既存SQLiteアプリ
PlanetScaleMySQL互換、ブランチスケーラブルなMySQL
NeonPostgreSQL、ブランチ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を使い分け、最適なアーキテクチャを構築することが推奨されます。

この技術を体系的に学びたいですか?

未来学では東証プライム上場企業のITエンジニアが24時間サポート。月額24,800円から、退会金0円のオンラインIT塾です。

LINEで無料相談する
← 一覧に戻る