Commit aeb6f9f5 by luoqi

fix(monitor): push 断流告警加显式开关 —— FRIDAY 联调期关掉,别把真告警淹了

现象:FRIDAY 每小时报一次 [PAC CRITICAL] push 断流(28.9h / 29.9h 无推送),
但它根本还没正式上线推送。

根因在触发条件 —— 监控只跳过"从未推过"的宿主(!lastPush → continue),
而 FRIDAY 联调期推过几次就停了,于是被当成"已上线、现在挂了"。
"推几次就停"恰恰是联调的正常节奏,不是数据在漏。

加 monitoring.push_lag_alert(默认 true):
  - FRIDAY manifest 置 false,并写明**正式上线推送后删掉该段即恢复**
  - 不配 → true,既有宿主行为不变,不会静默失去监控
  - 命中时打一行 log( 已按 manifest 关闭),不是无声跳过

【为什么不用"把阈值调大"】那样语义是"容忍 10 万小时不推",读的人分不出是故意关掉
还是填错了;显式布尔把意图留在 yaml 里,上线时删一行即可。

测试 818 项(+3)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent caa2f312
Pipeline #3518 failed in 0 seconds
......@@ -21,6 +21,13 @@ host_name: friday
display_name: FRIDAY SaaS(多品牌市场)
auto_sync: false
# 监控:push 断流告警**联调期关闭**。
# 触发条件本是"曾经推过"(有 push SyncLog)—— 但 FRIDAY 目前是联调,推几次就停是正常节奏,
# 不是"链路挂了、数据在漏"。开着会每小时报一次 critical(2026-08-01 实测连报数轮),
# 把真告警淹掉。⚠️ 宿主**正式上线推送后删掉这段**(或置 true)以恢复监控。
monitoring:
push_lag_alert: false
tenant_id: friday-market # PAC 租户 = 合成市场根(静态)
identity_namespace_field: tenant_id # patients.source_unit ← 源品牌列(宿主原生列名;与 PAC 租户概念的区分在 PAC 侧,宿主不改名)
......
......@@ -566,6 +566,8 @@ export class ColdImportService {
dwLagWarnHours: number | null;
dwLagErrorHours: number | null;
pushLagErrorHours: number | null;
/// push 断流告警开关(manifest monitoring.push_lag_alert;缺省 true)。联调期宿主关掉它。
pushLagAlert: boolean;
/// 是否 pull/DW 宿主 —— manifest 声明了 sql_source(与 files 二选一)才由 PAC 按游标去数仓拉。
/// push / 文件宿主没有游标可推进,DW 滞后监控对它们不适用(见 dw-lag-monitor)。
hasSqlSource: boolean;
......@@ -576,6 +578,7 @@ export class ColdImportService {
dwLagWarnHours: null,
dwLagErrorHours: null,
pushLagErrorHours: null,
pushLagAlert: true,
hasSqlSource: false,
};
try {
......@@ -586,6 +589,7 @@ export class ColdImportService {
dwLagWarnHours: m.monitoring?.dw_lag_warn_hours ?? null,
dwLagErrorHours: m.monitoring?.dw_lag_error_hours ?? null,
pushLagErrorHours: m.monitoring?.push_lag_error_hours ?? null,
pushLagAlert: m.monitoring?.push_lag_alert ?? true,
hasSqlSource: !!m.sql_source,
};
} catch {
......
......@@ -179,6 +179,11 @@ export const ColdImportManifestSchema = z
/// push 断流告警(push host):超过此小时数无 push → critical。
/// 实时推的 host 设小(如 3),每晚批量推的设大(如 26)。
push_lag_error_hours: z.number().positive().optional(),
/// push 断流告警总开关(默认 true)。**联调期宿主置 false**:
/// 监控的触发条件是"曾经推过"(有 push SyncLog),但联调阶段推几次就停是正常节奏,
/// 不是"链路挂了、数据在漏" —— 那样每小时报一次 critical,真故障反而被淹没。
/// 宿主正式上线推送后删掉本行(或置 true)即恢复监控。
push_lag_alert: z.boolean().optional().default(true),
})
.optional(),
......
......@@ -125,7 +125,14 @@ export class DwLagMonitorService {
const gErrorH = Number(process.env.PAC_PUSH_LAG_ERROR_HOURS ?? '26');
for (const host of hosts) {
// 每宿主阈值:实时推的 host 设小(断几小时就该报),批量推的设大;缺省回退全局
const errorH = this.coldImport.getHostOpsConfig(host.name).pushLagErrorHours ?? gErrorH;
const ops = this.coldImport.getHostOpsConfig(host.name);
// 联调期宿主 manifest 置 push_lag_alert: false —— "推过几次就停"是正常节奏,
// 不是链路故障;每小时报一次 critical 会把真告警淹掉。
if (!ops.pushLagAlert) {
this.logger.log(`push-lag: ⏸ host=${host.name} 告警已按 manifest 关闭(联调期)`);
continue;
}
const errorH = ops.pushLagErrorHours ?? gErrorH;
const lastPush = await this.prisma.syncLog.findFirst({
where: { hostId: host.id, direction: 'push' },
orderBy: { startedAt: 'desc' },
......
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import * as yaml from 'js-yaml';
import { ColdImportManifestSchema } from '../src/modules/sync/cold-import/manifest.schema';
/**
* push 断流告警开关(monitoring.push_lag_alert)。
*
* 【为什么需要】监控的触发条件是"曾经推过"(有 push SyncLog),对**联调期**宿主不成立:
* 推几次就停是正常节奏,不是"链路挂了、数据在漏"。2026-08-01 FRIDAY 就连报了数轮
* critical(28.9h / 29.9h 无推送),而它根本还没正式上线推送 —— 这类噪音会把真告警淹掉。
*
* 【为什么不用"把阈值调大"糊弄】那样语义是"容忍 100000 小时不推",读代码的人无法分辨
* 这是"故意关掉"还是"填错了"。显式布尔让意图留在 yaml 里,且宿主上线时删一行即恢复。
*/
const readManifest = (host: string) =>
ColdImportManifestSchema.parse(
yaml.load(readFileSync(join(__dirname, `../data/${host}/manifest.yaml`), 'utf-8')),
);
describe('push_lag_alert', () => {
test('⭐ FRIDAY 联调期显式关闭 —— 上线推送后要删掉这行', () => {
expect(readManifest('friday').monitoring?.push_lag_alert).toBe(false);
});
test('不配 → 默认 true(既有宿主行为不变,不会静默失去监控)', () => {
const parsed = ColdImportManifestSchema.parse({
host_name: 'x',
tenant_id: 't1',
amount_unit: 'yuan',
timezone: 'UTC',
assemblers: [{ file: 'assemblers/x.yaml' }],
tables: [{ table: 't', file: 't.csv' }],
monitoring: { push_lag_error_hours: 26 },
});
expect(parsed.monitoring?.push_lag_alert).toBe(true);
});
test('整个 monitoring 段缺失也不报错(jvs-dw 是 pull 宿主,压根不涉及 push 断流)', () => {
const parsed = ColdImportManifestSchema.parse({
host_name: 'x',
tenant_id: 't1',
amount_unit: 'yuan',
timezone: 'UTC',
assemblers: [{ file: 'assemblers/x.yaml' }],
tables: [{ table: 't', file: 't.csv' }],
});
expect(parsed.monitoring).toBeUndefined();
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment