「エッジにある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」は半分本当・半分ミスリード
| 指標(単位 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まで実測で切り分けました。
結果②:ウォーム(定常)― 「エッジ」より「距離」と「接続経路」が効いた
(生の分解データはリポジトリ参照。)
- 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の欠陥ではなくクエリ設計の問題。ただし課金モデルを知らないと踏む地雷なので共有します。
- 超過時の挙動(公式Docs):D1=日次00:00 UTCリセット・超過はエラーでブロック/Turso=月次・BLOCKEDエラー/Neon=compute suspend/書込失敗。いずれもブロック/停止型で自動課金ではない(※本記事は本番DBを止めないため壁への破壊試験は未実施・挙動は公式Docs準拠)。
-
rows_read="スキャン行"の罠(D1/Turso):D1・Tursoは返した行数でなく"めくった"行数(スキャン行)で課金。
count(*)やインデックス無し検索は1回で全1万行を読み取ります。実測では、count+unindexed_scanを各30回(=1巡にフルスキャン60回)含むウォーム1巡で約60万行(rows_read_approx=600,000)を消費。無料枠はD1が日次(500万行/日)・Tursoが月次(500M/月)と窓が違うが、D1ならこの極端な巡回を約8巡で日次上限に触れ得る規模感。適切なindexで激減します(=無料枠の罠ではなくクエリ設計の話)。
で、結局どれを選ぶ?(用途別早見表)
原理は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つ
- 誤:Neonのコールドを「初回クエリ時間」で測ったら warm と同じ約87msで「cold startほぼ無い」と結論。/正:wakeは接続確立(connect)に乗る。接続ごと測り直すと復帰は約1157ms。この取りこぼしは起きやすい。
- 誤:Neonの1157msを丸ごと「wake」と表示。/正:起床中の新規接続を実測したら約540msは距離×pgハンドシェイクで、純wakeは約620msだった。"1.2秒=起き直し"は過大。
- 誤:DB作成直後の初回1発が1秒近い外れ値で「約15倍」と速断。/正:外れ値(本記事の定常データセット外)。定常n=30でD1のcoldは約152ms。単発を一般化しない。
- 誤:短いアイドル(5.5分)のNeonに570msが混じり分散大(※旧試行・現データセット外)。/正:570msはsuspend未達。アイドルを7分にし別プロセスにしたら 1084–1606ms(n=15)に収まった。
- 誤:「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 PLANでSCANを確認)。 - 「no cold start」を鵜呑みにしない(新規接続の初回は必ず約120〜150ms・pgならもっと)。
- 自分のユーザーの居場所で測り直す(この記事は西日本1地点)。
次回予告:(a) 多拠点VMから同ハーネスで距離を確定 (b) クォータ壁に実際にぶつける、を予定。この"失敗と検証"の物語版はnoteに書きます。
筆者について
この記事のスタンスは一貫しています——「速い」「no cold start」といった語感を信じず、自分で叩いて確かめる。
- 「AIに書かせたコードが、本当に正しいのか不安」
- 「無料枠がいつ溶けるか読めない。本番に出して大丈夫か」
- 「DB選定やアーキの判断が、これで合っているか誰かに検証してほしい」
——こうした「動いてはいるが確信が持てない」を、一次情報で裏取りして検証し、直すところまでを仕事にしています(コード監査・小規模アプリ開発)。まずは相談から。
→ ココナラ:コード監査 / ココナラ:アプリ開発 / getaxiom.dev
関連記事(実測シリーズ)
- 続編(詰まった瞬間に何が起きるか): Cloudflare D1の無料枠を500MBの壁まで埋めて実測:全面read-onlyにはならず、DELETEで容量メーターが即戻った
- 物語版(非エンジニア向けの読み物): 速いはずのDBを測ったら、3倍差で負けてた

