Connection Limitエラーにさようなら:TursoとLibSQLでエッジデータベースを構築する

Database tutorial - IT technology blog
Database tutorial - IT technology blog

午前2時の電話と従来のデータベースの限界

午前2時ちょうど、私の電話が激しく震えた。システムが大量の「504 Gateway Timeout」エラーを報告している。Vercelのログを確認すると、ETIMEDOUTエラーがずらりと並んでいた。問題はコードのロジックではなく、シンガポールにあるPostgreSQLインスタンスが、世界中に分散したEdge Runtimeからの急激な接続増加に耐えきれず「悲鳴」を上げていたのだ。

その時、Serverlessシステムで中央集権的なデータベースを使用する代償を痛感した。Lambda関数が起動(Cold Start)するたびに、新しいTCP接続を確立しなければならない。SSL/TLSのオーバーヘッドも加わり、レイテンシ(遅延)は許容できないレベルまで跳ね上がった。Connection Poolをアップグレード(pgpool-IIによるPostgreSQLの最適化など)してみたが、AWSの請求額が月に50ドル以上増えただけで、パフォーマンスは依然として低迷したままだった。そこで出会ったのがTursoLibSQLだ。

Serverless向けデータベース選びで陥りやすい3つの「落とし穴」

最終的な案に決める前に、私は3つのアプローチを試した。それぞれがEdge環境において致命的な欠点を持っていた:

1. 従来のSQL (PostgreSQL/MySQL)

  • 問題点: Connection limit(接続制限)が最大の苦しみとなる。接続を管理するためにPgBouncerやAWS RDS Proxyに追加料金を払わざるを得ない。ユーザーがアメリカにいてデータベースがシンガポールにある場合、300msのレイテンシは当たり前だ。

2. NoSQL (DynamoDB/MongoDB Atlas)

  • 問題点: データモデリングを一から学び直す必要がある。複雑なクエリやJOINは至難の業だ。従来のSQLなら5分で済むクエリロジックを書き直すためだけに、丸3日を費やしたこともある。

3. ローカルのSQLite

  • 問題点: 高速だがデータを共有できない。Serverlessはステートレスだ。関数が実行されるたびに空のSQLiteファイルが作成され、古いデータは完全に消えてしまう。

Tursoは完璧なパズルの一片として登場した: SQLiteを本番環境へ導入する際の課題を解決し、そのパワーをクラウドにもたらす。Tursoは世界中の数十のロケーションへのレプリケーションをサポートし、TCPの代わりにHTTPプロトコルを使用して接続のボトルネックを完全に解消する。

なぜTursoとLibSQLは投資する価値があるのか?

LibSQLは、Tursoチームによって最適化されたSQLiteのオープンソースフォークだ。実際のベンチマークを経て、プロジェクト全体をTursoに移行すると決めた3つの理由を挙げる:

  • 超高速なレスポンス: Tursoはユーザーに最も近い場所にレプリカ(replicas)を作成できる。ロンドンのユーザーは、地球を半周する信号を待つことなく、ロンドンで直接クエリを実行できる。
  • HTTP/WebSocketsプロトコル: LibSQLクライアントは重いTCP接続を維持しない。これは、VercelやCloudflare Workers上で数秒しか生存しない関数に非常に適している。
  • 開発者体験: TursoのCLIは非常にスムーズだ。本番環境で使用可能なデータベースを初期化するのに、30秒もかからなかった。

Node.jsプロジェクトへのTurso導入ガイド

以下は、Tursoデータベースをセットアップし、Node.js & TypeScriptでPrisma ORMを利用する際などにも役立つ、TypeScriptアプリケーションへの接続手順だ。

ステップ1:CLIによるデータベースの初期化

まず、CLIをインストールする。macOSを使用している場合は、Brewを使うのが早い:

brew install tursodatabase/tap/turso
turso auth signup
turso db create my-awesome-app

このコマンドを実行すると、libsql://my-awesome-app-user.turso.ioのようなURLが返される。これを保存しておこう。

ステップ2:Auth Tokenによるセキュリティ設定

データベースを誰にでも公開したままにしてはいけない。アクセス用トークンを作成する必要がある:

turso db tokens create my-awesome-app

ステップ3:コードからの接続

LibSQLの公式ドライバーをインストールする:

npm install @libsql/client dotenv

以下は、私がデータを操作する際によく書くスクリプトだ。SQLiteを使ったことがあれば、非常に馴染みのある構文だろう:

import { createClient } from "@libsql/client";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

async function initUser() {
  // データベースの操作
  await client.execute(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT
    )
  `);

  await client.execute({
    sql: "INSERT INTO users (name) VALUES (?)",
    args: ["DevPro"],
  });

  const rs = await client.execute("SELECT * FROM users");
  console.table(rs.rows);
}

実践的な経験:Tursoが適さないケースとは?

Tursoは非常に強力だが、「万能」ではない。しばらく本番環境で運用してみて、注意すべき2つの点が見えてきた。

第一に、SQLiteにはPostgreSQLのJSONBのような高度なデータ型が不足している。アプリがPostGISや複雑なJSONクエリに大きく依存している場合、Tursoへの移行によりアプリケーション層のコードが肥大化する可能性がある。

第二に、Single-Primaryメカニズムだ。すべての書き込み操作は、依然としてメインリージョンに送信される必要がある。世界中から頻繁に書き込みが行われるアプリの場合、書き込みコマンドの遅延は依然として残る(これはCitusでPostgreSQLを「無制限」に拡張しても直面する物理的な制約だ)。Tursoが真に輝くのは、Read-heavy(読み取りが多く、書き込みが少ない)なアプリだ。

最適化の秘策:Embedded Replicas

これは私が最も気に入っている機能だ。データベースのレプリカを、アプリが動作しているサーバーのハードディスクに直接同期できる。そうすれば、SELECT文はローカル速度(レイテンシほぼ0ms)で実行される。ドライバーがバックグラウンドで自動的にデータを同期してくれる。

const client = createClient({
  url: "file:local.db",
  syncUrl: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

// クラウドからローカルへデータを同期する
await client.sync();

おわりに

今日のServerless向けデータベース選びは、単にSQLかNoSQLかを選ぶだけではない。最も重要なのはData Proximity(データの近接性)、つまりデータをいかにユーザーの近くに配置するかだ。TursoとLibSQLは、SQLのシンプルさを維持しながら、この点を非常にうまく実現している。Atlas (Ariga)によるスキーマ管理などと組み合わせ、肥大化したデータベースクラスターの設定に疲れているなら、ぜひTursoを試して、そのパフォーマンスの違いを即座に体感してほしい。

Share: