Skip to content

[bug] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう #3862

Closed

Description

@KATO-Hiro

Summary / 概要

  • ログインした状態で一覧表ページ > 未ログイン 状態になってしまう
  • 毎回発生するわけでもない
  • 何か対策したわけでもないが解消しているユーザもいる
  • 別のタブで開くと解消

Steps to reproduce / 再現方法

  1. xxx
  2. yyy
  3. zzz

environments

  • Desktop (please complete the following information):

    OS: [e.g. Mac, Windows11, Linux]
    Browser [e.g. Chrome, Safari, Edge, Firefox]
    Version [e.g. 117.0.05938.44]

  • Smartphone (please complete the following information):

    Device: [e.g. iPhone15, Android13]
    OS: [e.g. iOS16.5, Android 13]
    Browser [e.g. Chrome, Safari]
    Version [e.g. 16.5]

  • Tablet (please complete the following information):

    OS: [e.g. Android OS, Chrome OS, Windows11, iPad OS]
    Browser [e.g. Chrome, Safari, Edge, Firefox]
    Version [e.g. 16.5]

Expected behavior / 期待される挙動

  • ログインした状態で一覧表が表示される

Actual behavior / 実際の挙動

  • ログインした状態で、一覧表にアクセスするとログインされていない状態
  • 別タブで開くと解消

Screenshots

  • If applicable, add screenshots to help explain your problem.

Other notes / その他

  • Add any other context about the problem here.
  • Will you try to create a pull request?
    • yes / no

https://x.com/woody_1227/status/2077183136501502365?s=20

Activity

  1. added theissue type on Jul 15, 2026
  2. changed the title [-][bug] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう[/-] [+][bug?] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう[/+] on Jul 15, 2026
  3. changed the title [-][bug?] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう[/-] [+][bug] 一覧表 ページのみログインが解除される状態が不定期で起きているので、原因を特定して修正しましょう[/+] on Jul 19, 2026
  4. KATO-Hiro commented on Jul 19, 2026

    @KATO-Hiro
    CollaboratorAuthor

    概要

    ログイン済みユーザーが一覧表ページ(/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 の有無は配信判定に使われない。つまり宣言漏れはそのまま事故になる。

    発生シーケンス:

    1. auth_session を持たない匿名訪問者(bot・未ログイン閲覧者)が /problems にアクセス
      → 匿名版の HTML / __data.json が CDN に最大 5〜15 分キャッシュされる
    2. その間にログイン済みユーザーが同じ URL にアクセス
      → CDN がセッション Cookie を無視してキャッシュ済み匿名版を返す
      → サーバーに到達すらせず、ページ全体(レイアウトの user 含む)が未ログイン表示になる

    「ログイン済みレスポンスにはヘッダーを付けない」という既存の対策
    (e2e/problems_cache.spec.ts で検証済み)はキャッシュへの書き込み側しか防いでおらず、
    読み取り側(Cookie 付きリクエストへのキャッシュ配信)が盲点だった。

    補足 — 誰がキャッシュを温めるか:

    • 有効セッション保持者: session !== null でヘッダーが付かない → 温めない
    • 期限切れ Cookie を送る訪問者: stale cookie 削除の Set-Cookie(auth.ts:54)が付くため
      キャッシュ対象外 → 温めない。ただし expires: idlePeriodExpiresAt を過ぎるとブラウザが
      Cookie 自体を送らなくなり、以後は匿名訪問者として温める側に回る
    • GA Cookie のみを持つ再訪問者: 温める。_ga はクライアント JS が発行するため
      オリジンは Set-Cookie を返さず、auth_session も無いので匿名レスポンスがキャッシュされる

    症状との整合性

    症状 説明
    不定期に発生 直近 5〜15 分以内に同一 URL への匿名アクセスがあった場合のみ発生
    一部ユーザーのみ Vercel CDN キャッシュはリージョン単位で分離されている(公式仕様)
    ログイン直後にも発生 ログイン前の本人の匿名アクセスがキャッシュを温めるパターン
    別タブで解消することが多い 推測(未検証)。HTML と __data.json が別エントリであること、s-maxage=300 の失効、リージョン差などが候補。修正方針に影響しないため深追いしない

    時系列

    日付 出来事
    2026-06-21 PR #3707(サーバー内メモリキャッシュ)、PR #3709(/votes に CDN ヘッダー追加)マージ
    2026-07-04 PR #3801 マージ — ce00537e で /problems に CDN ヘッダー追加(一覧表での発生開始点)
    2026-07-15 Issue #3862 起票

    設計方針

    s-maxage / stale-while-revalidate の CDN ヘッダーを /problems から撤去する。

    • 読み取り側を制御する手段は Vary: Cookie のみ。付ければ正しさは回復する(危険な選択肢ではない)が、
      後述のとおり本サイトでは得られる効果が不確かなわりに仕組みが複雑になる。hotfix では単純さを取る
    • DB 負荷対策は PR feat(cache): add server-side caching for tasks, votes, and workbooks #3707 のインメモリキャッシュが引き続き担うため、失うのは
      匿名アクセス時の関数実行削減のみ
    • 修正範囲は /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()(= ユーザー作成タイプの問題集すべて)であり閲覧者ごとの
    スコープではない。命名が紛らわしいだけ。

    却下した代替案

    1. Vary: Cookie を付けて匿名キャッシュを維持
      — 正しさは回復する。危険な選択肢ではない(ログイン済みは必ず別キーになり、
      そもそも s-maxage を付けないのでキャッシュもされない)。却下理由は安全性ではなく費用対効果。

      Vary は Cookie ヘッダー全体の値をキーにするため、「auth_session だけ見て _ga は無視」
      といった個別 Cookie 単位の指定はできない。本サイトは Google Analytics
      (src/lib/components/GoogleAnalytics.svelte, G-LFHMP1606H)を読み込んでおり、
      _ga / _ga_* は訪問者ごとに一意の値を持つ(gtag.js の標準挙動。本サイトでの実測は未実施)。

      リクエスト元 Cookie ヘッダー Vary: Cookie 下のキー 結果
      bot・クローラー(JS 実行なし) なし 全員共通 HIT
      初回訪問者の最初のリクエスト なし 全員共通 HIT
      再訪問の匿名ユーザー _ga=GA1.1.123… 訪問者ごとに一意 MISS
      ログイン済みユーザー auth_session=…; _ga=… 一意 MISS(元々キャッシュ対象外)

      GA Cookie はクライアント JS が発行するため初回リクエストにはまだ付いておらず、
      bot と初回訪問者ぶんの HIT は残る。失うのは再訪問の匿名ユーザーぶんで、
      その割合は未測定。Vercel 公式も「Vary を増やすとエントリ数が指数的に増える」と警告している。

      効果が不確かなわりに「Vary と s-maxage の両方が揃って初めて正しい」という
      壊れやすい構成になる(どちらかが失われても静かに退行する)ため、hotfix では採用しない。

    2. Cookie 有無で setHeaders を条件分岐するのみ
      — 書き込み側の制御であり、読み取り側(キャッシュ済みエントリの配信)を防げないため単独では無効。

    3. ISR(adapter-vercel の isr 設定)
      — パス単位の共有キャッシュが前提なので、セッションで内容が変わるページには構造的に不適。

    検証

    デプロイ後の負荷観測(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)以前の状態そのものであり、未知の負荷ではない。

    観測手順:

    • 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")。

    参考:

    残タスク(別 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
  5. added a commit that references this issue on Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    invalidThis doesn't seem right

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions