TursoとlibSQL:分散データベースをエッジへスケールさせる

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

モダンなサーバーレススタックにおけるレイテンシのボトルネック

最近、クライアントのAPIを標準的なVPSからVercel Edge Functionsに移行しました。理論的には、グローバルな分散配置と1ミリ秒未満のコールドスタートという、完璧なセットアップでした。しかし、現実は甘くありませんでした。us-east-1にあるPostgreSQLインスタンスというデータベースが、「エッジ」の利点を台無しにしてしまったのです。シンガポールのユーザーは、バージニアに到達するためだけに300ミリ秒の往復遅延に直面しました。光の速さは、克服しがたいボトルネックなのです。

これには構造的な障壁があります。ほとんどのデータベースはモノリシックです。リードレプリカがあっても、サーバーレス環境でグローバルな状態管理やコネクションプーリングを行うのは困難です。サーバーレス関数は短命(エフェメラル)です。TCPコネクションを長時間維持するのが苦手なのです。その結果、データベースがデータ提供よりもハンドシェイクにリソースを費やしてしまう「コネクション枯渇」を引き起こします。

だからこそ、私はTurso and libSQLに目を向けました。これらはデータをグローバルに分散された資産として扱い、可能な限りコンピューティング層の近くに配置します。

クイックスタート:5分で稼働させる

Tursoを初めて使うなら、実際に動かしてみるのが一番の理解への近道です。Tursoは、モダンなWeb向けに設計されたSQLiteのオープンソースフォークであるlibSQLをベースとしたマネージドプラットフォームです。

1. Turso CLIのインストール

ターミナルを開き、インストールスクリプトを実行します:

curl -sSfL https://get.turso.io/install.sh | bash

インストールが完了したら、アカウントの認証を行います:

turso auth signup

2. 最初のデータベースを作成する

データベースを立ち上げましょう。Tursoは現在の場所に近いリージョンを自動的に選択します。

turso db create my-app-db

3. 対話型シェルの使用

SQLシェルに直接入り、テーブルの作成やデータの投入を行います:

turso db shell my-app-db

-- シェル内:
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);
INSERT INTO users (name, email) VALUES ('Alex', '[email protected]');
SELECT * FROM users;

4. 接続詳細の取得

アプリを接続するには、URLと認証トークンが必要です:

turso db show my-app-db --url
turso db tokens create my-app-db

ディープダイブ:なぜlibSQLなのか?

標準的なSQLiteはローカル開発には最適ですが、根本的には単一ファイルのデータベースです。複数のサーバー間で簡単に共有することはできません。libSQLは、HTTP/WebSocketsによるネットワーク通信、レプリケーション、およびネイティブなベクトル検索を追加することで、SQLiteのDNAを拡張しています。

データインポートのためにCSVをJSONに変換する必要があるときは、toolcraft.app/ja/tools/data/csv-to-jsonを使用しています。これは完全にブラウザ上で動作します。マシンからデータが送信されないため、移行中のクライアントのプライバシー維持に不可欠です。

組み込みレプリカ(Embedded Replica)アーキテクチャ

組み込みレプリカは特筆すべき機能です。クエリごとにネットワークコールを行う代わりに、サーバー上やDockerコンテナ内にローカルのSQLiteファイルを保持できます。このファイルはリードキャッシュとして機能します。Tursoはバックグラウンドでこれを同期します。読み取りはローカルディスク上で行われるため事実上0ミリ秒となり、書き込みはプライマリインスタンスに同期されます。

HTTP/WebSocketプロトコル

ネットワーク面も大きな利点です。Postgres用のlibpqのような従来のドライバーはTCPに依存しています。AWS LambdaやCloudflare Workersのような環境では、これらの接続は頻繁に切断されます。TursoはHTTPまたはWebSockets上のカスタムプロトコルを使用します。これは、5432や3306といった標準的なデータベースポートをブロックするプラットフォームに最適です。

レプリカによるグローバル分散

このアーキテクチャが真にスケールするのは、グローバル分散においてです。プライマリデータベースがord(シカゴ)にあり、ユーザーがロンドンにいる場合、コマンド1つでlhr(ロンドン)にレプリカを作成できます:

turso db replicate my-app-db lhr

ヨーロッパのサーバーレス関数に到達したリクエストは、ロンドンのレプリカにルーティングされます。これにより、データベースのレイテンシが150ミリ秒から10ミリ秒未満に激減します。

Node.jsからの接続

@libsql/clientライブラリが複雑な処理を肩代わりしてくれます。ローカルファイルとリモートのTursoインスタンスの違いを抽象化してくれます。

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

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

async function getUser(id: number) {
  const rs = await client.execute({
    sql: "SELECT * FROM users WHERE id = ?",
    args: [id],
  });
  return rs.rows[0];
}

本番環境向けの実践的なヒント

1. スキーママイグレーション

TursoはSQLiteベースなので、**Drizzle ORM**や**Prisma**を使用できます。私はDrizzleを好みます。軽量で「エッジ」の哲学に合致しているからです。Drizzleは、Tursoインスタンスに対してシームレスに実行できる標準的なSQLファイルを生成します。

2. パフォーマンスチューニング

データベースのハンドシェイクには時間がかかります。Tursoであっても、コールド状態のサーバーレス関数からの最初の接続には、TLSハンドシェイクのオーバーヘッドが発生します。アプリケーションがレイテンシに極端に敏感な場合は、組み込みレプリカ戦略を使用してください。ビルドステップでローカルファイルシステム上にデータベースを初期化し、起動時にデータが準備できている状態にします。

3. コストと制限

Tursoの無料プランは寛大です。現在は月間10億行の読み取りと2500万行の書き込みを提供しています。ただし、ストレージ容量には注意してください。SQLiteは効率的ですが、大きなバイナリデータ(BLOB)はコストを増加させます。画像はS3互換のバケットに保存し、Tursoにはメタデータのみを保持するようにしましょう。

4. AI向けベクトル検索

ベクトル検索が組み込まれています。RAG(検索拡張生成)アプリケーションを構築する場合、PineconeやMilvusは不要です。リレーショナルデータと並べてエンベディングを直接保存できます。これによりスタックが簡素化され、コストが削減されます。

-- libSQLでのベクトル検索の例
SELECT * FROM documents 
ORDER BY vector_distance_cos(embedding, '[0.1, 0.2, ...]') 
LIMIT 5;

Tursoのような分散データベースへの移行は、私の開発ワークフローを変えました。「ローカル開発」と「グローバルな本番環境」の間の摩擦がなくなります。SQLiteのシンプルさと、グローバルクラウドのパワーを同時に手に入れることができます。VPCや接続文字列との格闘に疲れたなら、データをエッジに移動させる時です。

Share: