Astro + Cloudflare D1でSSRが異常に遅い?EmDashでD1 Sessionsが50秒ハングした原因と対処法
Astro・Cloudflare Workers・D1・EmDash構成でSSRが最大約50秒停止した事例をもとに、D1 Sessionsとglobal_fetch_strictly_publicの非互換、切り分け方、対処法を解説します。

Astro + Cloudflare Workers + D1を使ってWebサイトを構築していたところ、SSRページの初回表示に数十秒、当社の環境では長いときに約50秒かかる現象に遭遇しました。
最初に疑ったのはCloudflare D1です。しかし調べていくと、SQLの実行そのものに50秒かかっていたわけではありませんでした。
今回問題になったのは、EmDashのD1設定で使用していたsession: "auto"と、Cloudflare WorkersのCompatibility Flagであるglobal_fetch_strictly_publicの組み合わせです。
この組み合わせによる問題はEmDashのGitHub Issue #1273でも報告されており、現在のEmDash公式Cloudflareデプロイガイドにも非互換であることが明記されています。
この記事では、実際にどのような症状が発生したのか、なぜD1自体の遅延と勘違いしやすかったのか、そしてどのように解消したのかを紹介します。
発生した症状:SSRだけ異常に遅い
今回の構成は、おおまかに次のとおりです。
- Astro
- Cloudflare Workers
- Cloudflare D1
- EmDash
- SSR
通常なら、ページ表示に数十秒も待たされるような構成ではありません。ところが、しばらくアクセスしていない状態などからSSRページへアクセスすると、ページがなかなか返ってこないことがありました。
体感的な「少し遅い」というレベルではなく、8秒、10秒、場合によっては40〜50秒近く待たされる状態です。一方、静的ファイルは正常に配信されており、D1へアクセスするSSR処理の付近で止まっているように見えました。
そこで最初は、次のような可能性を疑いました。
- D1のコールドスタートが遅いのではないか
- 日本からD1のプライマリーデータベースが遠いのではないか
- D1そのものが混雑しているのではないか
SQL自体はほとんど時間がかかっていなかった
調査していて奇妙だったのは、D1のSQL処理時間そのものは非常に短かったことです。
つまり、「重いSQLを実行しているから50秒かかっている」という説明では辻褄が合いません。
Webアプリケーションでデータベースアクセス付近が遅いと、つい「データベースが遅い」と考えてしまいます。しかし実際には、SQLへ到達するまでの経路で待機している可能性があります。
SSRリクエスト
↓
D1を利用する処理
↓
セッションの初期化・ルーティング
↓
SQL実行
↓
レスポンス
SQLの実行時間だけを確認していても、原因を特定できないケースでした。
EmDashのD1設定を確認する
今回使用していたEmDashでは、D1を次のように設定していました。
import { d1 } from "@emdash-cms/cloudflare";
emdash({
database: d1({
binding: "DB",
session: "auto",
}),
});
ポイントはsession: "auto"です。
この設定ではD1 Sessions APIが利用されます。Cloudflare D1のSessions APIは複数のクエリを論理的なセッションとして扱い、Bookmarkを使ってSequential Consistency(逐次一貫性)を確保する仕組みです。
また、Cloudflareによると、D1のRead Replicationを利用するにはSessions APIが必要です。Sessions APIを使わない場合、クエリはプライマリーデータベースで実行されます。詳しい仕組みはCloudflare D1のRead Replication公式ドキュメントで確認できます。
本来は、グローバルにユーザーがいるサイトの読み取りレイテンシを改善できる仕組みです。問題はSessions APIそのものではなく、今回のWorkers設定との組み合わせでした。
原因はglobal_fetch_strictly_publicとの組み合わせ
wrangler.jsoncには、次の設定がありました。
{
"compatibility_flags": [
"global_fetch_strictly_public",
"nodejs_compat"
]
}
Cloudflareの説明では、global_fetch_strictly_publicを有効にすると、Workerのグローバルfetch()はPublic Internetから行われたリクエストのようにルーティングされます。Worker自身のZoneへのリクエストもCloudflareの「front door」へ戻る動作になる場合があります。詳細はCloudflare WorkersのCompatibility Flags公式ドキュメントに記載されています。
EmDash Issue #1273では、session: "auto"とglobal_fetch_strictly_publicを組み合わせることで、SSRリクエストがハングする問題が報告されています。
なぜエラーではなく「ずっと遅い」に見えたのか
この問題を分かりにくくしていたのが、派手なエラーが出るのではなく、処理が待ち続けるように見えることです。
Issue #1273で報告されている症状は次のとおりです。
- SSRルートがハングする
- Static Assetsは正常に配信される
- WorkerのCPU Timeはほとんど使われていない
- Wall Timeだけが約10秒まで伸びる
outcome: "canceled"になる- ExceptionsとLogsには何も残らない
なお、Issueで報告されたタイムアウトは約10秒です。当社環境で観測した最大約50秒という値は、EmDash公式の測定値ではありません。
CPU使用時間が大きければSSRの重い処理を疑い、SQLの実行時間が長ければクエリ改善を検討できます。しかし、CPUもSQLも速く、エラーも出ないのにWall Timeだけ長い場合、アプリケーションコードだけを眺めていても原因を見つけにくくなります。
D1の「コールドスタート」だと思ってしまった
特に紛らわしかったのは、常に遅いわけではなかったことです。しばらくアクセスしていない状態から最初にアクセスしたときなどに目立ったため、D1のコールドスタートではないかと考えました。
しかし、数百ミリ秒程度の差ならともかく、数十秒単位の待ち時間は通常のデータベースレイテンシとして考えるには大きすぎます。
このような場合は「データベースが遅い」ではなく、「データベースへ到達するまでのどこかで待っていないか」という視点に切り替えると、原因を見つけやすくなります。
対処法:sessionをdisabledに変更
今回の環境では、EmDashのD1設定でSessions利用を無効にすることで症状が解消しました。
変更前:
database: d1({
binding: "DB",
session: "auto",
})
変更後:
database: d1({
binding: "DB",
session: "disabled",
})
session: "disabled"はIssue #1273でも回避策として紹介されています。EmDashの現在の設定リファレンスではdisabledがデフォルトです。
この変更によって、異常に長かったSSRの待ち時間は解消しました。つまり今回の場合、AstroのSSRそのものが遅かったわけでも、D1のSQLが50秒かかっていたわけでもありません。D1 Sessionsを利用する経路とWorkersのCompatibility Flagの組み合わせが問題でした。
global_fetch_strictly_publicを外す方法もある?
構成によってはglobal_fetch_strictly_public側を外せる可能性もあります。ただし、このフラグはアプリケーション全体のfetch()のルーティング動作に関係します。
そのため、「D1が遅いから、とりあえずCompatibility Flagを削除する」という変更はおすすめできません。そのWorkerでなぜこのフラグを使用しているのかを確認したうえで、Sessionsを無効にするのか、Compatibility Flagを見直すのかを判断する必要があります。
今回のようにRead Replicationを利用していないのであれば、session: "disabled"にする方が影響範囲を限定しやすい場合があります。
Read Replicationを使っていないならSessionsは必要?
Cloudflare D1のRead Replicationは、プライマリーデータベースとは別にRead Replicaを複数リージョンへ配置し、ユーザーに近いReplicaから読み取ることでレイテンシを下げる仕組みです。
Read Replicationを利用するにはSessions APIが必要です。逆にRead Replicationを利用していない構成なら、「このサイトでSessions APIを使う必要があるのか」を一度確認してもよいでしょう。
ただし、Sessions APIにはBookmarkによる一貫性管理という役割もあります。「Read Replicationを使っていないからSessionsは必ず不要」とは限りません。アプリケーションの読み書きや一貫性要件を確認したうえで判断する必要があります。
同じ症状が出たら確認したい7つのポイント
Astro + Cloudflare D1でSSRが異常に遅い場合は、次の順番で切り分けると原因を探しやすくなります。
1. 静的ページも遅いのか
静的ファイルは速く、SSRだけ遅いのであれば、SSRで実行している処理を疑います。
2. D1を使わないSSRは速いのか
D1アクセスを一時的に外した状態と比較すると、D1を利用する経路に原因があるかを切り分けやすくなります。
3. SQLの実行時間は本当に長いのか
SQLが数ミリ秒なのにリクエスト全体が数十秒かかっているなら、SQL以外の処理を疑います。
4. CPU TimeとWall Timeを比較する
CPU Timeが短いのにWall Timeだけ異常に長い場合、外部I/Oや待機処理で止まっている可能性があります。
5. D1 Sessionsを使っているか
EmDashの設定がsession: "auto"またはsession: "primary-first"になっていないか確認します。
6. Compatibility Flagsを確認する
特にglobal_fetch_strictly_publicとの組み合わせを確認します。
7. Read Replicationが本当に必要か確認する
Read Replicationを利用していないのであれば、Sessionsを有効にしている理由も確認します。
「D1が遅い」と判断する前に経路を見る
今回のトラブルから改めて感じたのは、「データベースを使うところで遅い」と「データベースが遅い」は別ということです。
ブラウザ
↓
Cloudflare
↓
Worker
↓
Astro SSR
↓
EmDash
↓
D1 Sessions
↓
D1
↓
SQL
ページ表示に50秒かかったからといって、D1のSQLが50秒かかったとは限りません。今回もSQLそのものではなく、Sessionsを利用する経路に問題がありました。
SSRやサーバーレス環境のパフォーマンス問題では、処理全体の時間だけではなく、どこからどこまでに時間がかかっているかを計測することが重要です。
参考資料
- EmDash Issue #1273:D1 Sessionsとglobal_fetch_strictly_publicによるハング
- EmDash公式:Deploy to Cloudflare
- EmDash公式:Database Options
- Cloudflare公式:D1 Global read replication
- Cloudflare公式:Workers Compatibility flags
まとめ
Astro + Cloudflare D1を使ったSSRで、当社の環境では数十秒、長いときには約50秒待たされる現象が発生しました。
当初はD1のコールドスタートやリージョン、SQLの処理速度などを疑いましたが、問題になったのは次の組み合わせです。
EmDashの session: "auto"
+
Cloudflare Workersの global_fetch_strictly_public
今回の環境では、session: "disabled"へ変更してD1 Sessionsの利用を無効化することで、異常な待ち時間が解消しました。
このような問題では、「D1アクセス付近で遅い」=「D1のSQLが遅い」と決めつけないことが重要です。SQLの実行時間、WorkerのCPU TimeとWall Time、SSRのどの処理で止まっているのかを順番に確認すると、原因を絞り込みやすくなります。
Astro + Cloudflare D1で「なぜかSSRだけ異常に遅い」という現象に遭遇した場合は、D1 Sessionsとglobal_fetch_strictly_publicの組み合わせも確認してみてください。
制作・保守のご相談
WordPressの保守・セキュリティ・フロントエンド開発についてお気軽にご相談ください。
お問い合わせ