新鲜度的时间判据改成「距今多久」,与时区无关
容器跑在世界协调时上:2026-09-10 早上七点十分出的计划,快照里记的是 2026-09-09T23:10。拿本地日期算「晚于今天六点」会差八小时,把新鲜的快照判成过期。 顺带更正一处判断:出计划本来就在数据基座之后跑,不是前一晚。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3ec99a5462
commit
7ce2484abe
32
plan.py
32
plan.py
|
|
@ -862,20 +862,22 @@ class StaleSnapshot(RuntimeError):
|
||||||
def snapshot_fresh_enough(snap: dict) -> tuple:
|
def snapshot_fresh_enough(snap: dict) -> tuple:
|
||||||
"""这份快照够不够新,能不能当今天的计划直接下发。返回 (能不能, 一句原因)。
|
"""这份快照够不够新,能不能当今天的计划直接下发。返回 (能不能, 一句原因)。
|
||||||
|
|
||||||
为什么需要这道守卫(2026-09-10,台账 057): 出计划这一步现在是**前一晚**跑的
|
为什么需要这道守卫(2026-09-10,台账 057): 快照是出计划那一刻的定格,而实时装配读的是
|
||||||
(实测 09-09 23:10 出的 09-09 那份),而数据基座凌晨才更新公司深度评析、催化事件
|
此刻的证据线。实测两条路比对,代码序列一模一样,但候选卡名次、判决、依据这些字段会有
|
||||||
这些证据线。所以快照天生比实时装配旧半天 —— 实测两条路比对,代码序列一模一样,
|
实质差异 —— 快照出得越早、差得越多。拿一份过时的快照下发,等于让下游用旧判决。
|
||||||
但候选卡名次、判决、依据这些字段有实质差异。直接拿旧快照下发,等于让下游用昨晚的判决。
|
|
||||||
|
|
||||||
两条判据,都不满足就退回实时装配(也就是今天的行为,不会变坏):
|
两条判据,任一不满足就退回实时装配(也就是这次改动之前的行为,不会变坏):
|
||||||
· 代码版本要和现在跑的一致 —— 版本变了说明判决逻辑可能变了,昨天算的不算数
|
· 代码版本要和现在跑的一致。版本变了说明判决逻辑可能变了,用旧代码算的那份不算数。
|
||||||
· 生成时刻要晚于今天的界(默认早上六点)—— 保证它是在数据基座凌晨那批任务之后出的
|
正常情况下两者相同——出计划任务和接口进程跑的是同一份部署;只有"刚部署了新代码、
|
||||||
|
还没重新出计划"这个窗口里会不一致,而那正是该回落的时候。
|
||||||
|
· 生成时刻距今不超过 PLAN_SNAPSHOT_MAX_AGE_HOURS 小时(默认十六)。
|
||||||
|
|
||||||
**这道守卫要能松开,靠的是把出计划挪到数据基座之后**(比如早上七点十分,代码注释里
|
**时间判据用的是"距今多久",不是"晚于今天几点"**:容器跑在世界协调时上(实测
|
||||||
本来就是这么设计的),不是把界调低。界调低只会让旧快照蒙混过关。
|
2026-09-10 早上七点十分出的计划,快照里记的是 2026-09-09T23:10),拿本地日期算界会
|
||||||
|
差八小时,把新鲜的快照判成过期。距今多久与时区无关,怎么部署都不会算错。
|
||||||
"""
|
"""
|
||||||
import version
|
import version
|
||||||
min_hour = int(os.environ.get("PLAN_SNAPSHOT_MIN_HOUR", "6"))
|
max_age_h = float(os.environ.get("PLAN_SNAPSHOT_MAX_AGE_HOURS", "16"))
|
||||||
gen = str(snap.get("generated_at") or "")
|
gen = str(snap.get("generated_at") or "")
|
||||||
ver = str(snap.get("plan_version") or "")
|
ver = str(snap.get("plan_version") or "")
|
||||||
now_ver = ""
|
now_ver = ""
|
||||||
|
|
@ -892,10 +894,12 @@ def snapshot_fresh_enough(snap: dict) -> tuple:
|
||||||
g = dt.datetime.fromisoformat(gen)
|
g = dt.datetime.fromisoformat(gen)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False, f"快照的生成时刻认不出: {gen}"
|
return False, f"快照的生成时刻认不出: {gen}"
|
||||||
today_line = dt.datetime.now().replace(hour=min_hour, minute=0, second=0, microsecond=0)
|
age_h = (dt.datetime.now() - g).total_seconds() / 3600.0
|
||||||
if g < today_line:
|
if age_h > max_age_h:
|
||||||
return False, (f"快照出于 {gen},早于今天 {min_hour:02d}:00;"
|
return False, (f"快照出于 {gen},距今 {age_h:.1f} 小时,超过 {max_age_h:g} 小时的界;"
|
||||||
f"数据基座凌晨那批证据线更新它没赶上,本次按实时装配")
|
f"证据线可能已经变了,本次按实时装配")
|
||||||
|
if age_h < -1: # 生成时刻在未来:多半是两边时区不一致,别当新鲜的用
|
||||||
|
return False, f"快照的生成时刻 {gen} 在未来 {-age_h:.1f} 小时,判不了新旧"
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,18 +127,30 @@ def test_freshness_guard():
|
||||||
def snap(gen, ver=None):
|
def snap(gen, ver=None):
|
||||||
return {"generated_at": gen, "plan_version": cur if ver is None else ver}
|
return {"generated_at": gen, "plan_version": cur if ver is None else ver}
|
||||||
|
|
||||||
fresh = now.replace(hour=7, minute=30, second=0, microsecond=0)
|
# 时间判据是「距今多久」,不是「晚于今天几点」——容器跑在世界协调时上,
|
||||||
if now < fresh: # 早上七点半之前跑测试时,用「一分钟前」当新鲜样本
|
# 拿本地日期算界会差八小时,把早上七点十分出的快照判成过期(2026-09-10 实测踩到)
|
||||||
fresh = now - _dt.timedelta(minutes=1)
|
ok, why = plan.snapshot_fresh_enough(
|
||||||
ok, why = plan.snapshot_fresh_enough(snap(fresh.isoformat(timespec="seconds")))
|
snap((now - _dt.timedelta(hours=2)).isoformat(timespec="seconds")))
|
||||||
t("今天出的、版本对得上的快照可用", ok, why)
|
t("两小时前出的、版本对得上的快照可用", ok, why)
|
||||||
|
|
||||||
y = (now - _dt.timedelta(days=1)).replace(hour=23, minute=10)
|
ok2, why2 = plan.snapshot_fresh_enough(
|
||||||
ok2, why2 = plan.snapshot_fresh_enough(snap(y.isoformat(timespec="seconds")))
|
snap((now - _dt.timedelta(hours=30)).isoformat(timespec="seconds")))
|
||||||
t("昨晚出的快照不可用(数据基座凌晨那批它没赶上)", not ok2 and "早于今天" in why2, why2)
|
t("三十小时前的快照不可用", not ok2 and "距今" in why2, why2)
|
||||||
|
|
||||||
|
ok_edge, _ = plan.snapshot_fresh_enough(
|
||||||
|
snap((now - _dt.timedelta(hours=15.5)).isoformat(timespec="seconds")))
|
||||||
|
t("十五个半小时刚好在界内", ok_edge)
|
||||||
|
ok_edge2, _ = plan.snapshot_fresh_enough(
|
||||||
|
snap((now - _dt.timedelta(hours=16.5)).isoformat(timespec="seconds")))
|
||||||
|
t("十六个半小时超界", not ok_edge2)
|
||||||
|
|
||||||
|
ok_f, why_f = plan.snapshot_fresh_enough(
|
||||||
|
snap((now + _dt.timedelta(hours=5)).isoformat(timespec="seconds")))
|
||||||
|
t("生成时刻在未来的不可用(多半是两边时区不一致)", not ok_f and "未来" in why_f, why_f)
|
||||||
|
|
||||||
if cur:
|
if cur:
|
||||||
ok3, why3 = plan.snapshot_fresh_enough(snap(fresh.isoformat(timespec="seconds"), "deadbee"))
|
ok3, why3 = plan.snapshot_fresh_enough(
|
||||||
|
snap((now - _dt.timedelta(hours=2)).isoformat(timespec="seconds"), "deadbee"))
|
||||||
t("代码版本对不上就不可用(判决逻辑可能变了)", not ok3 and "代码版本" in why3, why3)
|
t("代码版本对不上就不可用(判决逻辑可能变了)", not ok3 and "代码版本" in why3, why3)
|
||||||
|
|
||||||
ok4, _ = plan.snapshot_fresh_enough(snap(""))
|
ok4, _ = plan.snapshot_fresh_enough(snap(""))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue