SQLite互換サーバーレスDB Turso & FastAPI

2024/06/03に公開

Tursoとは?

公式ページによると,Tursoはスピードとスケーラビリティを兼ね備えたSQLite互換のデータベースです.
https://turso.tech/

あまり使ったことはないですが,Cloudflare D1に似てますね.

料金

Tursoの大きな特徴はコストの低さです.無料プランには以下の機能が含まれています.

  • 9 GBのストレージ基本容量、追加GBごとに$0.75
  • 最大500データベース、最大3箇所のロケーション
  • 月1億行の読み取り無料、超過分は10億行ごとに$1
  • 月2500万行の書き込み無料、超過分は100万行ごとに$1
  • 無制限の組み込みレプリカ
  • オーバーユース対応
  • 24時間以内のPITR
  • データベースブランチング

無料枠でも機能の縛りが少なく魅力的です.より高機能な有料プランも月$8.25から始められます.この値段でクラウドDBが使えるのはありがたいです.

https://turso.tech/pricing

Turso CLI

Tursoにはデータベースを管理するためのダッシュボードがありますが,ダッシュボード上でできる操作はあまり多くありません.主にCLIを使って操作していくことを想定しているようです.

Quick Startに沿って進めていくと,CLIのインストールと基本的な操作を学ぶことができます.

https://docs.turso.tech/quickstart

Turso & SQLModel & FastAPI

本題はこちらです.
TursoはSQLAlchemyに対応しています(sqlalchemy-libsql).さらにFastAPIと相性の良いSQLModelはSQLAlchemyがベースになっています.つまり Turso & SQLModel & FastAPI の組み合わせが実現できそう!と思い立って実際に試してみました.

参考:SQLAlchemy + Turso

データベース準備

Turso CLIでデータベースを作成します.

turso db create my-db
出力
Created database my-db at group default in 2.113s.

Start an interactive SQL shell with:

   turso db shell my-db

To see information about the database, including a connection URL, run:

   turso db show my-db

To get an authentication token for the database, run:

   turso db tokens create my-db

データベースのURLと,データベースにアクセスするためのトークンを取得します.

まずはデータベースのURLを取得します.

turso db show --url my-db
出力
libsql://...

次にトークンを取得します.

turso db tokens create my-db
出力
eyJhbGciOiJFZERT...

FastAPIから接続する

fastapisqlmodelsqlalchemysqlalchemy-libsqlをインストールします.

python -m venv .venv
source .vnev/bin/activate
pip install fastapi sqlmodel sqlalchemy sqlalchemy-libsql

検証用のコードです.<DB_URL><TOKEN>を先ほど取得したものに置き換えてください.

main.py
from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select

TURSO_DATABASE_URL = "<DB_URL>"
TURSO_AUTH_TOKEN = "<TOKEN>"

sqlite_url = f"sqlite+{TURSO_DATABASE_URL}/?authToken={TURSO_AUTH_TOKEN}&secure=true"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)

def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

app = FastAPI()

class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)

class HeroCreate(SQLModel):
    name: str
    secret_name: str
    age: int | None = None

@app.on_event("startup")
def on_startup():
    create_db_and_tables()

@app.post("/heroes/")
def create_hero(hero: HeroCreate):
    with Session(engine) as session:
        db_hero = Hero.model_validate(hero)
        session.add(db_hero)
        session.commit()
        session.refresh(db_hero)
        return db_hero

@app.get("/heroes/")
def read_heroes():
    with Session(engine) as session:
        heroes = session.exec(select(Hero)).all()
        return heroes

FastAPIを実行します.

fastapi dev main.py

動作確認

/heroesに対してPOSTリクエストを送信してデータを登録します.

curl -X POST "http://127.0.0.1:8000/heroes/" \
-H "Content-Type: application/json" \
-d '{"name": "Peter Parker", "secret_name": "Spider-Man", "age": 18}'
出力
{"id":1,"secret_name":"Spider-Man","name":"Peter Parker","age":18}

ちゃんとIDが振られてるのが確認できます.

続けてGETリクエストでデータが保存されているか確認します.

curl -X GET "http://127.0.0.1:8000/heroes/"
出力
[{"id":1,"secret_name":"Spider-Man","name":"Peter Parker","age":18}]

ちゃんとデータが保存されているのがわかります.

Tursoのダッシュボードからデータベースの中身を確認すると,確かにデータが登録されています.

ローカルサーバーも立てられる

ちなみに開発環境用でローカルのTursoデータベース(正確にはlibsqlサーバー)を立てられます.この場合トークンは不要です.

turso dev
出力
sqld listening on port 8080.
Use the following URL to configure your libSQL client SDK for local development:

    http://127.0.0.1:8080

No auth token is required when sqld is running locally.

This server is using an ephemeral database. Changes will be lost when this server stops.
If you want to persist changes, use --db-file to specify a SQLite database file instead.

turso db shellでローカルデータベースのアドレスを指定することもできます.

turso db shell http://127.0.0.1:8080

おわり

今回紹介しませんでしたが,レプリカ作ったりPITRしたりダンプしたりも簡単にできます.最高ですね.

Discussion