🪛
Drizzle and Supabase on Cloudflare Workersでは、DBのコネクションを使い回すことができない
Drizzle and Supabase on Cloudflare Workersな構成でハマった所をまとめました。
DBのコネクションオブジェクトを再利用できない
当初は次のようにモジュールレベルで、コネクションを作成し、各ハンドラーの中で再利用するやり方をしていました。
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
export const client = postgres(process.env.DATABASE_URL, { prepare: false });
export const db = drizzle(client, { schema });
しかし、これだとサーバーを立ち上げた2回目のリクエストに次のエラーが発生します。
Error: Cannot perform I/O on behalf of a different request. I/O objects (such as streams, request/response bodies, and others) created in the context of one request handler cannot be accessed from a different request's handler. This is a limitation of Cloudflare Workers which allows us to improve overall performance.
これはCloudflare Workersがエッジ環境で動きサーバーレスであり、リクエストごとに異なる環境で実行されることがあるため、I/Oに関わるオブジェクトを異なるリクエストで再利用することは禁止されていることに起因するエラーです。
解決策はリクエストごとにDBクライアントを作成すること
先ほどのようにモジュールレベルで初期化したDBクライアントを再利用するのではなく、次のようにDBクライアントを作成する関数を作成し、リクエストの度にその関数を使ってDBクライアントを都度作成することで解決することができます。
// drizzle.ts
export function createDBClient(database_url: string) {
const connectionString = database_url;
// Disable prefetch as it is not supported for "Transaction" pool mode
const client = postgres(connectionString, { prepare: false });
return drizzle(client, { schema });
}
// app.ts
import { Hono } from "hono";
const app = new Hono()
app.get("/", async (c) => {
const db = createDBClient(c.env.DATABASE_URL);
const examplesList = await db.select().from(example);
return c.json(examplesList);
})
export default app
このやり方ではリクエストの度にコネクションを再作成することになる?ので、パフォーマンス(レスポンスタイムなど)が悪化してしまうのではないか? が気になりました。
SupabaseにはSupervisorというコネクションプール機能があるため、DB側の問題はないはずと考えています。
しかし実際の所、本当に問題ないかどうかはこれから検証したいです。
Supervisorとの接続部分で、結局遅くなってしまうのでは?という懸念あるので、もしダメそうならCloudflare Hyperdriveを試します。
こちらであれば、Cloudflare Workersと密に結合されているため通信部分での懸念がありません。
参考資料
参考にした資料を再掲します。
Discussion