Skip to content

fix(replay): repair cleanup and add configurable cache TTL - #1400

Merged
ding113 merged 1 commit into
devfrom
replay-cache-ttl-cleanup
Aug 7, 2026
Merged

fix(replay): repair cleanup and add configurable cache TTL#1400
ding113 merged 1 commit into
devfrom
replay-cache-ttl-cleanup

Conversation

@ding113

@ding113 ding113 commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fix the PostgreSQL Replay cleanup query so the Drizzle timestamp encoder is used for the cutoff value.
  • Add the database-backed replayCacheTtlMinutes system setting, defaulting to 30 minutes with an inclusive 5-120 minute range.
  • Cap the Redis hot-layer TTL to the configured Replay window without allowing the setting to extend the existing REPLAY_TTL_SECONDS limit.
  • Preserve nested Drizzle/Postgres error details in scheduler logs and record cleanup batch/deletion progress.
  • Add the generated Drizzle migration, REST/OpenAPI contract, runtime cache/repository wiring, five-language UI, and focused tests.

Root Cause

The cleanup scheduler bound a raw JavaScript Date in this predicate:

WHERE expires_at < ${cutoff}

That raw SQL path did not select the expires_at timestamp encoder. postgres.js failed during parameter binding with:

TypeError: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Date

Drizzle then wrapped it as DrizzleQueryError: Failed query. This matches the report that hand-written expires_at < now() SQL succeeds while the application scheduler repeatedly fails, and explains why the previous log did not expose the database-level cause.

The fixed query uses:

sql.param(cutoff, replayPayloads.expiresAt)

Behavior

  • PostgreSQL determines the durable completed-Replay window from replayCacheTtlMinutes.
  • Redis uses min(REPLAY_TTL_SECONDS, replayCacheTtlMinutes * 60).
  • Changing the setting affects later writes only and does not rewrite existing unexpired rows.
  • Deployments temporarily reading an older schema fall back to 30 minutes.
  • Invalid REST values return REPLAY_CACHE_TTL_INVALID for localized UI handling.

Validation

  • Real PostgreSQL 18 cleanup fixture: 125 expired rows and 5 future rows.
  • First cleanup deleted 100; second cleanup deleted 25; final state was 0 expired and 5 future.
  • Migration verified DEFAULT 30 and NOT NULL.
  • Browser full-stack check verified default 30, save to 45 with HTTP 200, refresh persistence, and stored PostgreSQL value 45.
  • 390x844 mobile check verified the label, description, input, and unit do not overlap.
  • bun run lint: passed.
  • bun run typecheck: passed.
  • bun run openapi:check: passed.
  • bun run build: passed.
  • bun run test: 859 files passed, 2 skipped; 8399 tests passed, 13 skipped; 0 failures.
  • git diff --check origin/dev...HEAD: passed.

Production Recovery Note

This PR repairs future scheduler executions, but merging it will not immediately return the historical approximately 7GiB table/TOAST space to the host filesystem. Production recovery should still use a maintenance window: verify that the PostgreSQL backup is restorable, stop the application while keeping PostgreSQL running, confirm Replay is disabled, delete only expires_at < now() rows in fixed small committed batches with short pauses, and finish with VACUUM (ANALYZE) replay_payloads.

The existing scheduler deletes at most 500 rows every 10 minutes, so 32,404 rows require roughly 11 hours in the ideal case. Evaluate VACUUM FULL or an online rewrite separately only when host-level disk reclamation is required and the locking/operational cost is acceptable.

If Node RSS/heap still grows after Replay is disabled and the historical payloads are gone, investigate it as a separate memory issue.

Acceptance

Introduce replayCacheTtlMinutes (5-120 min, default 30) as a
database-backed system setting controlling how long completed
Replay payloads remain reusable in the PostgreSQL durable layer.
The Redis hot-layer TTL is capped to the same window so both
tiers expire in sync. This replaces the removed
REPLAY_COMPLETED_TTL_SECONDS environment variable, moving the
durable TTL into the admin-editable settings surface with full
validation, i18n labels, API schema, and UI input.

Also fix the replay cleanup query to bind the cutoff Date through
sql.param for correct PostgreSQL type coercion, and preserve
wrapped database error causes in the cleanup scheduler logging.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

新增 Replay 缓存 TTL 系统设置,默认值为 30 分钟,范围为 5–120 分钟。配置贯穿数据库、校验、仓储、API、管理界面、本地化文本和 Replay 运行时清理流程。

Changes

Replay 缓存 TTL

Layer / File(s) Summary
配置契约与数据库迁移
drizzle/*, src/drizzle/schema.ts, src/types/system-config.ts, src/lib/validation/*, src/lib/api/v1/schemas/*, src/lib/api-client/v1/openapi-types.gen.ts
新增 replay_cache_ttl_minutes 字段及类型声明。系统设置校验限制为 5–120 分钟的整数。
系统设置持久化与运行时映射
src/lib/config/*, src/lib/system-settings/*, src/repository/*, tests/unit/repository/*, tests/unit/proxy/stream-gate-mode-resolution.test.ts
系统设置缓存、仓储转换、旧列降级读取和代理运行时设置均支持该字段,并使用默认值回退。
设置保存接口与管理界面
src/actions/system-config.ts, src/app/[locale]/settings/config/*, src/app/api/v1/resources/system/router.ts, messages/*/settings/config.json, tests/api/v1/system/*, tests/unit/actions/*, tests/unit/settings/*
设置表单新增 TTL 输入和本地化提示。保存接口和错误处理支持专用校验错误码。
Replay TTL 运行时与清理日志
src/app/v1/_lib/proxy/replay/replay-store.ts, src/instrumentation.ts, tests/unit/proxy/replay-store.test.ts, tests/unit/instrumentation-replay-cleanup.test.ts
Redis 热层和 PostgreSQL 完成记录使用运行时 TTL。清理任务记录删除数量,并输出结构化错误信息。

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: brisbanehuang

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 标题准确概括了 Replay 清理修复和可配置缓存 TTL 两项主要变更,内容简洁明确。
Description check ✅ Passed 描述与变更内容一致,说明了根因、行为、验证结果、迁移、界面和测试。
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch replay-cache-ttl-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib/validation/schemas.ts (1)

24-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

使用 @/ 路径别名。

将此导入改为 @/lib/validation/replay-settings。仓库规则要求 TypeScript 导入使用 @/ 映射到 ./src/

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/validation/schemas.ts` around lines 24 - 28, Update the
replay-settings import in the validation schemas module to use the repository’s
`@/` alias, targeting `@/lib/validation/replay-settings` instead of the relative
path, while preserving the imported symbols.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/validation/schemas.ts`:
- Around line 24-28: Update the replay-settings import in the validation schemas
module to use the repository’s `@/` alias, targeting
`@/lib/validation/replay-settings` instead of the relative path, while preserving
the imported symbols.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a6718b1a-6334-46a6-afcc-e064c8c5c79f

📥 Commits

Reviewing files that changed from the base of the PR and between ccbad37 and 37e5062.

📒 Files selected for processing (34)
  • drizzle/0119_tiresome_banshee.sql
  • drizzle/meta/0119_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/settings/config.json
  • messages/ja/settings/config.json
  • messages/ru/settings/config.json
  • messages/zh-CN/settings/config.json
  • messages/zh-TW/settings/config.json
  • src/actions/system-config.ts
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/app/api/v1/resources/system/router.ts
  • src/app/v1/_lib/proxy/replay/replay-store.ts
  • src/drizzle/schema.ts
  • src/instrumentation.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/api/v1/schemas/system-config.ts
  • src/lib/config/env.schema.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/system-settings/proxy-runtime.ts
  • src/lib/validation/replay-settings.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.test.ts
  • src/repository/_shared/transformers.ts
  • src/repository/system-config.ts
  • src/types/system-config.ts
  • tests/api/v1/system/system-config.test.ts
  • tests/unit/actions/system-config-save.test.ts
  • tests/unit/instrumentation-replay-cleanup.test.ts
  • tests/unit/proxy/replay-store.test.ts
  • tests/unit/proxy/stream-gate-mode-resolution.test.ts
  • tests/unit/repository/system-config-degradation-ladder.test.ts
  • tests/unit/repository/system-config-update-missing-columns.test.ts
  • tests/unit/settings/system-settings-form-replay-cache-toggles.test.tsx
💤 Files with no reviewable changes (1)
  • src/lib/config/env.schema.ts

@ding113
ding113 merged commit b37db15 into dev Aug 7, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Aug 7, 2026
@ding113 ding113 mentioned this pull request Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant