feat(ingress): 按处理时间为空扫描信箱,移除水位消费(ACM2-78)

readUnprocessed 替代 ID 水位;去掉 INBOX_CURSOR/cutover-watermark/max-commit-delay 及相关健康与指标。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
windyboy
2026-09-21 14:49:40 +08:00
co-authored by Cursor
parent 23b74ed554
commit 891088235c
29 changed files with 166 additions and 805 deletions
@@ -14,7 +14,6 @@ import com.gzzn.omms.msgexchange.domain.ProcStatus
import com.gzzn.omms.msgexchange.infra.log.TraceLog
import com.gzzn.omms.msgexchange.infra.metrics.PipelineCounters
import com.gzzn.omms.msgexchange.infra.persistence.CminmsgInboxRepository
import com.gzzn.omms.msgexchange.infra.persistence.InboxCursorRepository
import com.gzzn.omms.msgexchange.infra.persistence.ProcStateRepository
import com.gzzn.omms.msgexchange.infra.retry.ProcFailure
import jakarta.inject.Singleton
@@ -38,7 +37,6 @@ import java.util.concurrent.atomic.AtomicLong
@Singleton
class Pump(
private val procState: ProcStateRepository,
private val cursor: InboxCursorRepository,
private val processor: MessageProcessor,
private val props: PipelineProps,
private val clock: Clock,
@@ -80,17 +78,6 @@ class Pump(
sleepQuietly(props.pipeline.pollInterval)
return
}
// 只领取"已被水位覆盖"的队头(`msgId <= W`)。
//
// 水位以内的行都是收报按 ID 顺序发现并登记的;水位之外的行只可能来自兼容入口
// 直接写 PROC_STATE(它不参与水位)。若允许领取,它就会越过那些尚未入队的较小 ID,
// 破坏 FIFO(不变量"只领取已发现的行"`specification.md` `INV-4`)。这种行在空洞补齐、`W` 追平之后自然可领取。
val watermark = cursor.load().committedUpTo
if (head.msgId > watermark) {
warnBeyondWatermark(head.msgId, watermark)
sleepQuietly(props.pipeline.pollInterval)
return
}
val now = clock.instant()
when {
head.state == ProcStatus.FAILED && head.attempts >= props.pipeline.maxAttempts -> {
@@ -111,19 +98,6 @@ class Pump(
}
}
/** 上一次"队头在水位之外"告警时的水位值:只在它变化时告警,避免每秒刷屏。 */
private val warnedWatermark = AtomicLong(Long.MIN_VALUE)
private fun warnBeyondWatermark(msgId: Long, watermark: Long) {
if (warnedWatermark.getAndSet(watermark) != watermark) {
log.warn(
"head msgId={} is beyond watermark W={}; waiting for discovery " +
"(row injected by the compat entry point?)",
msgId, watermark,
)
}
}
private fun sleepQuietly(d: Duration) {
if (!d.isNegative && !d.isZero) Thread.sleep(d.toMillis().coerceAtLeast(1))
}