feat(worker): IO Worker 分道(WK-1)
io worker 进程内按任务声明的 lane 分文件 / 网络 / 维护三条道,各自并发(缺省 2/4/1)、 各自排队;维护道在文件道或网络道有排队/运行时让路(连续 10 分钟放行一个),调度器内联的 清理/回填/保留期裁剪也走维护道。并发在 设置 → 自动化 → 高级 热生效,env 覆盖;旧 WORKER_CONCURRENCY 映射到网络道。运行状态页按道显示运行中/并发/排队。归属表写进 docs/runbook/dev.md,ci 与注册表逐项核对。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
68f53390f6
commit
da33cad81d
38 files changed
+1240
-105
No files matched your search
@@ -10,7 +10,7 @@ import { type FormValues, num } from "./resource-section.tsx";
|
||||
import { anchorId, SettingsBlock } from "./settings-block.tsx";
|
||||
import { type CardSpec, n, SettingsCard, SettingsGate } from "./settings-card.tsx";
|
||||
|
||||
// 自动化分区:总开关、只读对账、缓存水位、每日下载预算、保留天数、存储预算与保留(CACHE-2)
|
||||
// 自动化分区:总开关、只读对账、缓存水位、每日下载预算、保留天数、后台任务并发(WK-1)、存储预算与保留(CACHE-2)
|
||||
|
||||
const GiB = 1024 ** 3;
|
||||
|
||||
@@ -106,6 +106,33 @@ const dayField = (group: "retentionDays" | "cacheTtl", k: string, label: string,
|
||||
const pick = (v: FormValues, prefix: string, keys: readonly string[]) =>
|
||||
Object.fromEntries(keys.map((k) => [k, num(v[`${prefix}.${k}`])]));
|
||||
|
||||
// WK-1:io worker 三条道的并发(settings.automation.workerLanes,docs/runbook/dev.md「IO Worker 分道」)
|
||||
const LANE_KEYS = ["file", "network", "maintenance"] as const;
|
||||
const WORKER_LANES_CARD: CardSpec = {
|
||||
anchor: "worker-lanes",
|
||||
title: "后台任务并发",
|
||||
description:
|
||||
"后台任务分三条道各自排队,互不占用。保存后几秒内生效,不用重启;服务器上设置了对应的环境变量时以环境变量为准。",
|
||||
fields: [
|
||||
{ name: "automation.workerLanes.file", label: "文件道", kind: "number", hint: "整理、硬链接、移动、校验;1–16" },
|
||||
{
|
||||
name: "automation.workerLanes.network",
|
||||
label: "网络道",
|
||||
kind: "number",
|
||||
hint: "站点、RSS、TMDB、下载器、搜索;单站限流另计;1–16",
|
||||
},
|
||||
{
|
||||
name: "automation.workerLanes.maintenance",
|
||||
label: "维护道",
|
||||
kind: "number",
|
||||
hint: "清理、回填、对账;前两道有活时让路;1–16",
|
||||
},
|
||||
],
|
||||
fromSettings: (s) =>
|
||||
Object.fromEntries(LANE_KEYS.map((k) => [`automation.workerLanes.${k}`, n(s.automation.workerLanes[k])])),
|
||||
toPatch: (v) => ({ automation: { workerLanes: pick(v, "automation.workerLanes", LANE_KEYS) } }),
|
||||
};
|
||||
|
||||
const BUDGET_KEYS = ["pgDataGb", "pgDataWarnGb", "imagesGb", "backupsGb", "sessionsEvidenceGb"] as const;
|
||||
const STORAGE_BUDGET_CARD: CardSpec = {
|
||||
anchor: "storage",
|
||||
@@ -212,7 +239,7 @@ function ToggleRow({
|
||||
);
|
||||
}
|
||||
|
||||
/** 高级:只读对账、存储预算、流水保留、缓存有效期;默认收起,从 #automation/advanced 进来时展开 */
|
||||
/** 高级:只读对账、后台任务并发、存储预算、流水保留、缓存有效期;默认收起,从 #automation/advanced 进来时展开 */
|
||||
function Advanced({ children }: { children: ReactNode }) {
|
||||
const location = useLocation();
|
||||
const [open, setOpen] = useState(() => location.hash.endsWith("/advanced"));
|
||||
@@ -232,7 +259,9 @@ function Advanced({ children }: { children: ReactNode }) {
|
||||
className={cn("size-4 text-muted-foreground transition-transform duration-base", open && "rotate-180")}
|
||||
/>
|
||||
</button>
|
||||
<p className="mt-1 text-caption text-muted-foreground">只读对账、存储预算、数据保留和缓存有效期。一般不用改。</p>
|
||||
<p className="mt-1 text-caption text-muted-foreground">
|
||||
只读对账、后台任务并发、存储预算、数据保留和缓存有效期。一般不用改。
|
||||
</p>
|
||||
{open ? <div className="mt-6 grid grid-cols-1 gap-8">{children}</div> : null}
|
||||
</section>
|
||||
);
|
||||
@@ -309,6 +338,7 @@ export function AutomationSection() {
|
||||
<SettingsCard section="automation" spec={RETENTION_CARD} settings={s} />
|
||||
<Advanced>
|
||||
<ReadOnlySwitch />
|
||||
<SettingsCard section="automation" spec={WORKER_LANES_CARD} settings={s} />
|
||||
<SettingsCard section="automation" spec={STORAGE_BUDGET_CARD} settings={s} />
|
||||
<SettingsCard section="automation" spec={STORAGE_RETENTION_CARD} settings={s} />
|
||||
<SettingsCard section="automation" spec={STORAGE_TTL_CARD} settings={s} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import type { HealthLevel, SystemHealth, SystemProcess } from "@umt/contracts";
|
||||
import type { HealthLevel, SystemHealth, SystemProcess, SystemWorkerLane } from "@umt/contracts";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ChevronDown,
|
||||
@@ -226,6 +226,8 @@ function Processes({ d }: { d: SystemHealth }) {
|
||||
cell: (p) =>
|
||||
p.concurrency.io === undefined && p.concurrency.agent === undefined ? (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
) : p.lanes.length ? (
|
||||
<LaneCell p={p} />
|
||||
) : (
|
||||
<span className="whitespace-nowrap tabular-nums">
|
||||
io {p.concurrency.io ?? 0} · 智能体 {p.concurrency.agent ?? 0}
|
||||
@@ -261,6 +263,30 @@ function Processes({ d }: { d: SystemHealth }) {
|
||||
);
|
||||
}
|
||||
|
||||
const LANE_LABEL: Record<SystemWorkerLane["lane"], string> = { file: "文件", network: "网络", maintenance: "维护" };
|
||||
|
||||
/** io worker 分道(WK-1):每道一行「运行中/并发 · 排队」,维护道让路时标出来 */
|
||||
function LaneCell({ p }: { p: SystemProcess }) {
|
||||
return (
|
||||
<div className="grid justify-items-end gap-0.5 text-meta">
|
||||
{p.lanes.map((l) => (
|
||||
<span
|
||||
key={l.lane}
|
||||
className="whitespace-nowrap tabular-nums"
|
||||
title={l.source === "env" ? "并发由环境变量指定,设置页的值不生效" : undefined}
|
||||
>
|
||||
{LANE_LABEL[l.lane]} {l.running}/{l.concurrency}
|
||||
{l.queued ? ` · 排队 ${l.queued}` : ""}
|
||||
{l.yielding ? " · 让路中" : ""}
|
||||
</span>
|
||||
))}
|
||||
{p.concurrency.agent !== undefined ? (
|
||||
<span className="whitespace-nowrap tabular-nums text-muted-foreground">智能体 {p.concurrency.agent}</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** 已下线实例:已退出、容器已重建或被同容器新进程替换的旧心跳行,默认折叠,不参与告警 */
|
||||
function RetiredProcesses({ rows }: { rows: SystemProcess[] }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
Reference in new issue
Block a user