You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
増えるのは通信量ではなく関数実行回数。キャッシュ HIT でも関数実行でもクライアントに送る
バイト数は同じなので Fast Data Transfer はほぼ変わらない。公式ドキュメント "Tracked events" が
「キャッシュ済みなら 1 Edge Request イベント、そうでなければ Edge Request + Function Invocation」
と明記しているとおり、撤去で増えるのは Function Invocation と CPU 時間。
Observability > Edge Requests タブ(全プランで利用可)で /problems を選び、
関数実行回数の推移と bot の内訳を確認する。
公式ドキュメント: "provides detailed breakdowns for individual bots and bot categories,
including AI crawlers and search engines"
AI クローラーが目立つ場合は AI bots managed ruleset を log モードで有効化して実態を測る。
デフォルトは無効(ダッシュボード表記は "Allow")。ブロックせず計測だけできるので
まず log から入り、必要になってから deny を検討する
Observability Plus(Paid Pro / Enterprise)を使える場合は、
bot カテゴリでの絞り込み・個別 bot のメトリクス・クエリビルダーでの集計が追加で使える
注意: Bot protection managed ruleset(非ブラウザ的クライアントに JS チャレンジを出す方)は challenge モードにすると Phase 1 の curl 検証や Playwright の E2E が弾かれる可能性がある
(公式ドキュメントが "prevents requests that falsely claim to be from a browser such as a curl
request identifying as Chrome" と明記)。こちらもデフォルト無効("Off")。
Activity
[-][bug] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう[/-][+][bug?] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう[/+][-][bug?] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう[/-][+][bug] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう[/+]KATO-Hiro commentedon Jul 19, 2026
概要
ログイン済みユーザーが一覧表ページ(
/problems)にアクセスすると、不定期に未ログイン状態の表示になる。別タブで開き直すと解消されることが多い。根本原因
/problemsのレスポンスが「共有キャッシュ可」を宣言しながら、Vary: Cookieを宣言していない。その結果、匿名ユーザー向けにキャッシュされたレスポンスがログイン済みユーザーにも配信される。
src/routes/problems/+page.server.ts:38-40で、未ログインレスポンスにCache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=600を設定している。共有キャッシュのキーは URL + メソッド +
Varyが指名したヘッダーだけで構成される(RFC 9111)。Vary: Cookieを宣言しない限り、Cookie の有無・値ではエントリが分岐しない。これは Vercel 固有の挙動ではなく標準的な共有キャッシュの仕様であり、責任はアプリ側にある。
加えて Vercel は「Cookie 付きリクエストはキャッシュを迂回する」という安全側のヒューリスティクスを
持たない(Phase 1 で実測確認済み)。公式ドキュメント "Cacheable response criteria" の除外条件は
「リクエストに
Authorizationヘッダーがない」「レスポンスにset-cookieがない」等のみで、リクエスト側の Cookie の有無は配信判定に使われない。つまり宣言漏れはそのまま事故になる。
発生シーケンス:
auth_sessionを持たない匿名訪問者(bot・未ログイン閲覧者)が/problemsにアクセス→ 匿名版の HTML /
__data.jsonが CDN に最大 5〜15 分キャッシュされる→ CDN がセッション Cookie を無視してキャッシュ済み匿名版を返す
→ サーバーに到達すらせず、ページ全体(レイアウトの
user含む)が未ログイン表示になる「ログイン済みレスポンスにはヘッダーを付けない」という既存の対策
(
e2e/problems_cache.spec.tsで検証済み)はキャッシュへの書き込み側しか防いでおらず、読み取り側(Cookie 付きリクエストへのキャッシュ配信)が盲点だった。
補足 — 誰がキャッシュを温めるか:
session !== nullでヘッダーが付かない → 温めないSet-Cookie(auth.ts:54)が付くためキャッシュ対象外 → 温めない。ただし
expires: idlePeriodExpiresAtを過ぎるとブラウザがCookie 自体を送らなくなり、以後は匿名訪問者として温める側に回る
_gaはクライアント JS が発行するためオリジンは
Set-Cookieを返さず、auth_sessionも無いので匿名レスポンスがキャッシュされる症状との整合性
__data.jsonが別エントリであること、s-maxage=300の失効、リージョン差などが候補。修正方針に影響しないため深追いしない時系列
/votesに CDN ヘッダー追加)マージce00537eで/problemsに CDN ヘッダー追加(一覧表での発生開始点)Cache<T>)は共有データのみを保持しており、本バグとは無関係。/votesに同じヘッダー追加)や、後続の ui: scroll vote dropdown to center current grade on open (#3796) #3801 を含む一連の変更として認識された可能性が高い。
一覧表(
/problems)に限れば発生開始点は ui: scroll vote dropdown to center current grade on open (#3796) #3801(7/4)。設計方針
s-maxage/stale-while-revalidateの CDN ヘッダーを/problemsから撤去する。Vary: Cookieのみ。付ければ正しさは回復する(危険な選択肢ではない)が、後述のとおり本サイトでは得られる効果が不確かなわりに仕組みが複雑になる。hotfix では単純さを取る
匿名アクセス時の関数実行削減のみ
/problemsのみ(ユーザー決定)。/votesの同一パターンは別 Issue で対応撤去しても新たな問題が起きない根拠(運用実績)
Cache-Controlはsession === nullのときだけ設定されるため、ログイン済みユーザーへのレスポンスには現在も
Cache-Controlが一切付いていない。この「ヘッダーなし」の状態はce00537e(2026-07-04)以前から全ユーザーに対して継続しており、それに起因する不具合報告はない。撤去後は全ユーザーがこの既知の安全な状態に戻るだけで、未知の構成にはならない。
また
s-maxageが無ければ Vercel は関数レスポンスを CDN キャッシュしない(公式 "How to cache responses" が
s-maxageを必須としている)ため、明示的な
private/no-storeの追加は不要(YAGNI)。インメモリキャッシュとの関係(調査済み・変更なし)
CDN キャッシュとアプリ内のインメモリキャッシュ(
Cache<T>)は独立しており、本修正の影響を受けない。/problemsが読むデータは性質が分かれている:getVoteGradeStatistics()): インメモリキャッシュ経由。全ユーザー共通の集計値のみgetTaskResults(userId)/getTasksWithTagIds(tagIds, userId)):src/lib/services/task_results.tsはキャッシュを一切 import しておらず、毎リクエスト DB を引くしたがって個人データがインメモリキャッシュに載ることはなく、CDN ヘッダー撤去後も
共有データのキャッシュは有効なまま DB 負荷対策として機能し続ける。
補足(バグではない):
getCachedWorkbooksByUserは固定キーworkbooks_by_userを使うため一見ユーザー固有データを共有しているように読めるが、実際の呼び出し元は
getWorkBooksCreatedByUsers()(= ユーザー作成タイプの問題集すべて)であり閲覧者ごとのスコープではない。命名が紛らわしいだけ。
却下した代替案
Vary: Cookieを付けて匿名キャッシュを維持— 正しさは回復する。危険な選択肢ではない(ログイン済みは必ず別キーになり、
そもそも
s-maxageを付けないのでキャッシュもされない)。却下理由は安全性ではなく費用対効果。Varyは Cookie ヘッダー全体の値をキーにするため、「auth_sessionだけ見て_gaは無視」といった個別 Cookie 単位の指定はできない。本サイトは Google Analytics
(
src/lib/components/GoogleAnalytics.svelte,G-LFHMP1606H)を読み込んでおり、_ga/_ga_*は訪問者ごとに一意の値を持つ(gtag.js の標準挙動。本サイトでの実測は未実施)。Vary: Cookie下のキー_ga=GA1.1.123…auth_session=…; _ga=…GA Cookie はクライアント JS が発行するため初回リクエストにはまだ付いておらず、
bot と初回訪問者ぶんの HIT は残る。失うのは再訪問の匿名ユーザーぶんで、
その割合は未測定。Vercel 公式も「
Varyを増やすとエントリ数が指数的に増える」と警告している。効果が不確かなわりに「
Varyとs-maxageの両方が揃って初めて正しい」という壊れやすい構成になる(どちらかが失われても静かに退行する)ため、hotfix では採用しない。
Cookie 有無で
setHeadersを条件分岐するのみ— 書き込み側の制御であり、読み取り側(キャッシュ済みエントリの配信)を防げないため単独では無効。
ISR(adapter-vercel の
isr設定)— パス単位の共有キャッシュが前提なので、セッションで内容が変わるページには構造的に不適。
検証
/problemsのcache-controlにs-maxageが含まれず、x-vercel-cacheがHITにならないことを確認。Vercel の CDN キャッシュはデプロイ単位で分離されるため手動パージは不要だが、
想定外に
HITが続く場合はダッシュボードから手動パージするデプロイ後の負荷観測(Vercel Observability)
増えるのは通信量ではなく関数実行回数。キャッシュ HIT でも関数実行でもクライアントに送る
バイト数は同じなので Fast Data Transfer はほぼ変わらない。公式ドキュメント "Tracked events" が
「キャッシュ済みなら 1 Edge Request イベント、そうでなければ Edge Request + Function Invocation」
と明記しているとおり、撤去で増えるのは Function Invocation と CPU 時間。
増加幅は「5 分間に匿名
/problemsリクエストが何件あるか」でほぼ決まる(旧設定ではリージョンごと 5 分に 1 回しかオリジンに来なかったため)。
ログイン済みリクエストは元から毎回関数を実行しているので変化なし。
またこの構成は
ce00537e(2026-07-04)以前の状態そのものであり、未知の負荷ではない。観測手順:
/problemsを選び、関数実行回数の推移と bot の内訳を確認する。
公式ドキュメント: "provides detailed breakdowns for individual bots and bot categories,
including AI crawlers and search engines"
logモードで有効化して実態を測る。デフォルトは無効(ダッシュボード表記は "Allow")。ブロックせず計測だけできるので
まず log から入り、必要になってから
denyを検討するbot カテゴリでの絞り込み・個別 bot のメトリクス・クエリビルダーでの集計が追加で使える
注意: Bot protection managed ruleset(非ブラウザ的クライアントに JS チャレンジを出す方)は
challengeモードにすると Phase 1 の curl 検証や Playwright の E2E が弾かれる可能性がある(公式ドキュメントが "prevents requests that falsely claim to be from a browser such as a
curlrequest identifying as Chrome" と明記)。こちらもデフォルト無効("Off")。
参考:
残タスク(別 Issue 推奨)
/votesの同一パターン(src/routes/votes/+page.server.ts:20-22)の修正 — 同じバグが潜在。setHeadersを使うルートは/problemsと/votesの 2 つのみ(grep 確認済み)Vary: Cookieによる匿名 CDN キャッシュ再導入 — 実質的に見送り(2026-07-19 の実測を受けて降格)。アプリ全体で Edge Requests 25k・検証済み bot 4% という規模では、
Vary を入れて救えるトラフィック(Cookie を持たない層)がごくわずかで、複雑さに見合わない。
以下は当時の技術的評価として残す。
正しさは担保されるが、GA Cookie による断片化で再訪問の匿名ユーザーぶんの HIT を失う
(bot・初回訪問者ぶんは残る)。着手するなら先に「匿名トラフィックのうち
Cookie を持たないリクエストの割合」を計測し、割に合うか判断すること。
計測手段は「デプロイ後の負荷観測」の Edge Requests タブがそのまま使える
— bot はまさに Cookie を持たない層なので、その内訳が
「
Vary: Cookieを入れても HIT が残る割合」の近似になる。現時点では関数実行コストが問題になっていないため YAGNI
Merge pull request #3874 from AtCoder-NoviSteps/hotfix/#3862