エッジデータベース完全比較ガイド - Turso、Neon、PlanetScale、D1、Upstashを徹底検証
エッジデータベース完全比較ガイド
エッジコンピューティングの普及により、グローバルに分散されたデータベースの重要性が高まっています。この記事では、主要なエッジデータベースサービスを徹底的に比較し、プロジェクトに最適な選択をサポートします。
エッジデータベースとは
従来のデータベースとの違い
従来のデータベース:
User (東京) → Application Server (東京) → Database (米国)
レイテンシ: 200-300ms
エッジデータベース:
User (東京) → Edge Server (東京) → Edge DB (東京)
レイテンシ: 10-30ms
エッジDBの特徴
- 低レイテンシ - ユーザーに近い場所にデータを配置
- グローバル分散 - 世界中にレプリカを配置
- 自動スケーリング - トラフィックに応じた自動拡張
- 従量課金 - 使った分だけの支払い
Turso(SQLite)
概要
TursoはlibSQLベースの分散SQLiteデータベースです。エッジでSQLiteを実行できる唯一のソリューションです。
主な特徴
// Turso クライアント
import { createClient } from '@libsql/client';
const client = createClient({
url: 'libsql://your-database.turso.io',
authToken: process.env.TURSO_AUTH_TOKEN,
});
// シンプルなクエリ
const result = await client.execute('SELECT * FROM users WHERE id = ?', [userId]);
// トランザクション
const batch = await client.batch([
{ sql: 'INSERT INTO users (name, email) VALUES (?, ?)', args: ['Alice', 'alice@example.com'] },
{ sql: 'INSERT INTO profiles (user_id, bio) VALUES (?, ?)', args: [1, 'Hello'] },
]);
// Prepared Statement(パフォーマンス向上)
const stmt = await client.prepare('SELECT * FROM users WHERE email = ?');
const users = await stmt.execute(['user@example.com']);
Drizzle ORMとの統合
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
driver: 'turso',
dbCredentials: {
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
},
});
// src/db/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { sql } from 'drizzle-orm';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: integer('created_at', { mode: 'timestamp' })
.default(sql`(unixepoch())`),
});
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
content: text('content').notNull(),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
published: integer('published', { mode: 'boolean' }).default(false),
createdAt: integer('created_at', { mode: 'timestamp' })
.default(sql`(unixepoch())`),
});
// src/db/client.ts
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import * as schema from './schema';
const client = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
});
export const db = drizzle(client, { schema });
使用例
// src/repositories/user.ts
import { db } from '../db/client';
import { users, posts } from '../db/schema';
import { eq, and, desc } from 'drizzle-orm';
export async function createUser(name: string, email: string) {
const [user] = await db.insert(users).values({ name, email }).returning();
return user;
}
export async function getUserWithPosts(userId: number) {
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
with: {
posts: {
where: eq(posts.published, true),
orderBy: desc(posts.createdAt),
},
},
});
return user;
}
export async function updateUser(userId: number, data: { name?: string; email?: string }) {
const [updated] = await db
.update(users)
.set(data)
.where(eq(users.id, userId))
.returning();
return updated;
}
価格
無料プラン:
- 3データベース
- 9GB ストレージ
- 月間500Mリード/書き込み
Pro ($29/月):
- 無制限データベース
- 無制限ストレージ
- 月間25Bリード/書き込み
メリット・デメリット
メリット:
- SQLiteの高速性とシンプルさ
- エッジでの実行
- 低コスト
- リレーショナルクエリ対応
デメリット:
- 複雑な集約クエリは苦手
- 大規模データには不向き
Neon(PostgreSQL)
概要
NeonはサーバーレスPostgreSQLです。分岐、瞬時のスケーリング、低コストが特徴です。
セットアップ
// @neondatabase/serverless
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// シンプルなクエリ
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
// パラメータ化クエリ
const posts = await sql`
SELECT p.*, u.name as author_name
FROM posts p
JOIN users u ON p.author_id = u.id
WHERE p.published = true
ORDER BY p.created_at DESC
LIMIT ${limit}
`;
// トランザクション
await sql.transaction([
sql`INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')`,
sql`INSERT INTO audit_log (action, details) VALUES ('user_created', 'Alice')`,
]);
Drizzle ORM統合
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
});
// src/db/schema.ts
import { pgTable, serial, text, timestamp, boolean, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content').notNull(),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
published: boolean('published').default(false),
viewCount: integer('view_count').default(0),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
// src/db/client.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
ブランチング機能
# Neon CLIでブランチ作成
neon branches create --name feature/new-schema
# 開発用ブランチのURL取得
neon connection-string feature/new-schema
# ブランチ削除
neon branches delete feature/new-schema
// 環境ごとに異なるブランチを使用
const getDatabaseUrl = () => {
if (process.env.NODE_ENV === 'production') {
return process.env.NEON_MAIN_URL;
}
if (process.env.BRANCH_NAME) {
return process.env[`NEON_BRANCH_${process.env.BRANCH_NAME}_URL`];
}
return process.env.NEON_DEV_URL;
};
const sql = neon(getDatabaseUrl()!);
価格
無料プラン:
- 0.5GB ストレージ
- 無制限コンピュート時間
Launch ($19/月):
- 10GB ストレージ
- 300時間コンピュート
Scale ($69/月):
- 50GB ストレージ
- 750時間コンピュート
メリット・デメリット
メリット:
- フルPostgreSQL互換
- ブランチング機能
- 瞬時のスケーリング
- 優れたDX
デメリット:
- Tursoより高価
- コールドスタートあり
PlanetScale(MySQL)
概要
PlanetScaleはサーバーレスMySQLです。Vitessベースで、水平スケーリングが容易です。
セットアップ
// @planetscale/database
import { connect } from '@planetscale/database';
const config = {
url: process.env.DATABASE_URL,
};
const conn = connect(config);
// クエリ実行
const results = await conn.execute('SELECT * FROM users WHERE id = ?', [userId]);
// トランザクション
await conn.transaction(async (tx) => {
await tx.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['Bob', 'bob@example.com']);
await tx.execute('INSERT INTO audit_log (action) VALUES (?)', ['user_created']);
});
Prisma統合
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma" // PlanetScale用
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
model Post {
id Int @id @default(autoincrement())
title String
content String @db.Text
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([published])
}
// src/db/client.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
ブランチング
# PlanetScale CLIでブランチ作成
pscale branch create my-database feature-branch
# ローカルでブランチに接続
pscale connect my-database feature-branch --port 3309
# デプロイリクエスト作成
pscale deploy-request create my-database feature-branch
# マージ
pscale deploy-request deploy my-database 1
価格
無料プラン:
- 1データベース
- 5GB ストレージ
- 10億行読み込み/月
Scaler ($29/月):
- 無制限データベース
- 10GB ストレージ
- 100億行読み込み/月
メリット・デメリット
メリット:
- MySQL互換
- 優れたスケーラビリティ
- 無制限接続
- ブランチング機能
デメリット:
- 外部キー制約なし(アプリ側で管理)
- PostgreSQL機能が使えない
Cloudflare D1(SQLite)
概要
Cloudflare D1はCloudflare Workers上で動作するSQLiteデータベースです。
セットアップ
// wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "production-db"
database_id = "xxxx-xxxx-xxxx"
// src/index.ts
export interface Env {
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// クエリ実行
const { results } = await env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(userId).all();
// バッチ実行
const batch = [
env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('Alice'),
env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind('Bob'),
];
await env.DB.batch(batch);
return Response.json({ users: results });
},
};
Drizzle ORM統合
// src/db/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
});
// src/index.ts
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './db/schema';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const db = drizzle(env.DB, { schema });
const users = await db.select().from(schema.users);
return Response.json({ users });
},
};
価格
無料プラン:
- 無制限データベース
- 500MB ストレージ
- 月間500万読み取り
- 月間10万書き込み
Paid:
- $0.75/GB ストレージ
- $0.001/百万読み取り
- $1.00/百万書き込み
メリット・デメリット
メリット:
- Cloudflare Workersと完全統合
- 無料枠が大きい
- グローバルレプリケーション
デメリット:
- Workers専用(他で使えない)
- 機能がまだ限定的
Upstash Redis
概要
Upstash Redisはサーバーレス対応のRedisです。キャッシュやセッション管理に最適です。
セットアップ
// @upstash/redis
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// 基本操作
await redis.set('user:1', { name: 'Alice', email: 'alice@example.com' });
const user = await redis.get('user:1');
// TTL設定
await redis.set('session:abc123', { userId: 1 }, { ex: 3600 }); // 1時間
// リスト操作
await redis.lpush('notifications:1', 'New message');
const notifications = await redis.lrange('notifications:1', 0, 9);
// セット操作
await redis.sadd('tags:post:1', 'javascript', 'typescript', 'react');
const tags = await redis.smembers('tags:post:1');
// ソートセット(ランキング)
await redis.zadd('leaderboard', { score: 100, member: 'user:1' });
await redis.zadd('leaderboard', { score: 85, member: 'user:2' });
const topUsers = await redis.zrange('leaderboard', 0, 9, { rev: true });
キャッシュパターン
// キャッシュアサイドパターン
async function getUser(id: string) {
// キャッシュを確認
const cached = await redis.get(`user:${id}`);
if (cached) {
return cached as User;
}
// DBから取得
const user = await db.query.users.findFirst({
where: eq(users.id, id),
});
if (user) {
// キャッシュに保存(1時間)
await redis.set(`user:${id}`, user, { ex: 3600 });
}
return user;
}
// キャッシュ無効化
async function updateUser(id: string, data: Partial<User>) {
const updated = await db
.update(users)
.set(data)
.where(eq(users.id, id))
.returning();
// キャッシュ削除
await redis.del(`user:${id}`);
return updated[0];
}
レート制限
// スライディングウィンドウレート制限
async function checkRateLimit(userId: string, limit = 10, window = 60) {
const key = `ratelimit:${userId}`;
const now = Date.now();
const windowStart = now - window * 1000;
// 古いエントリを削除
await redis.zremrangebyscore(key, 0, windowStart);
// 現在のカウント取得
const count = await redis.zcard(key);
if (count >= limit) {
return { allowed: false, remaining: 0 };
}
// 新しいリクエストを追加
await redis.zadd(key, { score: now, member: `${now}` });
await redis.expire(key, window);
return { allowed: true, remaining: limit - count - 1 };
}
価格
無料プラン:
- 10,000コマンド/日
- 256MB ストレージ
Pay as you go:
- $0.2/100,000コマンド
- $0.25/GB ストレージ
メリット・デメリット
メリット:
- 高速(インメモリ)
- RESTful API
- エッジでの実行
- 柔軟なデータ構造
デメリット:
- リレーショナルクエリ不可
- プライマリDBには不向き
比較表
機能 Turso Neon PlanetScale D1 Upstash
------------------------------------------------------------
DB種類 SQLite Postgres MySQL SQLite Redis
エッジ実行 ◎ ○ ○ ◎ ◎
スケーラビリティ ○ ◎ ◎ ○ ◎
無料プラン ◎ ○ ○ ◎ ○
ブランチング × ◎ ◎ × ×
フルテキスト検索 △ ◎ ○ △ △
複雑なクエリ △ ◎ ◎ △ ×
コールドスタート なし あり あり なし なし
料金 $ $$ $$ $ $
選択ガイド
Turso を選ぶべき場合
- エッジでの低レイテンシが重要
- シンプルなスキーマ
- コスト重視
- SQLiteの軽量性を活かしたい
Neon を選ぶべき場合
- PostgreSQL機能が必要
- 複雑なクエリを実行
- ブランチングワークフロー
- JSONBやフルテキスト検索を使用
PlanetScale を選ぶべき場合
- MySQL互換が必要
- 大規模スケーリング
- 水平分割が必要
- ブランチングワークフロー
D1 を選ぶべき場合
- Cloudflare Workers使用
- グローバル分散が重要
- コスト最小化
- シンプルなユースケース
Upstash Redis を選ぶべき場合
- キャッシング
- セッション管理
- リアルタイムランキング
- レート制限
まとめ
エッジデータベースは、グローバルアプリケーションに不可欠です。
推奨構成
小規模アプリ:
- Turso(メインDB) + Upstash(キャッシュ)
中規模アプリ:
- Neon(メインDB) + Upstash(キャッシュ)
大規模アプリ:
- PlanetScale(メインDB) + Upstash(キャッシュ)
Cloudflareエコシステム:
- D1(メインDB) + Upstash(キャッシュ)
プロジェクトの要件に応じて最適なエッジデータベースを選択しましょう。