D1をやめてTursoに移行した話
はじめに
サイドプロジェクトでずっとD1を使用していましたが、最近色々と不便を感じたり、D1の仕様に限界を感じたため、Tursoに移行することにしました。
本記事のスコープ
本記事では、D1からTursoに移行するまでの作業をまとめたいと思います。
ただしデータの移行については割愛します。
前提条件
現在のプロジェクトでは下記の技術スタックを使用しています。
- 言語: TypeScript
- フレームワーク: Next.js
- DB: D1
- ORM: drizzle-orm
- Runtime: Bun
- Auth: NextAuth
- API: tRPC
そもそもなぜ移行しようと思ったのか
主に下記の理由で移行しようと思いました。
今後D1のアップデートにより改善される可能性もありますね。
- D1の最大サイズは10GBまでしかなく、ある程度の規模になるとマルチDB運用にしないといけない
- Worker以外からのアクセスは遅い
- AnalyticsはTursoの方が充実している
移行作業
1. Tursoの前準備
まずはTurso CLIをインストールします。
brew install tursodatabase/tap/turso
続いて、package.json
にdb関連の設定を変更します。
✴︎ここでついでにwrangler.toml
のd1_database
の設定も削除しておきます。
"scripts": {
"db:generate": "drizzle-kit generate",
- "db:migrate:local": "wrangler d1 migrations apply <db-name> --local",
- "db:migrate:prod": "wrangler d1 migrations apply <db-name> --remote",
- "db:studio:local": "LOCAL_DB_PATH=$(find .wrangler/state/v3/d1/miniflare-D1DatabaseObject -type f -name '*.sqlite' -print -quit) drizzle-kit studio",
- "db:studio:prod": "source .drizzle.env && DB_ID='db-id' drizzle-kit studio",
+ "db:migrate": "drizzle-kit migrate",
+ "db:studio": "drizzle-kit studio",
...
続いて、Turso CLIでログインして、 database credentialsを取得します。
✴︎事前にTursoコンソールでDBを作成していました。
# ブラウザで認証する
turso auth login
# DBのURLを取得する
turso db show --url <db-name>
# output: libsql://xxx.turso.io
# DBの認証トークンを取得する
turso db tokens create <db-name>
# output: eyJhb....
続いて、DB URLと認証トークンを.env
に追記します。
TURSO_DATABASE_URL=libsql://xxx.turso.io
TURSO_AUTH_TOKEN=eyJhb....
2. schemaについて
D1でもTursoでもdrizzle-orm/sqlite-coreを使用しているため、schemaはそのまま使用できます。
3. drizzle.config.tsの編集
下記のように、drizzle.config.ts
を編集します。
import type { Config } from "drizzle-kit";
- const { LOCAL_DB_PATH, DB_ID, D1_TOKEN, CF_ACCOUNT_ID } = process.env;
+ const { TURSO_DATABASE_URL, TURSO_AUTH_TOKEN } = process.env;
// Use better-sqlite driver for local development
export default {
schema: "./src/server/db/schema.ts",
out: "./migrations",
dialect: "sqlite",
- driver: "d1-http",
+ driver: "turso",
dbCredentials: {
- databaseId: DB_ID!,
- token: D1_TOKEN!,
- accountId: CF_ACCOUNT_ID!,
+ url: TURSO_DATABASE_URL!,
+ authToken: TURSO_AUTH_TOKEN!,
},
} satisfies Config;
4. connect 設定
connect関連の設定を編集します。
- import { getRequestContext } from "@cloudflare/next-on-pages";
- import { drizzle } from "drizzle-orm/d1";
- import * as schema from "./schema";
- const globalForDb = globalThis as unknown as {
- client?: D1Database;
- };
- export let client: D1Database | undefined;
- export const db = () => {
- client = globalForDb.client ?? getRequestContext().env.DB;
- if (env.NODE_ENV !== "production") globalForDb.client = client;
- return drizzle(client, { schema });
- };
+ import { drizzle } from "drizzle-orm/libsql";
+ import { createClient } from "@libsql/client";
+ import { env } from "@/env";
+ const turso = createClient({
+ url: env.TURSO_DATABASE_URL,
+ authToken: env.TURSO_AUTH_TOKEN,
+ });
+ export const db = drizzle(turso);
5. NextAuthの修正
NextAuthの修正を行います。
...
- import { drizzle } from "drizzle-orm/d1"
- import { getRequestContext } from "@cloudflare/next-on-pages"
+ import { db } from "@/server/db"
export const { handlers, auth, signIn, signOut } = NextAuth(() => {
- const db = drizzle(getRequestContext().env.DB)
return {
adapter: DrizzleAdapter(db, {
usersTable: users,
accountsTable: accounts,
sessionsTable: sessions,
verificationTokensTable: verificationTokens,
}),
session: { strategy: "jwt" },
providers: [GitHub, Google, Facebook, Twitter, Apple],
callbacks: {
session: async ({ session, token }) => {
session.user.id = token.id as string;
return session;
},
jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
},
}
})
6. tRPCの修正
tRPCの修正を行います。
...
import { db } from "@/server/db";
export const createTRPCContext = async (opts: { headers: Headers }) => {
const session = await auth();
return {
- db: db(),
+ db: db,
session,
ratelimit: unkey(),
...opts,
};
};
7. マイグレーション
マイグレーションできてから一度コンソール上で確認するか、bun run db:studio
で確認します。
bun run db:generate
bun run db:migrate
---
$ drizzle-kit migrate
No config path provided, using default 'drizzle.config.ts'
Reading config file 'xxxx/drizzle.config.ts'
[✓] migrations applied successfully!%
以上で全ての修正が完了しました。
困ったこと
queryの方法を変えないといけない
クライアントがdrizzle-orm/d1
-> drizzle-orm/libsql
に変わったため、以前のqueryの方法を変えないといけない。
例えば、下記のようにtursoの場合、ctx.db.query.xxx
のような方法ではなく、ctx.db.select().from(xxx)
のような方法でqueryする必要があります。
before:
export const userRouter = createTRPCRouter({
getById: protectedProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const user = await ctx.db.query.users.findFirst({
where: eq(users.id, input.id),
});
if (!user) throw new Error("User not found");
return user;
}),
...
after:
import { users } from "@/server/db/schema";
export const userRouter = createTRPCRouter({
getById: protectedProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const user = await ctx.db.select().from(users).where(eq(users.id, input.id))
if (!user) throw new Error("User not found");
return user;
}),
...
レイテンシーが期待よりは早くなっていない?
ローカルで検証したところ、レイテンシーが期待よりは早くなっていないように感じました。
大体200ms ~ 800msくらいでした。
まとめ
今回はD1からTursoに移行する手順をまとめました。
少しギャップもありましたが、移行自体はスムーズに行けてほぼ満足です。
今後D1のアップデートにより改善される可能性もあるので、期待しています。
Discussion