perf-debug

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Perf debug (front → back)

性能调试(前端 → 后端)

Repeatable loop for resolving Kodus web performance problems, from the rendered screen down to the DB query. Built from real sessions on the token-usage, pull-requests, settings and cockpit screens.
用于解决 Kodus Web 性能问题的可重复流程,贯穿从渲染后的页面到 DB 查询。该流程基于对 token-usage、pull-requests、settings 和 cockpit 页面的实际调试会话沉淀而成。

The loop

调试循环

  1. Open the screen — Playwright (
    mcp__plugin_playwright_playwright__*
    ) or Chrome MCP. App is auth-gated;
    /
    is 404 post-Next16, log in at
    /sign-in
    (email → Continue → password). Test user:
    Novus@teste.com
    (org
    aae1c003-…
    has real data). Reset its password via Postgres if needed (bcryptjs hash; see the memory).
  2. Observe — never trust the first load. Capture
    browser_console_messages
    (errors) +
    browser_network_requests
    (fan-out). Reload 2× on a settled dev server: HMR produces phantom hydration errors / truncated-parse 500s. Only what reproduces on a clean load is real.
  3. Measure via the Performance API (
    browser_evaluate
    ):
    nav.responseStart
    (TTFB), FCP, LCP, load. Classify the bottleneck:
    • high TTFB → SSR-data-bound (server component awaiting slow data)
    • big JS / slow FCP after TTFB → bundle-bound
    • janky interaction → render-bound (note: React Compiler is ON, so manual memo is rarely the fix).
  4. Understand the components — read the page.tsx / client component. Find the server/client split and the loading states. Watch for
    if (!isMounted) return null
    (blanks the body pre-hydration → show a skeleton instead) and effects that
    router.replace
    with
    searchParams
    in deps (reload loops).
  5. Trace to the API — from the fan-out, pick the heavy/redundant endpoints (duplicates, per-page
    /api/auth/session
    ,
    /executions
    , cockpit fan-out).
  6. Understand the query — read
    apps/api/src/controllers/*
    →
    libs/**/use-cases
    → repository. Then hit the DB directly:
    • Postgres:
      docker exec kodus_api printenv API_PG_DB_PASSWORD
      , then
      docker exec -e PGPASSWORD=… db_postgres psql -U kodusdev -d kodus_db
      . Check
      pg_indexes
      for the table; look for OFFSET-in-loop, uncached
      COUNT(*)
      , N+1 (
      relations:[...]
      on a to-one is a JOIN, not N+1).
    • Mongo:
      docker exec mongodb mongosh "mongodb://kodusdev:<pass>@localhost:27017/<db>?authSource=admin"
      .
      db.<coll>.getIndexes()
      (watch for partial indexes matching the filter),
      .explain("executionStats")
      — compare
      totalKeysExamined
      vs
      nReturned
      . See the
      mongodb-query-optimizer
      skill for deeper analysis.
    • Kodus DB is generally well-indexed; the real backend wins are usually algorithmic (keyset vs OFFSET) or caching — and only reproduce at prod scale (dev DB is tiny), so prepare the patch and validate in staging.
  7. Fix surgically + verify live — one change, then re-measure + re-check console on a clean reload. Confirm no new hydration errors, no 500.
  8. Isolate when unsure — revert to compare. If a change might be the cause,
    git checkout -- <file>
    (back it up first) and reload: if the symptom persists with the original, your change is exonerated. (Used to prove the token-usage blank was a pre-existing reload loop, not the recharts port.)
  1. 打开页面——使用 Playwright(
    mcp__plugin_playwright_playwright__*
    )或 Chrome MCP。应用需要身份验证;Next16 之后
    /
    返回 404,需在
    /sign-in
    登录(email → Continue → password)。测试用户:
    Novus@teste.com
    (组织
    aae1c003-…
    中有真实数据)。如有必要,可通过 Postgres 重置其密码(bcryptjs hash;参见 memory)。
  2. 观察——绝不要相信首次加载。 捕获
    browser_console_messages
    (错误)以及
    browser_network_requests
    (请求扇出)。在开发服务器稳定后重新加载 2 次:HMR 会产生虚假的 hydration 错误或因截断解析导致的 500 错误。只有在干净加载时能够复现的问题才是真问题。
  3. 测量,通过 Performance API(
    browser_evaluate
    ):
    nav.responseStart
    (TTFB)、FCP、LCP、load。判断瓶颈类型:
    • TTFB 过高 → SSR-data-bound(服务端组件正在等待缓慢的数据)
    • JS 体积过大 / TTFB 后 FCP 缓慢 → bundle-bound
    • 交互卡顿 → render-bound(注意:React Compiler 已开启,因此手动 memo 很少是正确的修复方式)。
  4. 理解组件——阅读 page.tsx / client component。找出服务端与客户端的拆分方式以及 loading state。注意
    if (!isMounted) return null
    (在 hydration 前清空正文 → 应改为显示 skeleton),以及在依赖项中包含
    searchParams
    时调用
    router.replace
    的 effects(会导致重复加载循环)。
  5. 追踪至 API——从请求扇出中找出耗时较高或冗余的 endpoint(重复请求、逐页调用
    /api/auth/session
    、
    /executions
    、cockpit 请求扇出)。
  6. 理解查询——阅读
    apps/api/src/controllers/*
    →
    libs/**/use-cases
    → repository。然后直接连接 DB:
    • Postgres:
      docker exec kodus_api printenv API_PG_DB_PASSWORD
      ,然后运行
      docker exec -e PGPASSWORD=… db_postgres psql -U kodusdev -d kodus_db
      。检查表对应的
      pg_indexes
      ;查找循环中使用 OFFSET、未缓存的
      COUNT(*)
      、N+1(to-one 关系上的
      relations:[...]
      是 JOIN,而不是 N+1)。
    • Mongo:
      docker exec mongodb mongosh "mongodb://kodusdev:<pass>@localhost:27017/<db>?authSource=admin"
      。使用
      db.<coll>.getIndexes()
      (留意与过滤条件匹配的部分索引),并使用
      .explain("executionStats")
      ——比较
      totalKeysExamined
      与
      nReturned
      。如需更深入的分析,请参阅
      mongodb-query-optimizer
      skill。
    • Kodus DB 的索引通常已经比较完善;后端真正的性能收益通常来自算法优化(keyset 而非 OFFSET)或缓存——而且只有按生产规模复现(dev DB 的数据量很小)才能观察到真实差异,因此应准备补丁并在 staging 中验证。
  7. 精准修复并进行实时验证——每次只做一项更改,然后重新测量,并在干净重载后重新检查 console。确认没有新的 hydration 错误,也没有 500 错误。
  8. 不确定时进行隔离——回退后对比。 如果某项更改可能是原因,先备份文件,再执行
    git checkout -- <file>
    并重新加载:如果恢复原始版本后症状仍然存在,即可排除此次更改是原因。(曾使用该方法证明 token-usage 页面空白源于已有的重复加载循环,而不是 recharts 移植所致。)

Dev-env gotchas (these cost hours if unknown)

开发环境陷阱(如果不了解这些问题,可能会耗费数小时)

  • HMR noise → hydration/parse errors that vanish on a 2nd clean reload.
  • Turbopack truncated-parse cache (
    Unexpected eof
    on a valid file) →
    touch
    the file to force a fresh re-read. Root cause:
    CHOKIDAR_USEPOLLING=false
    • :delegated
      bind-mount catches partial writes.
  • OOM → heavy routes (charts + turbopack) blow the web container's memory limit. Check
    docker stats
    /
    docker inspect --format '{{.State.OOMKilled}}'
    ; bump
    deploy.resources.limits.memory
    (web needed 4G, 2G OOM'd).
  • Reload loop → a page hammering its own document (
    GET /x
    ×1000s, CPU pegged, blank body). Diagnose via request count in
    docker logs
    . Cause is usually an effect that navigates with the value it depends on in its deps.
  • Cache masks slow loads — the token-usage
    $facet
    is ~7s cold / ~400ms cached; bust the cache (uncached filter combo) to observe the real load.
  • Healthcheck false-unhealthy —
    kodus_web
    pings
    /
    (404 post-Next16) so it shows "unhealthy" while working.
  • HMR 噪声 → hydration/解析错误会在第二次干净重载后消失。
  • Turbopack 截断解析缓存(有效文件出现
    Unexpected eof
    )→
    touch
    该文件以强制重新读取。根因是
    CHOKIDAR_USEPOLLING=false
    +
    :delegated
    bind mount 捕获到了部分写入。
  • OOM → 重量级路由(charts + Turbopack)会超出 web 容器的内存限制。检查
    docker stats
    /
    docker inspect --format '{{.State.OOMKilled}}'
    ;提高
    deploy.resources.limits.memory
    (web 容器需要 4G,2G 时会 OOM)。
  • 重复加载循环 → 页面会疯狂请求自身的 document(
    GET /x
    × 1000 次,CPU 跑满,正文空白)。可通过
    docker logs
    中的请求数量进行诊断。常见原因是某个 effect 在导航时使用了它所依赖的值,并将该值放入 deps。
  • 缓存会掩盖慢加载——token-usage 的
    $facet
    冷加载约需 7 秒,缓存命中时约需 400 毫秒;请绕过缓存(使用未缓存的过滤条件组合)以观察真实加载耗时。
  • 健康检查误报不健康——
    kodus_web
    会探测
    /
    (Next16 之后返回 404),因此即使应用正常运行也会显示 "unhealthy"。

Reference

参考

See memory
reference_perf_debug_flow
for the condensed version and
project_frontend_perf_next16
for the concrete fixes shipped with this flow.
有关该流程的精简版,请参阅 memory
reference_perf_debug_flow
;有关该流程已实际交付的具体修复,请参阅
project_frontend_perf_next16
。