## [ERR-20260427-006] ADP API 429 限流 + Linker 未匹配统计方向反了

**日期**: 2026-04-27
**类别**: ADP API / 业务逻辑
**严重度**: 高（PTO 同步 158 用户中 44 个失败；linker 日志方向错给运维造成误导）

### 问题描述

第一次大规模 PTO 同步暴露两个问题：

#### 问题 A：429 限流（44/158 失败）
PTO sync 里 `for` 循环串行调用 `fetchTimeOffRequests`，无延迟、无 retry。158 个用户连发后 ADP 返回 429 = 44 条 errors：
```
user=... aoid=... fetch 失败: Request failed with status code 429
```
单用户测试不会触发（之前 1 个 user 的测试通过）。

#### 问题 B：Linker unmatched 方向反了
原逻辑：扫 FFAI 待 link User → 找不到 ADP 对应的算 unmatched。但 FFAI 用户量（221）> ADP 员工数（164），FFAI 多出来的本来就不该期望 ADP 有，列进 unmatched 没意义（215 条噪声）。

正确语义：unmatched 应该是「ADP 有员工记录但 FFAI 没对应 User」—— 这才是要找运维补 FFAI 用户的场景。

### 修复

#### A：SDK 加 429 retry + 同步层节流
```ts
// adp-api.service.ts: fetchTimeOffRequests 内部
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
  try { return await client.get(url); ... }
  catch (e) {
    if (e.response?.status === 429 && attempt < MAX_RETRIES) {
      const retryAfter = Number(e.response.headers['retry-after']);
      const waitMs = retryAfter > 0 ? retryAfter * 1000 : 2 ** (attempt + 1) * 1000;
      await sleep(waitMs);
      continue;
    }
    throw e;
  }
}
```
+ PTO sync 里循环间隔 200ms 节流。

#### B：Linker 改为 ADP 主导匹配
```ts
// 原：for FFAI user in targetUsers → 找 aoid
// 新：for [localPart, adp] in localPartToAdp → 找 FFAI user，找不到则 ADP-side unmatched
```

### 启示
- **任何对外 API 的批量调用都要预设 429 处理**：包括 retry-after 解析、指数退避、节流间隔。单元素测试不会暴露限流问题
- **"未匹配"这种统计要明确"以谁为基准"**：FFAI 一侧有更多用户是正常情况，反向才是真问题
- 测试覆盖里要包含两种 mock：(a) ADP 多 FFAI 少（暴露反向 unmatched），(b) FFAI 多 ADP 少（验证不会误报）

### 关键补充：ADP 限流官方参数（很反直觉）
ADP Web API Gateway 限流是**双重限制**且**单节点限制远低于总量**：

| Tier | 全 4 节点合计 | 单节点 |
|---|---|---|
| Default | 300/min, 50 concurrent | **75/min ≈ 1.25 req/s, 12 concurrent** |

ADP 文档原话："最常见的 429 原因是违反 single node concurrency limit"。

**含义**：即使全局 rate 看起来很宽松（5 req/s），**因为请求负载均衡到不同节点，连续请求路由不均时会撞单节点限制**。我们 158 个 user × 200ms = 5 req/s 全节点，但单节点瞬时可能 1.5-3 req/s（>1.25），就 borderline 触发 429。

**正确节流参数**：间隔 ≥ 800ms（≈ 1.25 req/s 全节点 → 任意单节点都 ≤ 1/s 安全）。158 user × 800ms = 126s，对后台 cron 任务完全可接受。

**永远别只看"总 rate"忽略单节点限制**。ADP 这种分布式 gateway 是常见模式（AWS API Gateway、Cloudflare Workers 等），都是同一道理。

---
