Redis Streams 消费组把新条目分配给具名消费者,并记录尚未确认的投递。它不保证业务副作用恰好发生一次,不会阻止保留策略删除数据,也不会自行恢复被遗弃的任务。将其用作工作队列前,应区分组的读取位置、待处理条目与流里实际保留的数据。
流载荷、组位置与 PEL 分别变化。投递在确认前推进位置;确认只移除待处理引用。XAUTOCLAIM 转移归属,可能重复模拟副作用,也找不回已删除的载荷。图示来自原有实验。 读取位置与待处理任务不同 XGROUP CREATE jobs workers 0-0 从现有历史之前起步;使用 $ 则从当前末端之后开始。同组成员分担新投递,不同消费组拥有各自独立的投递状态。并发工作进程应使用不同的消费者名字。最后投递位置属于整个组,并不是每个消费者分别提交一个偏移。
未使用 CLAIM 或 NOACK 的普通 XREADGROUP 以 > 请求新条目,投递后将它们记入待处理列表 PEL。组的位置在新条目投递时就推进,发生在 XACK 之前。XPENDING 统计的是已经投递但尚未确认的条目,不是从来没有投递过的积压。因此,待处理计数为零时,组里仍可能有大量未读工作。
XACK 把指定 ID 从该组 PEL 移除,不删除流里的记录,也不推进最后投递位置。确认可以乱序完成,其他组依然可以读取保留下来的条目。XREADGROUP 使用 0-0 时读取的是这个具名消费者自己的待处理历史,不是在请求新工作,也不会把另一位消费者名下的待处理消息交给它。
恢复需要显式决策 反复执行普通 > 读取,不会重试被遗弃的 PEL 条目。XAUTOCLAIM 能把满足条件的待处理条目转给请求方,参数必须包含流、组、消费者、最小空闲毫秒数和起始 ID。之后应使用返回游标继续扫描,直到返回 0-0。某一批认领结果为空,并不代表扫描已经完成;过一段时间再次扫描,原来尚未达到空闲阈值的条目也可能变得可认领。
...
自动清理资格不等于执行完成
繁忙表持续产生旧行版本,维护速度需要与这种产生速度匹配。降低自动回收门槛,可以让表更早具备被处理的资格,但“符合条件”并不等于“已经完成”。工作者仍需轮到这张表、获得所需锁,并在积压继续增长之前完成足够的有效清理。调优应观察这个完整循环,不能把一个参数值当成后台吞吐的保证。
坏掉的样子 普通更新和删除的触发条件,由固定门槛加上表行数估计乘比例因子构成;PostgreSQL 18 在启用上限时,还会应用最大触发门槛。插入触发清理、由事务年龄驱动的冻结,又有各自的运行原因。因此,只看一个死元组比例,会漏掉其他维护需求;每看见一次清理就断言更新删除门槛被触发,也可能误判真正原因。
把门槛调低,不能释放仍被旧快照需要的版本,也不能创造额外的磁盘吞吐。增加工作者或降低成本延迟,还可能加剧与前台查询的资源竞争。让死元组估计在每个瞬间都接近零,既不是通用的现实目标,也不能充分说明业务性能良好。真正需要维持的是可持续的清理能力、可接受的文件增长、请求延迟和维护成本。
2050 到 150 的计算要求有效上限禁用或至少为 2050。资格、自动执行与有效完成仍分开;运行待验证。 快速验证 本轮没有重跑原样保留的后台工作者集成。先前的自动清理观察属于历史记录,手动或单用户清理不能代替它。
使用 PostgreSQL 18 可丢弃实例,确认自动回收与统计收集已启用、有可用工作者,并使用正常的一分钟数据库检查间隔。通过一个全新的 psql -X -v ON_ERROR_STOP=1 连接执行下面的脚本。它最多等待一百三十秒,核对真实自动执行;如果超时,应把这次观察记为失败,不能手动清理一次就冒充自动任务已经运行。
CREATE SCHEMA churn_demo; CREATE TABLE churn_demo.jobs (id integer PRIMARY KEY,n integer) WITH (autovacuum_enabled=false, autovacuum_vacuum_threshold=50, autovacuum_vacuum_scale_factor=0.2); INSERT INTO churn_demo.jobs SELECT g,0 FROM generate_series(1,10000) AS g; SELECT pg_stat_force_next_flush(); VACUUM (ANALYZE) churn_demo.jobs; UPDATE churn_demo.jobs SET n=1 WHERE id<=1000; SELECT pg_stat_force_next_flush(); SELECT n_live_tup,n_dead_tup,vacuum_count,autovacuum_count FROM pg_stat_user_tables WHERE relid='churn_demo.jobs'::regclass; SELECT 50+0.2*reltuples AS old_threshold,50+0.01*reltuples AS new_threshold FROM pg_class WHERE oid='churn_demo.jobs'::regclass; ALTER TABLE churn_demo.jobs SET (autovacuum_enabled=true,autovacuum_vacuum_scale_factor=0.01); DO $$ DECLARE deadline timestamptz := clock_timestamp()+interval '130 seconds'; BEGIN LOOP PERFORM pg_stat_clear_snapshot(); EXIT WHEN (SELECT autovacuum_count>0 FROM pg_stat_user_tables WHERE relid='churn_demo.jobs'::regclass); IF clock_timestamp()>deadline THEN RAISE EXCEPTION 'no automatic vacuum observed within the experiment budget'; END IF; PERFORM pg_sleep(0.25); END LOOP; END $$; SELECT pg_stat_clear_snapshot(); SELECT n_dead_tup,last_autovacuum IS NOT NULL AS ran,autovacuum_count>0 AS counted FROM pg_stat_user_tables WHERE relid='churn_demo.jobs'::regclass; SELECT count(*),count(*) FILTER (WHERE n=1) FROM churn_demo.jobs; DO $$ BEGIN IF (SELECT count(*) FROM churn_demo.jobs)<>10000 OR (SELECT count(*) FROM churn_demo.jobs WHERE n=1)<>1000 THEN RAISE EXCEPTION 'maintenance changed application data'; END IF; END $$; DROP SCHEMA churn_demo CASCADE; 测试表最初只关闭自己的常规自动回收,让初始化与受控更新先完成。手动清理并分析建立统计,同时清掉初始化插入积压,然后再更新一千行。显式统计刷新调用用于同步这个短实验,避免待发布的写入增量落在手动维护更新估计值之后;它不是建议在线业务频繁调用的调优开关。
...
Cordon 与 drain 协调节点维护
Cordon 改变调度资格,drain 协调驱逐并等待 Pod 终止。两条命令都不保证维护期间工作一定保留;应用关闭逻辑、替代容量、存储和中断预算共同决定结果。应把排空视为需要观察的状态,并记录明确例外,而不是从命令名称推导节点已经可以安全关机。
Cordon 是放置限制,不证明节点已清空。Drain 不是原子事务,也不证明写入或替代副本成功。本轮没有执行原文保留的诊断命令。 Cordon 改变未来放置 Cordon 设置节点的不可调度字段,已有 Pod 继续运行,普通的新调度会避开它。Drain 自己也会执行 cordon,因此单独先做一次可以留下观察窗口,但不是必需步骤。Uncordon 重新允许调度,不保证立即出现新副本,也不会修好不健康的 kubelet;恢复前仍应核对节点状态与应用准备情况。
这项调度限制存在例外。DaemonSet Pod 容忍不可调度条件,直接填写 nodeName 的 Pod 则绕过普通调度选择。具有相应容忍度的其他 Pod 也可能符合放置条件。因此,需要检查真实剩余负载,不能把 cordon 说成阻止所有 Pod 到来的防火墙,更不能只凭不可调度字段就判断节点已经清空。
...