chapter 32 like/count lab, seed=322026, db=social32

CASE 32-A :: 正常路径：点赞写事实与 Outbox，消费者应用后计数等于事实数，滞后归零
  note like(user-0001, item-lab-01) = CHANGED(liked,v1,seq1)
  ok   首次点赞 CHANGED (expected=CHANGED, actual=CHANGED)
  ok   事实版本为 1 (expected=1, actual=1)
  ok   分配全局序号 1 (expected=1, actual=1)
  ok   同事务写入 1 条 delta=+1 的 Outbox 事件 (expected=1, actual=1)
  note before consume: n=0 lag=1 pendingForItem=1 stale=false
  ok   消费前滞后 = 1 (expected=1, actual=1)
  ok   消费前计数仍为 0 (expected=0, actual=0)
  note consumer: applied=1 covered=0 dup=0 watermark=1
  note after consume: n=1 lag=0 pendingForItem=0 stale=false
  ok   消费后计数 = 1 (expected=1, actual=1)
  note 32-A write-side: last_seq=1 outbox_rows=1 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-A read-side:  last_seq=1 watermark=1 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-A：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-A：滞后为 0 (expected=0, actual=0)
  ok   32-A：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-B :: 边界：重复点赞与未赞取消为 NOOP；赞/取消/赞产生三条事件；不存在的条目被外键拒绝；写 Outbox 后中止则事实与事件都不存在
  note repeat like = NOOP(liked,v1,already liked)
  ok   重复点赞 NOOP (expected=NOOP, actual=NOOP)
  ok   重复点赞不写新事件 (expected=1, actual=1)
  note unlike never-liked = NOOP(unliked,v0,not liked)
  ok   未赞过的取消 NOOP (expected=NOOP, actual=NOOP)
  note toggle: CHANGED(liked,v1,seq2) CHANGED(unliked,v2,seq3) CHANGED(liked,v3,seq4)
  ok   赞/取消/赞后版本 = 3 (expected=3, actual=3)
  ok   三条事件的增量依次为 +1,-1,+1 (expected=[1, -1, 1], actual=[1, -1, 1])
  note like nonexistent item = REJECTED(unliked,v0,no such item (23503))
  ok   不存在的条目 REJECTED (expected=REJECTED, actual=REJECTED)
  note inside tx: outbox rows visible to own tx = 1
  note aborted like: injected abort after outbox insert
  ok   注入的中止传到调用方
  ok   中止后事实行不存在 (expected=0, actual=0)
  ok   中止后 Outbox 事件不存在 (expected=0, actual=0)
  ok   中止后序号未前进 (expected=4, actual=4)
  note retry after abort = CHANGED(liked,v1,seq5)
  ok   重试拿到紧接着的序号（无空洞） (expected=5, actual=5)
  ok   item-lab-01 计数 = 1 (expected=1, actual=1)
  ok   item-lab-02 计数 = 1 (expected=1, actual=1)
  note 32-B write-side: last_seq=5 outbox_rows=5 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-B read-side:  last_seq=5 watermark=5 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-B：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-B：滞后为 0 (expected=0, actual=0)
  ok   32-B：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-C :: 并发：16 线程同时点赞同一 (user, item) 只产生一条事件；8 线程交错赞/取消 320 次后，版本 = 事件数，增量和 = 当前状态
  ok   并发重复点赞：线程无异常
  note 16 concurrent likes: CHANGED=1 NOOP=15
  ok   恰好 1 个 CHANGED (expected=1, actual=1)
  ok   其余 15 个 NOOP (expected=15, actual=15)
  ok   只有 1 条事件 (expected=1, actual=1)
  ok   交错赞/取消：线程无异常
  note toggles: requests=320 CHANGED=146 events=146 version=146 sum(delta)=0 liked=0
  ok   CHANGED 次数 = 事件数 (expected=146, actual=146)
  ok   事实版本 = 事件数 (expected=146, actual=146)
  ok   事件增量和 = 当前是否已赞 (expected=0, actual=0)
  note 32-C write-side: last_seq=147 outbox_rows=147 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-C read-side:  last_seq=147 watermark=147 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-C：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-C：滞后为 0 (expected=0, actual=0)
  ok   32-C：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-D :: 滞后：lag = like_seq.last − counter_watermark.applied_upto；允许滞后 16 条（教学设定），超出时读结果标 stale；误差不超过本条目未处理事件数
  note no consumer yet: item-lab-01 n=0 lag=30 pendingForItem=20 stale=true truth=20
  ok   未消费时 lag = 30 (expected=30, actual=30)
  ok   lag 30 > 16，读结果标 stale
  ok   |n − 事实数| ≤ 本条目未处理事件数
  note after 20 events: applied=20 covered=0 dup=0 watermark=20 | item-lab-01 n=14 lag=10 pendingForItem=6 stale=false | last_seq=30 watermark=20 lag=10 unprocessed=10 count_mismatch_items=2 max_abs_err=6 exact_violations=0 bound_violations=0
  ok   应用 20 条后 lag = 10 (expected=10, actual=10)
  ok   lag 10 ≤ 16，读结果不标 stale
  ok   |n − 事实数| ≤ 本条目未处理事件数
  ok   恒等式 n + 未处理增量 = 事实数 无违反 (expected=0, actual=0)
  ok   未处理事件数 ≤ lag
  note drained: item-lab-01 n=20 lag=0 pendingForItem=0 stale=false
  ok   排空后 item-lab-01 = 20 (expected=20, actual=20)
  note 32-D write-side: last_seq=30 outbox_rows=30 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-D read-side:  last_seq=30 watermark=30 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-D：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-D：滞后为 0 (expected=0, actual=0)
  ok   32-D：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-E :: 重复与乱序：每条事件投递 1–3 次并整体打乱，按 13 条一批应用；去重后计数等于事实数，重复投递全部被识别
  note requests=600 changed=302 events=302 deliveries=618
  note after 10 batches: processed=108 last_seq=302 watermark=3 lag=299 unprocessed=194 count_mismatch_items=5 max_abs_err=15 exact_violations=0 bound_violations=0
  note consumer total: applied=302 covered=0 dup=316 watermark=302 samples=4
  ok   applied = 事件数 (expected=302, actual=302)
  ok   dup = 投递数 − 事件数 (expected=316, actual=316)
  ok   中途采样：恒等式无违反 (expected=0, actual=0)
  ok   中途采样：误差上界无违反 (expected=0, actual=0)
  ok   中途采样：未处理事件数 ≤ lag (expected=0, actual=0)
  note 32-E write-side: last_seq=302 outbox_rows=302 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-E read-side:  last_seq=302 watermark=302 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-E：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-E：滞后为 0 (expected=0, actual=0)
  ok   32-E：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-F :: 负向对照 1：8 线程发送 400 个赞/取消请求，35% 的请求被客户端重发；事实入口计数一致，请求路径直接加减的计数偏离事实
  note requests=400 resent=136 sends=536
  ok   事实入口：线程无异常
  note fact-based: NOOP=347 events=189
  note 32-F fact-based write-side: last_seq=189 outbox_rows=189 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-F fact-based read-side:  last_seq=189 watermark=189 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-F fact-based：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-F fact-based：滞后为 0 (expected=0, actual=0)
  ok   32-F fact-based：计数与事实数不一致的条目数 (expected=0, actual=0)
  ok   直接加减：线程无异常
  note naive: items with drift=4 of 4, sum |naive − facts|=60
  note   item-lab-01 naive=15 facts=14
  note   item-lab-02 naive=0 facts=19
  note   item-lab-03 naive=-12 facts=15
  note   item-lab-04 naive=1 facts=14
  ok   negative control exposes: 请求路径直接加减的计数与事实数不一致

CASE 32-G :: 负向对照 2：同一批 Outbox 事件由 3 个竞争消费者从水位拉取，再重投 10% 旧事件；去重消费者计数一致，不去重消费者计数偏离；随后批量重算把计数校准回事实
  note events=243 redelivered=29
  ok   去重消费者：线程无异常
  note dedup consumers: applied=243 covered=0 dup=515 watermark=243
  ok   去重：applied = 事件数 (expected=243, actual=243)
  ok   去重：重投的旧事件全部记为 dup（dup=515）
  note 32-G dedup write-side: last_seq=243 outbox_rows=243 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-G dedup read-side:  last_seq=243 watermark=243 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-G dedup：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-G dedup：滞后为 0 (expected=0, actual=0)
  ok   32-G dedup：计数与事实数不一致的条目数 (expected=0, actual=0)
  ok   不去重消费者：线程无异常
  note no-dedup consumers: applied=758 covered=0 dup=0 watermark=243 (applications=758 for 243 events)
  note no-dedup read-side: last_seq=243 watermark=243 lag=0 unprocessed=0 count_mismatch_items=4 max_abs_err=44 exact_violations=4 bound_violations=4
  note   item-lab-01 count=38 facts=12
  note   item-lab-02 count=63 facts=19
  note   item-lab-03 count=44 facts=14
  note   item-lab-04 count=53 facts=18
  ok   negative control exposes: 不去重的消费者在重投下使计数偏离事实
  note rebuild: [item-lab-01:installed(n 38->12, base_seq=243, appliedAfterBase=0), item-lab-02:installed(n 63->19, base_seq=243, appliedAfterBase=0), item-lab-03:installed(n 44->14, base_seq=243, appliedAfterBase=0), item-lab-04:installed(n 53->18, base_seq=243, appliedAfterBase=0)]
  note after rebuild: last_seq=243 watermark=243 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   重算后计数与事实数一致 (expected=0, actual=0)
  note 50 more requests, dedup drain: applied=23 covered=0 dup=0 watermark=266
  note 32-G after rebuild write-side: last_seq=266 outbox_rows=266 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-G after rebuild read-side:  last_seq=266 watermark=266 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-G after rebuild：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-G after rebuild：滞后为 0 (expected=0, actual=0)
  ok   32-G after rebuild：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-H :: 重算与在线增量交错：快照 S=6 后又有事件 7、8，消费者先应用 1..3 与 7；按基线安装后 4..6 记为 COVERED，结果等于事实数；直接覆盖的重算重复计入 4..6 并丢失 7
  note base_seq install: applied{1,2,3} n=3; snapshot S=6 C(S)=6; +events 7,8, applied{7} n=4; item-lab-01:installed(n 4->7, base_seq=6, appliedAfterBase=1); applied{4,5,6,8} applied=1 covered=3 dup=0 watermark=8 n=8 truth=8
  ok   安装值 = C(S) + 已应用的序号 > S 的增量 = 6 + 1 (expected=7, actual=7)
  ok   4..6 记为 COVERED (expected=3, actual=3)
  ok   8 正常应用 (expected=1, actual=1)
  ok   最终计数 = 事实数 (expected=8, actual=8)
  note redeliver 1..8: applied=0 covered=0 dup=8 watermark=8 n=8
  ok   全部重投记为 dup (expected=8, actual=8)
  note 32-H write-side: last_seq=8 outbox_rows=8 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-H read-side:  last_seq=8 watermark=8 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-H：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-H：滞后为 0 (expected=0, actual=0)
  ok   32-H：计数与事实数不一致的条目数 (expected=0, actual=0)
  note naive overwrite: applied{1,2,3} n=3; snapshot S=6 C(S)=6; +events 7,8, applied{7} n=4; item-lab-01:overwritten(n 4->6, base_seq=0, appliedAfterBase=0); applied{4,5,6,8} applied=4 covered=0 dup=0 watermark=8 n=10 truth=8
  note naive: double-counted {4,5,6}=+3, lost {7}=-1, error=2
  ok   negative control exposes: 直接覆盖的重算与在线增量交错后计数偏离事实

CASE 32-I :: 重算与消费者争同一计数行：消费者事务持有 item-lab-01 计数行锁时，重算安装在 pg_locks 中等待；放行后安装读到已提交的事件 5
  ok   消费者停在提交前
  note snapshot S=4 C(S)=4; consumer paused before commit holding row lock; rebuild waiting locks=1
  ok   重算安装在计数行锁上等待（pg_locks granted=false）
  ok   消费者与重算：线程无异常
  note install: item-lab-01:installed(n 1->5, base_seq=4, appliedAfterBase=1)
  ok   安装读到了锁释放后已提交的事件 5 (expected=1, actual=1)
  ok   安装值 = 4 + 1 (expected=5, actual=5)
  note drain events 1..4: applied=0 covered=4 dup=1 watermark=5
  ok   1..4 记为 COVERED (expected=4, actual=4)
  note 32-I write-side: last_seq=5 outbox_rows=5 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-I read-side:  last_seq=5 watermark=5 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-I：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-I：滞后为 0 (expected=0, actual=0)
  ok   32-I：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-J :: 并发混合：8 个写线程各 300 个请求（热点条目占 50%），3 个竞争消费者、1 个重投线程、1 个周期重算线程同时运行；采样恒等式始终成立，排空后计数等于事实数
  ok   并发混合：线程无异常
  note requests=2400 CHANGED=1202 events=1202 rebuild rounds=9 redelivered=280
  note consumers+redeliver: applied=79 covered=1123 dup=2265 watermark=1202 covered rows=1123
  note sampler (values vary by run): samples=91 max lag=634 max |n-truth|=16
  ok   CHANGED 次数 = 事件数 (expected=1202, actual=1202)
  ok   每条事件恰好一次 APPLIED 或 COVERED (expected=1202, actual=1202)
  ok   采样与重算都实际运行过
  ok   采样：恒等式 n + 未处理增量 = 事实数 无违反 (expected=0, actual=0)
  ok   采样：|n − 事实数| ≤ 本条目未处理事件数 无违反 (expected=0, actual=0)
  ok   采样：未处理事件数 ≤ lag 无违反 (expected=0, actual=0)
  note 32-J write-side: last_seq=1202 outbox_rows=1202 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-J read-side:  last_seq=1202 watermark=1202 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-J：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-J：滞后为 0 (expected=0, actual=0)
  ok   32-J：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-K :: 恢复（多进程）：独立 JVM 消费者在事务内被 kill -9，未提交的批次全部回滚；再在提交后、保存位点前被 kill -9，重启后重投整批并被去重
  note events=120 batch=25 offsetFile=<tmp>/offset
  note child1| START mode=die-in-tx offset=0 pid=<child>
  note child1| PAUSED_IN_TX batch=2 seq=26..50
  ok   子进程 1 停在第 2 批事务内
  note child1 backend state idle in transaction = 1
  note kill -9 child1 exit=137 backendStillListed=0 watermark=25 processed=25 offsetFile=25
  ok   子进程 1 被 SIGKILL（退出码 137） (expected=137, actual=137)
  ok   服务端会话随客户端消失而结束 (expected=0, actual=0)
  ok   第 2 批未提交：水位停在 25 (expected=25, actual=25)
  ok   第 2 批的 processed_event 全部回滚 (expected=25, actual=25)
  ok   kill 后恒等式成立 (expected=0, actual=0)
  note child2| START mode=die-after-commit offset=25 pid=<child>
  note child2| COMMITTED_OFFSET_NOT_SAVED batch=2 seq=51..75 applied=25 covered=0 dup=0 watermark=75
  ok   子进程 2 提交第 2 批后停在保存位点之前
  note kill -9 child2 exit=137 watermark=75 offsetFile=50
  ok   数据库已提交到 75 (expected=75, actual=75)
  ok   本地位点仍是 50（提交与保存位点之间被杀） (expected=50, actual=50)
  note child3| START mode=run offset=50 pid=<child>
  note child3| DONE batches=3 applied=45 covered=0 dup=25 watermark=120
  note child3 exit=0
  ok   子进程 3 正常结束 (expected=0, actual=0)
  ok   重启后重投的 51..75 共 25 条全部记为 dup
  note 32-K write-side: last_seq=120 outbox_rows=120 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-K read-side:  last_seq=120 watermark=120 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-K：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-K：滞后为 0 (expected=0, actual=0)
  ok   32-K：计数与事实数不一致的条目数 (expected=0, actual=0)

CASE 32-L :: 恢复（数据库崩溃）：写线程与消费者运行中执行 pg.sh restart-immediate；重启后事实与 Outbox 同进同退，已确认的事件都在，计数可排空到与事实一致
  ok   崩溃前已确认至少 300 个 CHANGED
  ok   崩溃期间的线程（SQLException 已单独记录）：线程无异常
  note pg.sh restart-immediate rc=0 output=pg b3-32 restarted after immediate stop
  ok   restart-immediate 成功 (expected=0, actual=0)
  note acked CHANGED before crash=344; threads ended by SQLException=7 states=[08006, consumer:08006]
  ok   6 个写线程和 1 个消费者都因连接中断结束 (expected=7, actual=7)
  note after recovery write-side: last_seq=344 outbox_rows=344 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note after recovery read-side:  last_seq=344 watermark=180 lag=164 unprocessed=164 count_mismatch_items=3 max_abs_err=3 exact_violations=0 bound_violations=0
  ok   恢复后：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   已向调用方确认的事件在恢复后全部存在 (expected=0, actual=0)
  ok   恢复后事件数 ≥ 已确认数（差值是提交成功但确认未送达的请求）
  ok   恢复后恒等式 n + 未处理增量 = 事实数 成立 (expected=0, actual=0)
  note drain after recovery: applied=164 covered=0 dup=0 watermark=344
  note 32-L write-side: last_seq=344 outbox_rows=344 version_mismatch_pairs=0 delta_sum_mismatch_items=0
  note 32-L read-side:  last_seq=344 watermark=344 lag=0 unprocessed=0 count_mismatch_items=0 max_abs_err=0 exact_violations=0 bound_violations=0
  ok   32-L：序号无空洞、版本数等于事件数、事件增量和等于事实数
  ok   32-L：滞后为 0 (expected=0, actual=0)
  ok   32-L：计数与事实数不一致的条目数 (expected=0, actual=0)

tx retries observed (40001/40P01): 0

SUMMARY passed=128 failed=0
