PostHog Replay Vision 图像清洗 Sidecar 深度解析:原生 ML 管线、Kafka 背压与去标识化工程设计 📅 发布时间:2026/9/13 20:36:49 👁 浏览次数: PostHog Replay Vision 图像清洗 Sidecar 深度解析原生 ML 管线、Kafka 背压与去标识化工程设计【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog导读本文以 PostHog 仓库中 Replay Vision ML Mirror Image Scrub Sidecar 说明文档 为主体结合其源码、消费者实现与构建文件完整拆解这套「会话回放Session Replay图像清洗」系统的设计它如何用一套原生 ML 管线ONNX Runtime zxing-wasm sharp/libvips把每帧回放截图中的人脸、文本、二维码/条形码做不可逆的实心填充如何以「等待即背压」的消费模型对抗 Kafka 分区级 head-of-line 阻塞以及如何用「自验证测试」保证清洗质量可度量。读完本文你将掌握该 sidecar 的 HTTP 契约、死信判定规则、分辨率规划算法、环境变量矩阵与可观测性信号可直接对照仓库源码部署或二次开发。一、系统定位为什么需要一个独立的 Sidecar该组件全称ML Mirror Image Scrub Sidecar是「会话回放 ML 镜像」链路replay_vision的 image-scrub 阶段的一部分。它被设计为与 Kafka 图像清洗消费者同 Pod 部署的边车容器消费者位于 ingestion-session-replay-ml-image-scrub-server.tssidecar 自身则是一个独立 npm 包posthog/ml-mirror-image-scrub-sidecar见 package.json。1.1 与根工作区刻意隔离的原因包说明文档明确写出该包有意不纳入根 pnpm workspacepnpm-workspace.yaml之外因为 ML 依赖体积达数百 MB不会拖慢每次 CI 构建不会污染每个开发者的工作树不会把onnxruntime-node、sharp等重型原生二进制带进主 plugin-server 镜像。从 Dockerfile.ml-mirror-image-scrub 可以看到其独立构建路径只 COPY 该包自身的package.json与pnpm-lock.yaml以pnpm install --prod --frozen-lockfile独立安装使用node:24.13.0-bookworm-slim基础镜像并精确锁定 Node 版本。1.2 双监听器架构信任边界从网络开始sidecar 同时起两个 HTTP 服务见 server.ts监听器绑定地址端口默认职责/scrub127.0.0.1仅回环9010IMAGE_SCRUB_PORT接收原始图像字节返回清洗后字节/metrics/_health/_ready0.0.0.0全部接口9011IMAGE_SCRUB_METRICS_PORTPrometheus 抓取与 kubelet 探针不暴露任何图像字节/scrub是完全受信任的接口——它只与同 Pod 内的 Kafka 消费者通信因此必须作为共享网络命名空间的 sidecar 运行绝不能单独部署为独立 Service。代码中注释明确loopback 监听器绝不能暴露到 Pod IP 之外Loopback only: the consumer shares the pod netns; the pod IP must not expose /scrub。二、HTTP 契约与状态码语义2.1 核心接口POST /scrub请求体为原始图像字节content-type: application/octet-stream成功时返回 200 与清洗后字节。状态码的拆分是承重的load-bearing消费者与 sidecar 两侧必须同步修改见消费者半边的 scrub-client.ts状态码含义消费者行为200带字节清洗成功返回字节写入 S3200空 body极端情况清洗产物为空非契约错误按可重试处理避免一张图导致全 Pod crash-loop413请求体过大默认上限20 MiB永久跳过不重试422无法解码 / 元数据禁止 AI 训练XMP opt-out永久跳过500任务超时后 worker 被替换considered answer等待并重试503繁忙并发上限被触发提前拒绝等待并重试408/429/其他 5xx暂时不可服务等待并重试其余状态码未预期契约如错误SIDECAR_URL导致的 404立即抛错失败 batch绝不静默等待2.2 繁忙时的 503 是如何提前发送的server.ts 中的shedIfBusy中间件在 body 解析之前就检查inFlight maxConcurrency先req.resume()排空请求体再回 503——否则 Node 会在响应完成时销毁未读请求体的 socket消费者看到的是 reset 而非 503导致过载与sidecar 崩溃无法区分maxConcurrency ADMITTED_PER_WORKER(2) × SCRUB_WORKERS每 worker 预留 1 个额外槽位喂饱请求体读取间隙绝不让请求进入无界 accept 队列。2.3 为什么排队对消费者是失联而非繁忙消费者的每次请求超时是非活跃超时inactivity timeout排队的请求不产生任何字节会让消费者以为 sidecar 无响应。因此 sidecar 选择「提前拒绝、快速 503」这是消费者唯一能据以行动的繁忙信号。三、等待即背压消费侧的可靠性模型这是整个系统最核心的设计思想文档用一句话概括一个繁忙的 sidecar 会被一直等待绝不放弃没有任何图像会因容量不足而被丢弃。3.1 为什么永不限时重试Kafka 已经持久保存了图像有界重试唯一买到的是「把日志里安全的数据丢掉的机会」而消费得慢一点只付出 lag 的代价——那正是 topic 存在的意义。因此没有 batch 时间上限没有丢弃路径poll 到的一批消息必须在任何 offset 前进之前全部完成等待本身就是背压batch 花在卡死 sidecar 上的时间越长下一次consume()就越晚消费者自动按 sidecar 实际吞吐自我限速无需显式 pause 分区一个 wedged卡死的 sidecar 依然会阻塞其分区而不是排空它任何 batch size 都无法改变这一点。3.2 小 batch 的由来由于 batch 无时间上限其时长由包含的图像数量决定所以本 lane 用很小的CONSUMER_BATCH_SIZE默认50主默认是 500。原因batch 存活超过max.poll.interval.ms300s会让 Pod 在 batch 中途被逐出eviction而这不是干净的失败重试——被逐出的 Pod 丢失已处理工作的 offset分区落到的下一个 Pod 其 sidecar 同样繁忙会重做同样的图像导致已提供的负载上升而吞吐下降。见 ingestion-session-replay-ml-image-scrub-server.ts 中buildImageScrubConsumerConfig的注释——batch size 被写死在配置里fetchBatchSize: config.SESSION_RECORDING_ML_IMAGE_SCRUB_BATCH_SIZE而不是作为 deployment 值防止与设计脱节。3.3 中途 revoke 的处理如果 batch 中途发生分区 revokebatch 会在 flush 发现不再拥有该分区时立即停止而不是继续清洗并为一个新 owner 已在写入的 span 再写一个 shard。3.4 心跳策略本 lane 的 batch 会阻塞 poll 循环长达数分钟scrub S3 写可能远超常规心跳间隔因此每10 秒手动刷新一次心跳BATCH_HEARTBEAT_INTERVAL_MS 10_000必须低于CONSUMER_MAX_HEARTBEAT_INTERVAL_MS30s。四、死信队列什么才配叫毒图像4.1 分区级阻塞问题一张永远以同样方式失败的图像会卡住其分区头部head of line而分区内混着所有团队的记录且图像字节是用户可控的——也就是说任何人一张恶意图片都能制造全局停滞。因此这样的图像被停放到session_replay_image_scrub_dlq分区继续前进。4.2 DLQ 内容的三个铁律只放原始字节绝不放已清洗字节该 topic 持有未脱敏内容任何下游都不得把它当已清洗数据处理保留它就是目的丢弃等于销毁 sidecar bug 唯一的复现样本。30 天保留期就是修复 sidecar 并重放的窗口源 topic 7 天同样的集群、同样的清理策略两个 topic 都不设置cleanup.policy走 Kafkadelete默认值段文件自然老化。被停放的图像之所以比源副本多活 23 天正是这个窗口的宽度。停放原因通过headers传递因此无需读取图像内容即可分诊整个 topic。4.3 判定逻辑别的图成功你才被怪罪什么让一张图是毒而不是倒霉是这里唯一重要的问题。饱和时每张图都等很久所以任何以等待时长或失败次数为唯一依据的判定都会在积压期把整条流停掉——这正是「等待」机制要阻止的大规模丢失只不过换了一扇门进来。判定条件见 scrub-client.ts只有「经过考虑的答案」considered answer才计入怪罪即rejected原因sidecar 收下了图、看了它、却产不出字节500503 是 sidecar 拒绝看declining to look at allrefused/reset socket 对每张图都成立——这两类都不怪罪必须同时满足可怪罪失败数 ≥POISON_MIN_FAILURES12期间其他图成功数 ≥POISON_MIN_OTHER_SUCCESSES3证明 sidecar 是健康的或者rejected累计时间 ≥POISON_MAX_REJECTED_MS120s含退避作为无 peer 可作证时的兜底出口。POISON_MIN_OTHER_SUCCESSES 3是刻意的小数且必须低于 Pod 的 scrub 并发batch 中任何一张图在途时 batch 无法结束Pod 在 batch 结束前无法 poll 新工作所以能到达的成功只可能来自与这张图并行的少数几个槽位。要求更多会使 batch 后段的图像永远无法达到门槛——batch 永不返回Pod 完全停止消费却仍报 Ready。ImageBatcher在构造时会校验该关系未来并发数改动会在启动时失败而非在流量中失败。4.4 两个超时的顺序是承重的sidecar 先放弃它无法完成的任务IMAGE_SCRUB_JOB_TIMEOUT_MS默认 15s退休该 worker回答 500——这是关于这张图的考虑性答案使它可以被怪罪和停放消费者等待更久SESSION_RECORDING_ML_IMAGE_SCRUB_SCRUB_TIMEOUT_MS默认 45s覆盖 job 期限 admission 队列等待确保答案真的到达颠倒顺序会重新打开漏洞消费者先放弃 → 无法处理的图像每次尝试都只是看起来慢永远不获怪罪永远卡住共享分区头部。此时超时的含义才是它应有的sidecar 什么都没说对每张图都成立因此不可怪罪。4.5 各原因退避上限与指标scrub-client.ts 中按原因区分退避上限指数退避 全抖动避免同 Pod 多张在途图锁步重发原因退避上限busy50330stimeout5srefused5sreset5stransport5srejected30srefused 端口上无人监听boot 后即意味着 image-scrub 容器已退出因其常见原因是同 Pod 内 sidecar 重启加载模型退避短5s以免白等半分钟reset 连接被接受后又被丢弃——sidecar 关闭时对空闲 keep-alive socket 就是这么做的所以每次 rollout/缩容都会出现 resets不可达告警只选择refused和transport绝不选resettransport 其他任何 socket 失败在 rollout 之外持续出现就是需要调查的故障。4.6 发布失败的处理停放发生在 ref 标记之前、slot 退休之前因此发布失败时图像原地不动仍未清洗、仍未提交。发布持续失败会被重试而非抛出——因为 Kafka 循环在 batch 任何错误时都会退出进程一个缺失/错集群/过小的 DLQ topic 会让整个 lane 的每个 Pod 在同一张图上 crash-loop。ml_mirror_image_scrub_consumer_dead_letter_failed_total指标即此状态表示「该看 topic 了而不是该看图像」。未配置 DLQ 目的地时客户端继续等待——因为唯一替代方案是丢弃。4.7 专属 producer slotDLQ 通过本 lane 自己的 producer slot 发布到 replay 集群源 topic 所在处其message.max.bytes是按这些负载定制的通用 slot 指向别处并带 librdkafka 的 1MB 默认值停放一张普通图像会在每次尝试都失败。topic 必须预先存在且max.message.bytes至少等于源 topic 的。回滚方式 清空SESSION_RECORDING_ML_IMAGE_SCRUB_DLQ_TOPIC客户端即恢复为等待行为。4.8 等待的边界哪些状态码值得等只有「重试可能改变答案」的状态才等待5xx、408、429、socket 失败。任何其他状态码、以及不带字节的 200都意味着 sidecar 在回答一个「我们没以为会问的问题」因此要大声地让 batch 失败。等待一个错误SIDECAR_URL的 404会把部署失误变成「不消费任何东西、通过所有探针、只以 lag 显现」的幽灵 Pod。五、清洗管线NSFW 门 人脸/文本/码实心填充advancedScrubscrub.ts按以下顺序处理一张图图像策略opt-outXMPplus:DataMining声明禁止 AI 训练时拒绝处理422规划尺寸planScalesscale-plan.ts在读任何像素之前仅凭源尺寸决定所有缩放——解码帧、每个检测器看到什么、存什么NSFW/gore 门nsfl nsfw概率 ≥NSFW_THRESHOLD默认 0.6刻意宽松这是安全网时整帧塌缩为 1×1 空白人脸填充每个 YuNet 检出的人脸用其平均色填充文本填充每个 DBNet 检出的文本区域同样填充边距按框高 字号代理缩放——只检测文本在哪从不读取文本码填充每个可解码的 QR/条形码zxing同样填充——TOTP 配置 QR、票券条码是面部/文本检测器看不见的机器可读 PII。目标保护数据标注人员、降低 PII 暴露面。它不需要完美——下文的自验证测试负责监督它。5.1 为什么是实心填充而不是模糊/马赛克模糊和马赛克是低通滤波器移除细节但保留粗结构。大字号文本标题、大标题对有能力的人依然可读——文档记录了一个实证一个 LLM 仍能读出测试页上模糊的标题和开头句子被马赛克的人脸也能被重新平滑回检测器可再次发现的样子。而实心、量化的平均色填充彻底移除信息人脸/文本/码统一处理。填充实现细节compose与encodeStoredscrub.ts填充色量化为每通道高 4 位24bit → 12bit携带更少的颜色信号边缘羽化只模糊填充的颜色层alpha 掩码保持硬边——框内原始像素永远不被泄露先填后缩encodeStored在两次独立 pass 中先完成所有填充再降采样存储尺寸。若先缩放后填充resize 核会越过框边缘把框内内容加权平均到框外 1px 处——实测在温和降采样下残渣可达到满强度纯色帧isUniform直接走快速路径跳过全部检测——回放里大量空白页加载/过渡/清空视图都是这种帧。5.2 分辨率规划一条规则决定所有尺寸核心规则每个检测器看到的主题必须比存储图保留的至少大ratio倍每轴。于是任何在产物中仍可读的内容当初必然大到足以被检出并填充。ratio不是拍脑袋选的而是从 floors.ts 实测的地板推导主题可检出尺寸检测器输入处可从存储图读出的尺寸绑定 ratio人脸64pxYuNet 输入21px存储图64/21 ≈3.05文本7pxDBNet 输入3px存储图7/3 ≈2.33码96px280px 才能解码无约束自限制SCRUB_SAFETY_FACTOR默认1.3是额外余量——两个地板都来自近黑底白字的一种字体低对比度文本会让检测地板向错误方向移动。SCRUB_OUT_MAX_PIXELS默认 50,000是大多数人唯一需要动的旋钮它就是存储尺寸其余一切随之推导——帧预算 stored × ratio²检测器必须看得足够大才能守住规则。过去曾把帧预算设成独立参数结果两个各自合理的设置组合成了一条欠脱敏的管线所以SCRUB_MAX_PIXELS仍保留为 override 但默认由推导得出。默认值下一张 1080p 截图存成约161×90。存储小是刻意的、且是大部分保证的来源下游消费者只需要判断会话发生在什么类型的站点需要场景结构而非可读性——产物中文本不可读正是目的而非代价。补充面积的预算而非长边上限使高页面保持可读的原生分辨率而不被压扁。人脸在 letterboxed绝不 squash的 640×640 输入上检测超过 3:1 宽高比的帧沿长轴分块平铺重叠窗口FACE_TILE_ABOVE 3、FACE_TILE_ASPECT 6使高页面上的脸不至于缩小到低于检测器最小尺寸。分块次数有上限scale-plan.ts 中FACE_MAX_TILES由推断次数上限约束。六、这是原生代码不是 JS 里的 ML所有模型推理与图像处理都运行在优化的原生库中TypeScript 只是编排 轻量输出解码在小的降采样检测图上而非整图阶段库原生引擎NSFW/gore 分类SwiftFormeronnxruntime-nodeONNX Runtime (C)人脸检测YuNetonnxruntime-nodeONNX Runtime (C)文本检测DBNet / PP-OCRv3onnxruntime-nodeONNX Runtime (C)QR/条码检测zxing-wasmzxing-cpp (C/wasm)resize / blur / composite / encodesharplibvips (C)不训练任何东西JS 里不跑任何神经网络唯一手写 JS 是模型输出解码DBNet 阈值 膨胀 连通域、YuNet anchor 解码 NMS、tensor 打包、掩码填充跑在小的检测图上不是瓶颈所有模型形态统一跑在一个ML runtimeonnxruntime-node上是有意的第二个 ML runtime 意味着第二个原生二进制兼容面、第二套失败模式Node 版本耦合、慢速 fallback 后端。6.1 并发与线程模型cores.tsonnxruntime-node的run会阻塞调用线程所以单线程进程无论多少请求在途都只能有一份推理在执行。设计如下SCRUB_WORKERS每个核心一个 worker 线程默认上限 32并受内存上限约束——onnxruntime的会话、zxing wasm、V8 isolate 都无法跨 isolate 共享内存随 worker 数线性增长线程数从cgroup CPU 配额推导读/sys/fs/cgroup/cpu.maxv2 /cpu.cfs_quota_usv1而非availableParallelism()后者报告主机核数对限额容器过度订阅一个数量级ORT_THREADS默认 cores / SCRUB_WORKERS每 worker 一个会话intra-op 线程数相乘必须适配配额否则以 CFS 节流偿还WORKER_HEAP_MB按每 worker 的帧预算推导实测约 240MB 固定 每百万像素约 240MB使 V8 isolate 按份额而非整容器限制成长Dockerfile 设OMP_NUM_THREADS1、UV_THREADPOOL_SIZE8libuv 在加载器代码之前创建线程池且永不扩容须与 worker 数匹配否则 sharp 阶段会在 worker 数之上重新串行化。七、目录布局与运行方式7.1 目录结构src/是生产代码进入 sidecar 镜像测试以*.test.ts同居并被rm -f src/*.test.ts从镜像中剥离dev/全部是非生产代码基准、评测框架、数据准备。生产代码绝不 importdev/。生产侧关键文件README 原文 代码确认src/main.ts入口——先起 worker 池再起监听器src/pool.ts推理 worker——派发、每 job 期限、替换死 workersrc/scrub-worker.ts一个 worker 线程——持有自己的 ONNX 会话一次清洗一张图src/worker-protocol.ts跨线程 job/reply 消息src/cores.ts从 cgroup CPU 配额与内存上限推导 worker 与 ORT 线程数src/server.ts/scrub/metrics监听器清洗实现注入src/config.ts/src/env.ts环境变量运行时配置与校验——无效数值拒绝启动never fail opensrc/blur.ts基线模糊与 blur.rs 保持同步供对照src/scrub.tsML 清洗管线src/yunet.ts/src/dbnet.ts/src/qr.ts三个检测器封装src/scale-plan.ts/src/floors.ts分辨率规划与实测地板src/safety.tsNSFW/gore 门src/smoke.ts镜像构建期冒烟测试src/metrics.tsPrometheus 注册表src/image-input.ts/src/xmp.ts接受的解码器、像素上限、内嵌元数据策略与 PLUS Data Mining 解析。7.2 环境变量矩阵sidecar 侧变量默认值范围含义IMAGE_SCRUB_PORT90101–65535/scrub回环监听端口IMAGE_SCRUB_METRICS_PORT90111–65535指标/探针监听端口IMAGE_SCRUB_MAX_BODY_BYTES20 MiB1024–512 MiB请求体上限413 之上即异常服务自持内存边界IMAGE_SCRUB_JOB_TIMEOUT_MS15_0001000–600_000worker 持有单图最长时间超时退休并回 500SCRUB_WORKERS派生min(核心数, 32, 内存上限)1–32推理 worker 线程数ORT_THREADS派生cores/workers1–32每个 ONNX 会话的 intra-op 线程NSFW_THRESHOLD0.60.05–0.95NSFLNSFW 组合门限PNG_LEVEL30–9sharp PNG 压缩级别低快、大TEXT_MARGIN_FRAC0.250–2顶部/侧边文本边距框高分数TEXT_MARGIN_BOTTOM_FRAC0.450–2底部额外边距覆盖 descender: g/y/p/q/jTEXT_MARGIN_MIN40–64小文本最小边距pxEDGE_BLUR40–32填充边缘羽化 sigma0硬边永不泄露SCRUB_OUT_MAX_PIXELS50_000—存储尺寸预算唯一大多数人该动的旋钮SCRUB_MAX_PIXELS派生—解码帧面积上限overrideSCRUB_SAFETY_FACTOR1.3—地板之上余量调大花更多 CPU 换召回PARALLEL_DETECT关1开启并行运行三个检测器低延迟但每 worker 占多核所有数值旋钮经numFromEnv校验env.ts解析为 NaN 会失败打开例如prob NaN恒为 false → 零文本框 → 未脱敏输出被静默计为已清洗因此任何数值在模块加载时必须是有限且范围内的值否则进程拒绝启动。7.3 消费者侧关键环境变量变量默认值含义SESSION_RECORDING_ML_IMAGE_SCRUB_SIDECAR_URLhttp://127.0.0.1:9010注意用 127.0.0.1 而非 localhostsidecar 绑定 IPv4 回环localhost 可能先解析到 ::1SESSION_RECORDING_ML_IMAGE_SCRUB_SCRUB_TIMEOUT_MS45_000单次请求超时必须 job 超时 队列等待SESSION_RECORDING_ML_IMAGE_SCRUB_BATCH_SIZE50每 poll 消息数界住 batch 墙钟时间默认主消费为 500SESSION_RECORDING_ML_IMAGE_SCRUB_DLQ_TOPICKAFKA_SESSION_REPLAY_IMAGE_SCRUB_DLQ停放 topic清空即回滚为等待行为SESSION_RECORDING_ML_IMAGE_SCRUB_GROUP_IDsession-replay-ml-image-scrub消费组SESSION_RECORDING_ML_IMAGE_SCRUB_SCRUB_CONCURRENCY8每 Pod 并发清洗数SESSION_RECORDING_ML_IMAGE_SCRUB_MAX_IMAGES/MAX_BYTES1000 / 128 MiBbatch flush 阈值SESSION_RECORDING_ML_IMAGE_SCRUB_DEDUP_MAX_REFS250_000每 Pod seen-ref LRUtopic 按 ref 键控重复分区亲和SESSION_RECORDING_ML_IMAGE_SCRUB_S3_WRITE_TIMEOUT_MS30_000每次 S3 写超时一次 flush 两次写总界 2×以上默认值来自 config.ts 的getDefaultMlMirrorConfig()。7.4 本地运行pnpm install --ignore-workspace # 独立包自己的 lockfile位于根 workspace 之外 npm run setup # 下载 ONNX 模型 示例测试图生成语料 npm run test:unit # 快速单元测试不依赖模型/网络 npm run eval # 清洗质量评测套件文本 人脸跑真实图像 npm run bench # 延迟 分阶段明细 npm run smoke # 模型加载 一次端到端清洗镜像构建时运行 npm run start # 启动 sidecar 服务需先 npm run setup 获取模型TLS 坑如果模型/数据下载报 TLS 链错误说明机器缺少中间 CA。将NODE_EXTRA_CA_CERTS指向完整 bundle如 certifi 的cacert.pem而不是禁用证书校验。7.5 镜像构建期冒烟测试Dockerfile.ml-mirror-image-scrub 用ADD --checksumsha256:...从 commit 固定的上游 URL 拉取三个 ONNX 模型与dev/setup.ts的 pin sha256 保持一致随后在--networknone下执行tsx src/smoke.tssmoke.ts走完整 worker 池而非直接调advancedScrub顺带证明 tsx 加载器能到达 worker 线程否则 Pod 永不 Ready输入必须有内容且结果被断言而非只查字节——纯色帧走 uniform 快速路径会跳过全部推理一个能加载但不能run的 ONNX 二进制就漏进生产了文本断言走的是最长路径DBNet → composite因此模型损坏、原生二进制不匹配、意外的运行时网络依赖都会让镜像构建失败而不是部署后 crash-loopsidecar 启动时不做任何网络抓取。八、自验证测试用 OCR 和重新检测监督清洗质量生产路径用 DBNet检测文本快测试用 tesseract OCR读取清洗输出一个做识别而非检测的不同模型并统计置信的多字符单词数。OCR 通常比人更能读退化文本所以「OCR 读不出」是「标注员读不出」的保守代理。人脸检查则在清洗输出上以高灵敏度重跑 YuNet断言原人脸位置按 IoU不再有任何脸——成功实心填充的脸已不可检测。套件对会话回放的典型域清晰渲染 UI 文本 人脸门禁gated对更难的扫描文档集报告reportUI TEXT (gated): 31/31 clean, 0.0% leak [PASS] # rendered screenshots DOCUMENT TEXT (report): 19/20 clean, 2.7% worst [report] # faint fax/scan print, out of domain FACE: 89/89 faces redacted (100%)8.1 语料为什么必须包含大图语料覆盖0.3 到 8.3 兆像素、两种 device pixel ratio。过去语料最高到 1440×900完全在帧预算之下没有任何东西在检测前被降采样套件因此看不见该上限的代价。加入显示器尺寸帧后立即以14.3% 泄漏打脸门禁4K 在 DPR1 时 14px 文本经两次降采样到 DBNet 处只剩约 6px。DET_SHARPEN关闭了这个漏洞上面的数字是在其开启状态下测得的。浅色、低对比度的传真扫描线偶尔会幸存——那是对比度限制而非尺寸限制分辨率提高救不了每条褪色线它属于域外rendered-UI 之外符合「尽力而为、漏一点不是灾难」的标准。调大SCRUB_SAFETY_FACTOR花更多 CPU 换召回它会放大每个检测器看到的帧帧预算由它派生。8.2 重推导地板用tsx dev/glyph-floor.ts文本与tsx dev/floors.ts人脸与码重新推导地板两者都从limitsFromEnv()读取几何因此不可能与随镜像发布的配置漂移。九、可观测性隐私控制的信号/metrics在 HTTP 结果计数器scrubbed/failed/undecodable/rejected/too-large/aborted、时长、输出字节之外携带隐私控制所需的结果信号..._blanked_total— NSFW 门空白是破坏性且不可逆的对速率尖峰告警..._faces_redacted_total、..._text_boxes_redacted_total、..._codes_redacted_total— 流量下持续为零意味着检测器故障未脱敏输出而不是干净的流。消费者侧关键指标ml_mirror_image_scrub_consumer_scrub_waits_total按reasonbusy/timeout/refused/reset/transport/rejected— 无字节返回且将被重试的尝试数是饱和信号绝不是丢失信号ml_mirror_image_scrub_consumer_stuck_images_total— 任何一张图被重试超过健康 sidecar 应完成的时间后反复自增读作水位而非一次性边缘事件ml_mirror_image_scrub_consumer_dead_lettered_total— 应保持零任何超过涓流的值都是 sidecar bug 在多张图上复现修复应落在 sidecar 里。9.1 探针设计stalled 的 Pod 看起来是健康的本 lane 运行遗留心跳健康检查CONSUMER_LOOP_BASED_HEALTH_CHECK未设置消费者每 10s 为整个 batch 刷新心跳因此一个被单图卡住的 Pod 会永远保持 Ready/Live。这是刻意的——重启它只会重放同一张图——但也意味着lag 和上面两个计数器是唯一证据因此该 lane 的告警都是 lag 形态的。Sidecar 侧探针的取舍server.ts/_health在无可用 workercanScrub() false时回 503——这是存活探针而非就绪探针推理在 worker 线程上丢了全部 worker 的 Pod 对这个监听器的响应速度与健康 Pod 无异没有它 kubelet 永远不会重启该 Pod/_ready恒回 200——失败就绪探针会把 Pod 从 Prometheus 抓取的 Service 中摘掉在指标最能解释问题的那一刻失去指标。9.2 什么都不能把消费者打挂消费者 Kafka 循环在 batch 任何错误时退出进程因此到达它的失败会赔上整个 Pod并把分区交给同样繁忙的 Pod扩散饱和。况且消费者重启本来就修不好 sidecar——那是同 Pod 内的独立容器会继续运行。十、对仓库的延伸阅读消费者侧完整实现scrub-client.ts、ingestion-session-replay-ml-image-scrub-server.ts配置与默认值config.tssidecar 生产源码src/含scrub.ts、scale-plan.ts、cores.ts、server.ts、smoke.ts等构建镜像Dockerfile.ml-mirror-image-scrub评测与基准dev/scrub-eval.ts、bench.ts、floors.ts、glyph-floor.ts、make-corpus.ts单元测试src/*.test.tsscale-plan.characterisation.test.ts、server.test.ts、pool.test.ts、cores.test.ts等该组件与 Rust 侧的回放匿名化器共享设计意图blur.ts明确注明与 blur.rs 保持同步作为对照基线。若要继续深入回放数据脱敏体系可从这两个文件对照阅读开始。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考