refactor(ingress): 移除 G1 窗口补偿扫描与迟到到达检测
实际负载不足 10 条/秒,G1 属过度防御。删除迟到到达检测机制、 existingIds 接口、PipelineCounters 字段、lateDetect 配置, 以及文档中 CLM-1/CLM-2 声明与 G1 缺口索引。
This commit is contained in:
@@ -48,16 +48,6 @@ class PipelineMetricsTest {
|
||||
assertEquals(-1.0, gauge("msgx.pipeline.watermark.lag"), 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `permanent hole releases are exposed as a monotonic total`() {
|
||||
val counters = ctx.getBean(PipelineCounters::class.java)
|
||||
val before = gauge("msgx.pipeline.hole.aged_out.total")
|
||||
counters.holeAgedOutIncrement()
|
||||
counters.holeAgedOutIncrement()
|
||||
|
||||
assertEquals(before + 2.0, gauge("msgx.pipeline.hole.aged_out.total"), 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `job heartbeat gauges are registered and reflect job activity`() {
|
||||
val activity = ctx.getBean(JobActivity::class.java)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.gzzn.omms.msgexchange.ingress
|
||||
|
||||
import com.gzzn.omms.msgexchange.config.PipelineProps
|
||||
import com.gzzn.omms.msgexchange.infra.metrics.PipelineCounters
|
||||
import com.gzzn.omms.msgexchange.infra.persistence.InboxCursorRepository
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubInbox
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubInboxCursor
|
||||
@@ -45,7 +44,7 @@ class CutoverSeedTest {
|
||||
props.pipeline.cutoverWatermark = null
|
||||
poller = InboxPoller(
|
||||
inbox, proc, cursor, StubPipelineTx(), props,
|
||||
Clock.fixed(t0, ZoneOffset.UTC), PipelineCounters(),
|
||||
Clock.fixed(t0, ZoneOffset.UTC),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ class InboxPollerTest {
|
||||
poller = InboxPoller(
|
||||
inbox, proc, cursor, StubPipelineTx(), props,
|
||||
java.time.Clock.fixed(t0, java.time.ZoneOffset.UTC),
|
||||
com.gzzn.omms.msgexchange.infra.metrics.PipelineCounters(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,31 +149,6 @@ class InboxPollerTest {
|
||||
assertEquals(first + 2, third)
|
||||
}
|
||||
|
||||
/**
|
||||
* 【缺口基线 · G1】快路径只读 `ID > W`、从不回头:水位越过某个 ID 之后,该 ID 即使后来
|
||||
* 出现在信箱里也不会再被发现。这不是期望行为,而是"没有补偿扫描"的**已知缺口**;
|
||||
* 本用例把它固定成基线,补偿扫描(ACM2-41 / G1)落地后必须反转成"能被发现并安全处置"。
|
||||
*/
|
||||
@Test
|
||||
fun `baseline - an id that appears after the watermark passed it is never discovered`() {
|
||||
inbox.simulateExternalWrite("<MSG/>") // 1
|
||||
val hole = inbox.simulateExternalWrite("<MSG/>") // 2
|
||||
val third = inbox.simulateExternalWrite("<MSG/>") // 3
|
||||
inbox.removeRow(hole)
|
||||
|
||||
poller.pollOnce(t0) // W 停在 1,空洞在 2
|
||||
val agedOut = t0.plus(props.pipeline.maxCommitDelay)
|
||||
poller.pollOnce(agedOut) // 空洞判永久 → 放行
|
||||
poller.pollOnce(agedOut) // 发现 3
|
||||
assertEquals(third, cursor.cursor.committedUpTo)
|
||||
|
||||
// 迟到的 2 现在才出现:水位已经越过它,快路径再也不会读它。
|
||||
inbox.restoreRow(hole, "<MSG/>", agedOut)
|
||||
|
||||
assertEquals(0, poller.pollOnce(agedOut.plusSeconds(1)))
|
||||
assertNull(proc.find(hole))
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容入口会把消息登记在水位**之外**(`msgId > W`),因此它天然成为"最小未完成行"。
|
||||
* 领取侧的守卫在主泵:只领 `msgId <= W`(端到端验收见 `PipelineSmokeTest` 的
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.gzzn.omms.msgexchange.ingress
|
||||
|
||||
import com.gzzn.omms.msgexchange.MutableClock
|
||||
import com.gzzn.omms.msgexchange.config.PipelineProps
|
||||
import com.gzzn.omms.msgexchange.infra.metrics.PipelineCounters
|
||||
import com.gzzn.omms.msgexchange.infra.persistence.CminmsgInboxRepository
|
||||
import com.gzzn.omms.msgexchange.infra.persistence.MailboxMarkResult
|
||||
import com.gzzn.omms.msgexchange.infra.persistence.MailboxRow
|
||||
@@ -50,8 +49,6 @@ class InboxServiceTest {
|
||||
|
||||
override fun minId(): Long? = null
|
||||
|
||||
override fun existingIds(msgIds: Collection<Long>): Set<Long> = emptySet()
|
||||
|
||||
override fun markProcessedIfUnmarked(msgId: Long, value: String): MailboxMarkResult = MailboxMarkResult.MISSING
|
||||
}
|
||||
|
||||
@@ -106,7 +103,7 @@ class InboxServiceTest {
|
||||
val cursor = StubInboxCursor().apply { clear() }
|
||||
val clock = MutableClock(t0)
|
||||
val service = InboxService(inbox, FlakyProcState(proc), clock)
|
||||
val poller = InboxPoller(inbox, proc, cursor, StubPipelineTx(), PipelineProps(), clock, PipelineCounters())
|
||||
val poller = InboxPoller(inbox, proc, cursor, StubPipelineTx(), PipelineProps(), clock)
|
||||
|
||||
val receipt = service.accept("<MSG/>") // 落信成功;PG 入队失败但不抛
|
||||
assertEquals(1L, service.pgEnqueueFailures.get())
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
package com.gzzn.omms.msgexchange.ingress
|
||||
|
||||
import com.gzzn.omms.msgexchange.config.PipelineProps
|
||||
import com.gzzn.omms.msgexchange.infra.metrics.PipelineCounters
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubInbox
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubInboxCursor
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubPipelineTx
|
||||
import com.gzzn.omms.msgexchange.infra.stub.StubProcState
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* 迟到到达检测(ACM2-41 阶段 0):**只读观测**。
|
||||
*
|
||||
* 语义:被判定为永久空洞并放行的 ID,如果后来真的出现在信箱里,就是"上游提交晚于水位推进"。
|
||||
* 阶段 0 只计数与告警,**不入队、不改变处理语义**(补入队属阶段 1,需先与库方定案)。
|
||||
*/
|
||||
class LateArrivalDetectTest {
|
||||
|
||||
private val t0: Instant = Instant.parse("2026-09-08T03:00:00Z")
|
||||
private val props = PipelineProps()
|
||||
|
||||
private lateinit var inbox: StubInbox
|
||||
private lateinit var proc: StubProcState
|
||||
private lateinit var cursor: StubInboxCursor
|
||||
private lateinit var counters: PipelineCounters
|
||||
private lateinit var poller: InboxPoller
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
inbox = StubInbox().apply { clear() }
|
||||
proc = StubProcState().apply { clear() }
|
||||
cursor = StubInboxCursor().apply { clear() }
|
||||
counters = PipelineCounters()
|
||||
poller = InboxPoller(inbox, proc, cursor, StubPipelineTx(), props, Clock.fixed(t0, ZoneOffset.UTC), counters)
|
||||
}
|
||||
|
||||
/** 造出"1 存在、2 是空洞、3 存在",并把空洞等到超期放行。返回迟到的那个 ID。 */
|
||||
private fun ageOutHoleAt2(): Long {
|
||||
inbox.simulateExternalWrite("<MSG/>") // 1
|
||||
val hole = inbox.simulateExternalWrite("<MSG/>") // 2
|
||||
inbox.simulateExternalWrite("<MSG/>") // 3
|
||||
inbox.removeRow(hole)
|
||||
poller.pollOnce(t0) // W=1,空洞在 2
|
||||
poller.pollOnce(t0.plus(props.pipeline.maxCommitDelay)) // 空洞判永久 → 放行
|
||||
return hole
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a hole that really appears later is detected and counted`() {
|
||||
val hole = ageOutHoleAt2()
|
||||
inbox.restoreRow(hole, "<MSG/>", t0.plus(props.pipeline.maxCommitDelay))
|
||||
|
||||
// 过了检测周期再轮询一次
|
||||
poller.pollOnce(t0.plus(props.pipeline.maxCommitDelay).plus(props.pipeline.lateDetectPeriod))
|
||||
|
||||
assertEquals(1L, counters.lateArrivalDetectedCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detection does not enqueue the late message - phase 0 is observation only`() {
|
||||
val hole = ageOutHoleAt2()
|
||||
inbox.restoreRow(hole, "<MSG/>", t0.plus(props.pipeline.maxCommitDelay))
|
||||
|
||||
poller.pollOnce(t0.plus(props.pipeline.maxCommitDelay).plus(props.pipeline.lateDetectPeriod))
|
||||
|
||||
assertEquals(1L, counters.lateArrivalDetectedCount())
|
||||
assertNull(proc.find(hole)) // 仍然不会被补入队
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a hole that stays absent is not counted`() {
|
||||
ageOutHoleAt2()
|
||||
|
||||
poller.pollOnce(t0.plus(props.pipeline.maxCommitDelay).plus(props.pipeline.lateDetectPeriod))
|
||||
|
||||
assertEquals(0L, counters.lateArrivalDetectedCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detection does not run before the configured period elapses`() {
|
||||
val hole = ageOutHoleAt2()
|
||||
inbox.restoreRow(hole, "<MSG/>", t0)
|
||||
|
||||
// 只过了一个 max-commit-delay,未到检测周期
|
||||
poller.pollOnce(t0.plus(props.pipeline.maxCommitDelay).plusSeconds(1))
|
||||
|
||||
assertEquals(0L, counters.lateArrivalDetectedCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detection can be switched off`() {
|
||||
props.pipeline.lateDetectPeriod = Duration.ZERO
|
||||
val hole = ageOutHoleAt2()
|
||||
inbox.restoreRow(hole, "<MSG/>", t0)
|
||||
|
||||
poller.pollOnce(t0.plus(props.pipeline.maxCommitDelay).plus(Duration.ofHours(1)))
|
||||
|
||||
assertEquals(0L, counters.lateArrivalDetectedCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the same late id is counted once even after the hole is aged out again`() {
|
||||
val hole = ageOutHoleAt2()
|
||||
inbox.restoreRow(hole, "<MSG/>", t0.plus(props.pipeline.maxCommitDelay))
|
||||
val later = t0.plus(props.pipeline.maxCommitDelay).plus(props.pipeline.lateDetectPeriod)
|
||||
poller.pollOnce(later)
|
||||
assertEquals(1L, counters.lateArrivalDetectedCount())
|
||||
|
||||
poller.pollOnce(later.plus(props.pipeline.lateDetectPeriod))
|
||||
|
||||
assertEquals(1L, counters.lateArrivalDetectedCount()) // 已命中的 ID 不再重复计数
|
||||
assertNotNull(cursor.cursor)
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,6 @@ class BackfillServiceTest {
|
||||
override fun readRange(fromExclusive: Long, limit: Int): List<MailboxRow> = emptyList()
|
||||
override fun maxId(): Long? = null
|
||||
override fun minId(): Long? = null
|
||||
override fun existingIds(msgIds: Collection<Long>): Set<Long> = emptySet()
|
||||
override fun markProcessedIfUnmarked(msgId: Long, value: String): MailboxMarkResult {
|
||||
if (fail) throw IllegalStateException("mysql-down")
|
||||
if (missing) return MailboxMarkResult.MISSING
|
||||
@@ -240,7 +239,6 @@ class BackfillServiceTest {
|
||||
override fun readRange(fromExclusive: Long, limit: Int) = emptyList<MailboxRow>()
|
||||
override fun maxId(): Long? = 1L
|
||||
override fun minId(): Long? = 1L
|
||||
override fun existingIds(msgIds: Collection<Long>): Set<Long> = emptySet()
|
||||
override fun markProcessedIfUnmarked(msgId: Long, value: String): MailboxMarkResult {
|
||||
entered.countDown()
|
||||
release.await()
|
||||
|
||||
Reference in New Issue
Block a user