0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「エッジで速い」はずのCloudflare D1が、日本からREST直叩きで測るとTurso(東京)に負けた ― 無料枠サーバレスDB3種(D1/Turso/Neon)のcold/warm予備実測

0
Last updated at Posted at 2026-07-02

「エッジにあるDBなら速い」——そう思ってCloudflare D1を選んだら、日本からのREST直叩きでは、"ただ東京にサーバを置いただけ"のTursoに負けました。 ウォーム状態の中央値は Turso 22ms / D1 60ms / Neon 87ms。さらにNeonは無料枠だと5分でcomputeが止まるので、初回接続に約1.2秒の起き直し待ちが乗りました。

ただしこれは「D1が遅い」という単純な話ではありません。今回のD1は read replication / Sessions API を使わないREST直叩き(後述、これは選択ではなく外部クライアントの唯一の経路)。この記事で見たいのは宣伝文句の勝ち負けではなく、無料枠サーバレスDBの体感速度を cold / warm / 接続 / wake に分解することです。

⚠️ 本記事はベンチマークではなく"配線の可視化"。日本(西日本)の実機1台・単一セッション(2026-07)の予備実測で、絶対値の順位より「体感速度を決める要素の腑分け」が目的です。各社は無料枠と挙動を頻繁に変えます——賞味期限が長いのは数値でなく測り方の部分です。


結論(3行)

  • 無料枠サーバレスDBの「速い/遅い」は、宣伝文句の「エッジ(世界中に分散)だから速い」よりも、①DBの実行地点が近いか ②アイドル時にcomputeが止まるか ③どの接続経路(プロトコル)で叩くか の3つで大勢が決まりました。
  • 日本(西日本の自宅回線)から測ると warm中央値は Turso(東京) 22ms < D1 60ms < Neon(シンガポール) 87ms。「世界中に分散していて速い」はずのD1が最速ではなかった。
  • 「起動待ちなし(no cold start)」を謳うD1/Tursoでも、初めてつなぐ時は接続のあいさつだけで約120〜150ms。一方 Neonは眠りから起きる時に約1.2秒(うち純粋な起き直しは約620ms・後述)。この2つは"待ち時間"でも中身が別物です。

この比較で分かったこと / 分からないこと

分かったこと 分からないこと
日本からREST/HTTP/pgで叩いた時の初回体感差 世界中のユーザーでの一般的な順位
Neon無料枠のwakeが接続確立に乗ること・その内訳 Neonに東京リージョンが来た場合の順位
D1 REST直叩きではTurso(東京)より遅かったこと D1 read replica / Sessions API 有効時の実力
rows_readがフルスキャンで急増すること 本番負荷・並行アクセス時のp99

で、結局どれ?(超要約):国内ユーザー中心なら Turso(東京)、Postgresの機能が要るなら Neon、Workers/エッジ資産があるなら D1。初回体験を重視するなら Neon の約1.2秒だけ要注意。→ 詳しくは後述の「用途別早見表」。

なぜ測ったか

比較記事の多くは「無料枠の数値」と「エッジだから速い」というカタログと語感で語られます。でも本番投入直前に効くのは、初回リクエストが遅い(デモは速いのに翌朝の最初の1クエリだけ固まる)/深夜にリセット・停止されて動かない、というカタログに載らない挙動。ここは自分で叩かないと分かりません。

対象は性格の異なる3種(Supabaseなど"BaaS一式"は、DB単体のレイテンシ比較から外れるので今回は除外)。詳細は次の設定表:

  • Cloudflare D1 … Cloudflare Workers統合のサーバレスSQLite。read replication/Sessions APIで読み取りを近づけられるが、今回はREST直叩き・primary実行で測定
  • Turso … libSQL(SQLite)。2025-03の新プランで「no cold starts / no scale-to-zero」を掲げる(※ただし公式Docsに反例あり・後述)
  • Neon … サーバレスPostgres、唯一 5分でscale-to-zero公式・無料枠は無効化不可)

設定(無料枠・配置・測定環境)

Turso Cloudflare D1 Neon
エンジン / 接続 libSQL(SQLite) / libsqlクライアント(HTTP) SQLite / REST API(HTTP・バインディング未使用) PostgreSQL / pg(TCP直結)
プライマリ配置 東京(aws-ap-northeast-1) APAC(作成地近傍に自動配置=東アジア圏と推定) シンガポール東京リージョン無し
休眠(本記事の定義※) per-request休眠なし。ただし10日非アクティブでアーカイブ公式API・矛盾あり後述) per-request休眠なし 5分でcompute停止→wake待ち
無料超過時 BLOCKED(月次) エラー(日次00:00 UTC) suspend/write失敗(月次)

※本記事の「休眠」は、NeonのようにcomputeがIdleになり次回接続でwake待ちが出る挙動を指します。D1のPricingにも課金文脈の"scale-to-zero"表現がありますが、それは「時間課金なし」の意味で、ユーザーに見えるwake待ちとは分けて扱います。
※測定条件(以降の全数値に共通):クライアント=日本・西日本の実機1台(Win11・家庭用回線)・単一セッション・2026-07。Node 24 / @libsql/client(HTTP経由) / pg。同一スキーマ bench(id,k,v) に1万行投入。p50中心(cold/warmのn数は各表に明記)。絶対値はノイズ込み、相対傾向として読む。
⚠️ Neonは東京リージョンが無いため最寄りのシンガポールを選択。これは不公平ではなく「個人開発者はNeonを東京に置けない」という無料枠の現実込みの実力です(要望スレは長期積み上げ・東京は未確約)。

測り方(ここが本記事の肝・壁時計1本に丸めない)

  • transport段階分解curl -w で DNS→TCP→TLS→TTFB を工程ごとに記録。
  • cold と warm を別に測る:cold=毎回別プロセスで新規接続からの初回。warm=確立済み接続で同一クエリを200回。Neonは各サンプルで5分超(7分)アイドル→別プロセスで復帰を測る。
  • 接続とwakeの分離(今回の追加測定):Neonはcompute起床中に新規接続だけを繰り返し、pg接続確立コスト(wake抜き)を実測 → 純wakeを差分で出した。
  • warmは複数クエリ種(select1/count/insert等)で測りいずれもp50同水準だったためselect1を代表に採用(チェリーピック回避)。
  • パーセンタイル:p50中心。p99は無負荷・逐次(coordinated omission未補正)の下限(本番の並行負荷ではこれ以上にしかならない読み方専用)。

結果①:コールドスタート ― 「no cold start」は半分本当・半分ミスリード

図1:cold初回E2E(新規接続→最初の1クエリが返るまで)― Turso 121ms / D1 152ms / Neon 約1245ms。Neonのみ接続/wake/queryの3分割

指標(単位 ms) Turso(東京) D1(APAC) Neon(Singapore)
cold 初回E2E(新規接続の最初の1クエリが返るまで) 約121 (n=30) 約152 (n=30) 約1245 (n=15)
うち wake込み接続確立(実測) ≒E2E(HTTP接続に内包) ≒E2E(HTTP接続に内包) 約1157(幅1084–1606)
 うち pg接続確立(起床中・実測 n=11) 約540
 うち 純wake(scale-to-zeroの起き直し) 約620(≒1157−540※中央値の差)
うち 初回クエリ (上の接続に内包) (上の接続に内包) 約88(≒warm p50)
参考 serverMs ―(取得不可) 0.13 ―(≒warmに含む)
warm p50 (n=200) 22 60 87

※E2Eの揃え方:HTTPのD1/Tursoは1回のHTTPリクエストで接続とクエリを兼ねるので connectMs=初回E2E(値を二重計上していない)。pgのNeonは接続(wake込み)+クエリの合算で、E2E約1245は各サンプルのconnect+queryの中央値(n=15・幅1173–1695ms・"中央値の和"でなく"和の中央値")。

読み方:

  • D1/Turso は本当に"サーバ側cold startなし"(D1のserverMs=DB自体の計算は0.13ms=無視できる)。でも新規接続の初回はTLS+往復で約120〜150ms払う。これは"cold start"ではなく接続確立コスト。「no cold start」を「初回も速い」と読むと裏切られます。
  • Neonの約1.2秒の内訳:pg接続確立 約540ms + 純wake 約620ms + 初回クエリ約88ms(各値は丸め・総和はE2E中央値1245とは数ms差="和の中央値"のため)。ここが今回の発見——1.2秒の半分近くはscale-to-zeroの起き直しですらなく、pgの多往復ハンドシェイク×距離です。pgの新規接続はTCP+TLS+SCRAMで5〜6往復(540ms ÷ RTT88ms ≒ 6往復相当=位相別の実測でなく往復数の逆算)。HTTP系のD1/Turso(~120-150ms)は往復数が少ない上により近距離なので軽い——効くのは往復数×距離の積(③接続経路と①距離の複合で、プロトコル単独ではない)。純粋なscale-to-zeroの代償は約620msです。

💡 この記事の芯(接続コスト ≠ cold start):巷の"no cold start"論争は、ほぼ全部が2つを混同しています。(A)サーバがクエリを演算する時間(D1で0.13ms=本当にゼロ)と、(B)新規接続のコスト(HTTPで~120-150ms=少往復×近距離、pgで~540ms=多往復×遠距離。ゼロにならない)。"no cold start"が真なのは(A)だけ。本記事は(A)(B)、さらにNeonの(C)純wakeまで実測で切り分けました。

結果②:ウォーム(定常)― 「エッジ」より「距離」と「接続経路」が効いた

図2:cold接続のtransport段階分解(DNS/TCP/TLS/TTFB)の3社比較。Neonのみpg wireでなくHTTPSプローブ由来=距離の目安

(生の分解データはリポジトリ参照。)

  • Neonのwarm 87msの正体は距離:別途HTTPSプローブで測ったNeonホストへのTCP往復が約88msで、日本→シンガポール間の往復時間として妥当(※pg wireでなくHTTPSプローブによる距離の目安、後述の「限界」)。「Neonが遅い」ではなく「日本から遠い」。
  • D1(60) vs Turso(22) の38ms差は距離では説明しきれない:D1のprimaryは作成地近傍に自動配置(日本から作成=東アジア圏と推定。primaryの都市までは未確定だが、D1 warm 60ms < Neon(Singapore) 87ms なので少なくともSingaporeより近い=完全な遠距離ではない)。この差は③接続経路=REST直叩き vs libsql の差が主因の可能性が高い(結果①のpg接続540ms実測も同じ軸の傍証)。ただしprimaryが東京でなく近隣国なら差の一部は距離で、そこは断定しません。→ 「距離が支配」と明言できるのはNeon(遠い) vs 在アジア2社の軸だけ、と自分から境界を引きます。
  • 🔴 D1の"エッジ読取"は外部クライアントには構造的に存在しない:D1のread replicaを使うSessions APIはWorkerバインディング限定でREST APIからは使えない。つまり「エッジ機能を殺した」のではなく、外からD1を叩く唯一の経路(REST)には最初から無い。本記事の60msは"外から見たD1"の実力そのもの(Workersバインディング経由なら経路が全く異なる)。

結果③:無料枠の"壁" ― 課金は起きないが、行数は静かに溶ける

これはD1/Tursoの欠陥ではなくクエリ設計の問題。ただし課金モデルを知らないと踏む地雷なので共有します。

で、結局どれを選ぶ?(用途別早見表)

原理は3つ(距離・scale-to-zero・接続経路)でした。あとは「あなたが何を最優先するか」。

あなたの状況 第一候補 理由(本記事の実測から) 注意
日本のユーザーが中心 Turso(東京) 距離が近くwarm 22ms・cold 121msで3種最速 rows_read=スキャン行課金。indexを張る
Cloudflare Workers中心 D1 Workers同居・サーバ演算ほぼ0・warm 60ms RESTでなくバインディング+read replicaなら本領は別(未評価)
Postgresの機能が要る Neon(一択) 他2つはSQLite。pg機能が要るなら距離とcoldは受け入れ 初回1.2秒を許せない用途は不可・東京無し
初回体験が命(低頻度アクセス) Turso / D1 scale-to-zeroが無く"翌朝の最初の1クエリ"が固まらない 新規接続の約120〜150msは誰でも払う
無料で長期放置したい 要確認(D1/Neon寄り) Tursoは10日非アクティブでアーカイブ(unarchive必要)="放置"には向かない可能性 公式Docs内で「常時稼働」と矛盾・後述
超過での課金は絶対避けたい どれも無料枠ではブロック/停止型 即従量課金でなく止まる設計 有料化後・他サービス連携は別途確認

⚠️ この表は日本(西日本)から測った予備実測の相対傾向。ユーザーがEU/US/シンガポール近傍なら序列は変わり得ます(後述の「限界」)。

落とし穴ログ(誤 → 正)― 私が測り間違えた/思い込んだ5つ

  1. :Neonのコールドを「初回クエリ時間」で測ったら warm と同じ約87msで「cold startほぼ無い」と結論。/:wakeは接続確立(connect)に乗る。接続ごと測り直すと復帰は約1157ms。この取りこぼしは起きやすい
  2. :Neonの1157msを丸ごと「wake」と表示。/:起床中の新規接続を実測したら約540msは距離×pgハンドシェイクで、純wakeは約620msだった。"1.2秒=起き直し"は過大。
  3. :DB作成直後の初回1発が1秒近い外れ値で「約15倍」と速断。/:外れ値(本記事の定常データセット外)。定常n=30でD1のcoldは約152ms。単発を一般化しない。
  4. :短いアイドル(5.5分)のNeonに570msが混じり分散大(※旧試行・現データセット外)。/:570msはsuspend未達。アイドルを7分にし別プロセスにしたら 1084–1606ms(n=15)に収まった。
  5. :「rows_readでD1の無料枠は溶ける」と一般化。/:溶けるのはindex無しフルスキャン固有。indexがあれば別。

この記事の数字をそのまま鵜呑みにしてほしいのではありません。むしろ外れ値・suspend未達・非対称な比較・wakeの過大表示をどう見つけて潰したかを残すことで、読者が「この測り方なら自分の環境でも再現できるか」を判断できるようにしました。

事実確認で見つけた"公式Docs内の矛盾"(Turso)

Tursoは2025-03の新プラン発表で「No more cold starts! …your databases will now stay responsive at all times, regardless of how long they've been inactive(=非アクティブ期間の長さに関わらず常時応答)」と明言しています。一方でAPIリファレンス(unarchive)には無料プランのDBは10日非アクティブでアーカイブされunarchiveが必要現行ドキュメントに現存します。さらにCLIの機能要望 #917(2024-09起票・現在もopenの enhancement)には、アーカイブ済みDBへ接続すると failed to WebSocket dial: … got 502 になる実エラーが引用されています(同issueはこの分かりにくい502を「unarchiveを促すUX」に改善する提案=バグ報告ではない点に注意)。「非アクティブ期間に関わらず常時応答」という標榜と「10日でアーカイブ→接続が502で落ちる」は正面から食い違っており、per-requestのcold startは無くても10日級の放置は別問題です。本記事の単一セッション実測では観測できない挙動なので、放置運用(数日〜週単位)を想定する人は要確認です。

限界(正直な開示)

  • 日本(西日本)の実機1台・単一セッション・2026-07(同一時間帯・家庭用回線)。絶対値はノイズ込み。多拠点(Sin/Fra/Virginia)VM計測は未実施
  • プロトコル非対称:D1=REST・Turso=libsql(HTTP)・Neon=pg。warm絶対比較はエンジン純性能でなく経路差を含む(特にD1 vs Turso)。
  • Neonはpg(TCP直)で測定:Neon公式の@neondatabase/serverless(HTTP/WS)は接続の多往復ハンドシェイク(約540ms)を畳むため、coldは概ね純wake(約620ms)+1往復程度に短縮が見込まれる(本記事の1245msは"TCP直の上限ケース")。warmは距離律速で大きくは変わらない。この非対称は結論(Neonはscale-to-zeroで遠い)を覆さないが、cold絶対値の読み方には効く
  • D1のread replica/Sessions API 未評価(REST直叩き=primary固定・構造的にRESTからは使えない)。
  • Neon transport の内訳は pg wire でなく HTTPS GET プローブ由来(距離の目安)。
  • Turso 10日アーカイブは未実測(公式Docsの矛盾のみ提示)。
  • サンプル:warm n=200 / cold Turso・D1 n=30 / Neon n=15。p99は無負荷・逐次(CO未補正)の下限(分布はリポジトリ参照)。
  • クォータ壁の破壊試験は未実施(本番DBを止めないため)。

再現手順

実測ハーネス(Node.js)は curl -w 段階分解+percentile集計+各DBアダプタ(D1=REST / Turso=libsql / Neon=pg)で構成。骨格:

cp .env.example .env              # D1/Turso/Neon のクレデンシャルを記入
npm run setup                     # bench(id,k,v) に1万行投入(3DB共通スキーマ)
npm run coldstart -- --mode=cold  # 別プロセス×新規接続でcold標本(Neonは5分+アイドル)
npm run coldstart -- --mode=warm  # 確立済み接続で200回
npm run report                    # curl -w段階分解+percentile集計

リポジトリ(ハーネス一式・結果jsonl・図)https://github.com/axiom-pro/serverless-db-bench.envは同梱せず.env.exampleのみ。数値は実測のみ=反捏造)。

まとめ

  • 体感速度=距離 × scale-to-zeroの有無 × 接続経路。少なくとも日本1地点では"エッジ"の語感は当てにならなかった(多拠点で要検証)。
  • 「no cold start」=「初回も速い」ではない。接続確立コスト・本物のwake・pgの多往復ハンドシェイクは別物。
  • 数値より測り方(cold/warm分離・wakeは接続に乗る・接続とwakeを分ける・単発を一般化しない)が結論を左右する。

今日からできること(実測から導いたチェックリスト)

  • 日本のユーザー向けなら、まず Turso を東京に置く(距離がwarmを支配・ただし10日放置のアーカイブは要確認)。
  • Neonを選ぶなら、初回1.2秒を設計で吸収(Cron等で温める or 初回ローディングUI)。距離×pgハンドシェイクで、起床中でも新規接続は~540ms。
  • D1/Turso はまずindexを張る("スキャン行"課金・EXPLAIN QUERY PLANSCAN を確認)。
  • 「no cold start」を鵜呑みにしない(新規接続の初回は必ず約120〜150ms・pgならもっと)。
  • 自分のユーザーの居場所で測り直す(この記事は西日本1地点)。

次回予告:(a) 多拠点VMから同ハーネスで距離を確定 (b) クォータ壁に実際にぶつける、を予定。この"失敗と検証"の物語版はnoteに書きます。

筆者について

この記事のスタンスは一貫しています——「速い」「no cold start」といった語感を信じず、自分で叩いて確かめる。

  • 「AIに書かせたコードが、本当に正しいのか不安」
  • 「無料枠がいつ溶けるか読めない。本番に出して大丈夫か」
  • 「DB選定やアーキの判断が、これで合っているか誰かに検証してほしい」

——こうした「動いてはいるが確信が持てない」を、一次情報で裏取りして検証し、直すところまでを仕事にしています(コード監査・小規模アプリ開発)。まずは相談から。

ココナラ:コード監査ココナラ:アプリ開発getaxiom.dev


関連記事(実測シリーズ)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?