fix: preserve snapshot version across epoch changes
This commit is contained in:
parent
6f5a2faea6
commit
596e4ab371
@ -102,6 +102,10 @@ Worker 自报的超前版本或 epoch 也必须拒绝。
|
|||||||
和停止新分配的 `usable_until`。
|
和停止新分配的 `usable_until`。
|
||||||
- 内容 `checksum`。
|
- 内容 `checksum`。
|
||||||
|
|
||||||
|
`version` 在同一 Worker 流内严格连续递增,即使 `ownership_epoch` 因分配、Drain
|
||||||
|
或所有权恢复而前进也不重置。Gateway 只接受比当前 `version` 恰好大一的完整快照,
|
||||||
|
并拒绝倒退的 epoch;因此 epoch 变化不会让仍在长连接内的刷新快照被误判为缺口。
|
||||||
|
|
||||||
`usable_until = expires_at - allocationSafetyMargin`。Worker 必须以
|
`usable_until = expires_at - allocationSafetyMargin`。Worker 必须以
|
||||||
`usable_until` 作为最后可分配时刻;达到该时间后即使尚未到 `expires_at`,
|
`usable_until` 作为最后可分配时刻;达到该时间后即使尚未到 `expires_at`,
|
||||||
也不得再为新请求选择该 Proxy。
|
也不得再为新请求选择该 Proxy。
|
||||||
|
|||||||
@ -123,6 +123,9 @@ Routing 自上而下匹配,首条命中停止;支持 Gateway 与 Extract 两
|
|||||||
Gateway 使用既有 `SessionSupervisor` 退避重连。Gateway 本地 Store 对从完整 Snapshot
|
Gateway 使用既有 `SessionSupervisor` 退避重连。Gateway 本地 Store 对从完整 Snapshot
|
||||||
消失且仍有 Active/Reserved 的 Proxy 已按 draining 继续上报,但 Controller 尚未具备
|
消失且仍有 Active/Reserved 的 Proxy 已按 draining 继续上报,但 Controller 尚未具备
|
||||||
自动 `BeginDrain`/`AcknowledgeDrain` 编排或 Worker 分配器,不能视为自动 Drain 闭环。
|
自动 `BeginDrain`/`AcknowledgeDrain` 编排或 Worker 分配器,不能视为自动 Drain 闭环。
|
||||||
|
- Snapshot 的 `version` 是同一 Worker 流的连续序列,`ownership_epoch` 是独立且只能前进
|
||||||
|
的权威栅栏;epoch 变化不重置 version。此前 Gateway 错把 epoch 前进要求为 version=1,
|
||||||
|
与 Controller 的 `lastAppliedVersion+1` 生成规则冲突,现已用连续版本规则统一。
|
||||||
|
|
||||||
## Git 同步事实(2026-07-29)
|
## Git 同步事实(2026-07-29)
|
||||||
|
|
||||||
|
|||||||
@ -354,11 +354,11 @@ func TestAcquireSurvivesConcurrentSnapshotApply(t *testing.T) {
|
|||||||
{epoch: 1, version: 2},
|
{epoch: 1, version: 2},
|
||||||
{epoch: 1, version: 3},
|
{epoch: 1, version: 3},
|
||||||
{epoch: 1, version: 4},
|
{epoch: 1, version: 4},
|
||||||
{epoch: 2, version: 1},
|
{epoch: 2, version: 5},
|
||||||
{epoch: 2, version: 2},
|
{epoch: 2, version: 6},
|
||||||
{epoch: 2, version: 3},
|
{epoch: 2, version: 7},
|
||||||
{epoch: 3, version: 1},
|
{epoch: 3, version: 8},
|
||||||
{epoch: 3, version: 2},
|
{epoch: 3, version: 9},
|
||||||
}
|
}
|
||||||
for _, step := range steps {
|
for _, step := range steps {
|
||||||
next := snapshot.Envelope{
|
next := snapshot.Envelope{
|
||||||
|
|||||||
@ -267,9 +267,7 @@ func (s *Store) Apply(envelope Envelope) error {
|
|||||||
switch {
|
switch {
|
||||||
case envelope.Epoch < current.Epoch:
|
case envelope.Epoch < current.Epoch:
|
||||||
return ErrResyncRequired
|
return ErrResyncRequired
|
||||||
case envelope.Epoch == current.Epoch && envelope.Version != current.Version+1:
|
case envelope.Version != current.Version+1:
|
||||||
return ErrResyncRequired
|
|
||||||
case envelope.Epoch > current.Epoch && envelope.Version != 1:
|
|
||||||
return ErrResyncRequired
|
return ErrResyncRequired
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,6 +78,12 @@ func TestStoreRejectsWrongWorkerVersionGapAndChecksum(t *testing.T) {
|
|||||||
t.Fatalf("version gap error = %v, want ErrResyncRequired", err)
|
t.Fatalf("version gap error = %v, want ErrResyncRequired", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
epochReset := base
|
||||||
|
epochReset.Epoch = 2
|
||||||
|
if err := store.Apply(epochReset); !errors.Is(err, ErrResyncRequired) {
|
||||||
|
t.Fatalf("epoch reset version error = %v, want ErrResyncRequired", err)
|
||||||
|
}
|
||||||
|
|
||||||
badChecksum := base
|
badChecksum := base
|
||||||
badChecksum.Version = 2
|
badChecksum.Version = 2
|
||||||
badChecksum.Checksum = "bad"
|
badChecksum.Checksum = "bad"
|
||||||
@ -272,7 +278,7 @@ func TestStoreApplyReusesCapacityAcrossVersionsAndEpochs(t *testing.T) {
|
|||||||
|
|
||||||
third := second
|
third := second
|
||||||
third.Epoch = 2
|
third.Epoch = 2
|
||||||
third.Version = 1
|
third.Version = 3
|
||||||
third.Checksum = Checksum(third.Proxies)
|
third.Checksum = Checksum(third.Proxies)
|
||||||
if err := store.Apply(third); err != nil {
|
if err := store.Apply(third); err != nil {
|
||||||
t.Fatalf("Apply(third): %v", err)
|
t.Fatalf("Apply(third): %v", err)
|
||||||
|
|||||||
@ -21,6 +21,9 @@
|
|||||||
Delta 的 fail-closed 校验,也不把自动 Drain/ACK 记为已完成。
|
Delta 的 fail-closed 校验,也不把自动 Drain/ACK 记为已完成。
|
||||||
- Health Scheduler 现在先建立 EGRESS 与 Routing TARGET 的独立 due 引用,再投递
|
- Health Scheduler 现在先建立 EGRESS 与 Routing TARGET 的独立 due 引用,再投递
|
||||||
BASIC 任务;在单项小批次下,短 TTL Proxy 不会因 BASIC 先出队而错过首次出口/目标探测。
|
BASIC 任务;在单项小批次下,短 TTL Proxy 不会因 BASIC 先出队而错过首次出口/目标探测。
|
||||||
|
- 修复 Snapshot epoch/version 栅栏:Worker 流的 `version` 在 ownership epoch 前进时
|
||||||
|
仍严格递增,Gateway 以连续版本加非倒退 epoch 进行完整快照原子替换,避免动态分配后的
|
||||||
|
有效刷新被错误拒绝。这是后续自动 Drain 编排的必要前提。
|
||||||
|
|
||||||
## 2026-07-30
|
## 2026-07-30
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user