chapter 31 feed lab, seed=312026, db=social31

CASE 31-A :: 相同合成关注图：推、拉、混合(50/200) 的写入行数、读取语句与行数；四种方案结果与内存模型一致
  note graph users=1200 edges=26937 outDegree=5..40 zipf_s=1.0
  note followers p50=8 p90=37 p99=311 max=1090 zeroFollowers=5 authors>=50: 92 authors>=200: 20
  note posts=3000 visibility={FOLLOWERS=331, PUBLIC=2578, SELF=91} readers=100 pages=3x20
  ok   关系行数等于生成的边数 (expected=26937, actual=26937)
  ok   push 写入行数等于内存模型（帖子 + 推送帖子的粉丝数之和） (expected=67917, actual=67917)
  ok   pull 写入行数等于内存模型（帖子 + 推送帖子的粉丝数之和） (expected=3000, actual=3000)
  ok   h50 写入行数等于内存模型（帖子 + 推送帖子的粉丝数之和） (expected=32334, actual=32334)
  ok   h200 写入行数等于内存模型（帖子 + 推送帖子的粉丝数之和） (expected=47892, actual=47892)
  ok   push 100 个读者的前 3 页与内存模型逐条一致 (expected=0, actual=0)
  ok   push 无重复条目 (expected=0, actual=0)
  ok   push 返回的 4601 条全部通过 AccessCheck(TRUTH) (expected=0, actual=0)
  ok   pull 100 个读者的前 3 页与内存模型逐条一致 (expected=0, actual=0)
  ok   pull 无重复条目 (expected=0, actual=0)
  ok   pull 返回的 4601 条全部通过 AccessCheck(TRUTH) (expected=0, actual=0)
  note pull author-stream queries=5635, of which hot author user-0719 (followers=1090)=239
  ok   h50 100 个读者的前 3 页与内存模型逐条一致 (expected=0, actual=0)
  ok   h50 无重复条目 (expected=0, actual=0)
  ok   h50 返回的 4601 条全部通过 AccessCheck(TRUTH) (expected=0, actual=0)
  ok   h200 100 个读者的前 3 页与内存模型逐条一致 (expected=0, actual=0)
  ok   h200 无重复条目 (expected=0, actual=0)
  ok   h200 返回的 4601 条全部通过 AccessCheck(TRUTH) (expected=0, actual=0)
  note h200 author-stream queries=1590, of which hot author user-0719 (followers=1090)=239
  ok   四种方案对每个读者返回相同的帖子序列
  table scheme | writeQueries writeRows maxFanoutRows inboxRows pullPosts | readQueries readRows pages maxQueriesPerPage
  table push  | 6000 67917 1090 64917 0 | 256 4601 256 1
  table pull  | 3000 3000 0 0 3000 | 5891 16410 256 37
  table h50   | 8768 32334 49 29334 232 | 3439 12264 256 24
  table h200  | 8953 47892 195 44892 47 | 2102 8952 256 14

CASE 31-B :: 热点作者连续发 10 帖：每种方案的写入行数与单帖最大扇出；其粉丝首页顶部是这 10 帖
  note hot author=user-0719 followers=1090 sample follower=user-0002
  note push burst writeQueries=20 writeRows=10910 maxFanoutRows=1090 | follower first page readQueries=1 readRows=10
  ok   push 写入行数 (expected=10910, actual=10910)
  ok   push 粉丝首页前 10 条是热点作者的 10 帖
  note pull burst writeQueries=10 writeRows=10 maxFanoutRows=0 | follower first page readQueries=21 readRows=86
  ok   pull 写入行数 (expected=10, actual=10)
  ok   pull 粉丝首页前 10 条是热点作者的 10 帖
  note h50 burst writeQueries=20 writeRows=10 maxFanoutRows=0 | follower first page readQueries=9 readRows=48
  ok   h50 写入行数 (expected=10, actual=10)
  ok   h50 粉丝首页前 10 条是热点作者的 10 帖
  note h200 burst writeQueries=20 writeRows=10 maxFanoutRows=0 | follower first page readQueries=8 readRows=43
  ok   h200 写入行数 (expected=10, actual=10)
  ok   h200 粉丝首页前 10 条是热点作者的 10 帖

CASE 31-C :: 边界：30 帖中 20 帖共用同一 created_at，每页 7 条跨越同值区间；键集游标不漏不重，仅时间戳游标漏帖
  ok   push 键集游标：30 条按 (created_at, post_id) 严格降序且不漏不重
  note push TS_ONLY cursor returned=14 missing=16
  ok   negative control exposes: push 只比较 created_at 的游标在同值边界漏掉 16 帖
  ok   pull 键集游标：30 条按 (created_at, post_id) 严格降序且不漏不重
  note pull TS_ONLY cursor returned=14 missing=16
  ok   negative control exposes: pull 只比较 created_at 的游标在同值边界漏掉 16 帖
  ok   h50 键集游标：30 条按 (created_at, post_id) 严格降序且不漏不重
  note h50 TS_ONLY cursor returned=14 missing=16
  ok   negative control exposes: h50 只比较 created_at 的游标在同值边界漏掉 16 帖
  ok   h200 键集游标：30 条按 (created_at, post_id) 严格降序且不漏不重
  note h200 TS_ONLY cursor returned=14 missing=16
  ok   negative control exposes: h200 只比较 created_at 的游标在同值边界漏掉 16 帖

CASE 31-D :: 翻页期间发布：较新帖与游标之下的帖按规则出现；落入已读区间的帖本轮不可见、刷新后可见；OFFSET 分页出现重复
  note push cursor after page1=Cursor[createdAt=4000008, postId=710016] traversal size=25 sawBelowCursor=true sawInsideReadRange=false sawNewer=false
  ok   push 键集：原有 24 帖全部出现、无重复、严格降序
  ok   push 键集：游标之下的新帖可见；已读区间内与更新的帖本轮不可见（声明的边界）
  ok   push 从顶部刷新后看到 24+5 帖 (expected=29, actual=29)
  ok   push 刷新结果无重复 (expected=29, actual=29)
  note push OFFSET page2 repeated 3 items of page1
  ok   negative control exposes: push OFFSET 分页在顶部插入后第 2 页重复第 1 页的帖
  note pull cursor after page1=Cursor[createdAt=4000008, postId=710016] traversal size=25 sawBelowCursor=true sawInsideReadRange=false sawNewer=false
  ok   pull 键集：原有 24 帖全部出现、无重复、严格降序
  ok   pull 键集：游标之下的新帖可见；已读区间内与更新的帖本轮不可见（声明的边界）
  ok   pull 从顶部刷新后看到 24+5 帖 (expected=29, actual=29)
  ok   pull 刷新结果无重复 (expected=29, actual=29)
  note pull OFFSET page2 repeated 3 items of page1
  ok   negative control exposes: pull OFFSET 分页在顶部插入后第 2 页重复第 1 页的帖
  note h50 cursor after page1=Cursor[createdAt=4000008, postId=710016] traversal size=25 sawBelowCursor=true sawInsideReadRange=false sawNewer=false
  ok   h50 键集：原有 24 帖全部出现、无重复、严格降序
  ok   h50 键集：游标之下的新帖可见；已读区间内与更新的帖本轮不可见（声明的边界）
  ok   h50 从顶部刷新后看到 24+5 帖 (expected=29, actual=29)
  ok   h50 刷新结果无重复 (expected=29, actual=29)
  note h50 OFFSET page2 repeated 3 items of page1
  ok   negative control exposes: h50 OFFSET 分页在顶部插入后第 2 页重复第 1 页的帖
  note h200 cursor after page1=Cursor[createdAt=4000008, postId=710016] traversal size=25 sawBelowCursor=true sawInsideReadRange=false sawNewer=false
  ok   h200 键集：原有 24 帖全部出现、无重复、严格降序
  ok   h200 键集：游标之下的新帖可见；已读区间内与更新的帖本轮不可见（声明的边界）
  ok   h200 从顶部刷新后看到 24+5 帖 (expected=29, actual=29)
  ok   h200 刷新结果无重复 (expected=29, actual=29)
  note h200 OFFSET page2 repeated 3 items of page1
  ok   negative control exposes: h200 OFFSET 分页在顶部插入后第 2 页重复第 1 页的帖

CASE 31-E :: 撤销：取消关注、拉黑、帖子改为 SELF 后，过滤读取不再返回；直接返回收件箱行会泄露；恢复操作的结果
  ok   push 撤销前 4 帖可见 (expected=4, actual=4)
  ok   pull 撤销前 4 帖可见 (expected=4, actual=4)
  ok   h50 撤销前 4 帖可见 (expected=4, actual=4)
  ok   h200 撤销前 4 帖可见 (expected=4, actual=4)
  note ops: unfollow(user-8301->user-8311), block(user-8312 blocks user-8301), post 730004 -> SELF
  ok   push 过滤读取返回 0 帖 (expected=0, actual=0)
  note push raw inbox rows=4 : 730004=DENY/self-only 730003=DENY/truth 730002=ALLOW/public-block-check 730001=DENY/truth
  ok   negative control exposes: push 直接返回收件箱行时，3 帖是 AccessCheck 拒绝的内容
  ok   pull 过滤读取返回 0 帖 (expected=0, actual=0)
  ok   h50 过滤读取返回 0 帖 (expected=0, actual=0)
  note h50 raw inbox rows=3 : 730004=DENY/self-only 730002=ALLOW/public-block-check 730001=DENY/truth
  ok   negative control exposes: h50 直接返回收件箱行时，2 帖是 AccessCheck 拒绝的内容
  ok   h200 过滤读取返回 0 帖 (expected=0, actual=0)
  note h200 raw inbox rows=3 : 730004=DENY/self-only 730002=ALLOW/public-block-check 730001=DENY/truth
  ok   negative control exposes: h200 直接返回收件箱行时，2 帖是 AccessCheck 拒绝的内容
  note push after unblock + refollow + PUBLIC: [730004, 730002, 730001]
  ok   push 恢复后：重新关注的作者与改回公开的帖可见，解除拉黑不恢复旧关注 (expected=[730004, 730002, 730001], actual=[730004, 730002, 730001])
  note pull after unblock + refollow + PUBLIC: [730004, 730002, 730001]
  ok   pull 恢复后：重新关注的作者与改回公开的帖可见，解除拉黑不恢复旧关注 (expected=[730004, 730002, 730001], actual=[730004, 730002, 730001])
  note h50 after unblock + refollow + PUBLIC: [730004, 730002, 730001]
  ok   h50 恢复后：重新关注的作者与改回公开的帖可见，解除拉黑不恢复旧关注 (expected=[730004, 730002, 730001], actual=[730004, 730002, 730001])
  note h200 after unblock + refollow + PUBLIC: [730004, 730002, 730001]
  ok   h200 恢复后：重新关注的作者与改回公开的帖可见，解除拉黑不恢复旧关注 (expected=[730004, 730002, 730001], actual=[730004, 730002, 730001])

CASE 31-F :: 负向对照：取消关注时删除收件箱行，与未提交的扇出事务交错，删除漏掉在途行
  note cleanup deleted=1 raw inbox after publish commit=[740002]
  ok   清理删除了已提交的 740001 (expected=1, actual=1)
  ok   negative control exposes: 在途扇出的 740002 在清理之后仍留在收件箱
  ok   AccessCheck 拒绝 740002（FOLLOWERS，已取消关注）
  ok   读取时过滤仍返回 0 帖 (expected=0, actual=0)

CASE 31-G :: 真实线程：作者连发 5 帖的同时拉黑读者，20 轮；收件箱可能残留行，过滤读取一条都不返回
  note push rounds=20 leftover inbox rows (scheduling-dependent)=45
  note h50 rounds=20 leftover inbox rows (scheduling-dependent)=33
  ok   无异常 (expected=0, actual=0)
  ok   拉黑提交后，过滤读取与 AccessCheck 都不放行被拉黑作者的帖 (expected=0, actual=0)

CASE 31-H :: 混合方案：作者粉丝数跨越阈值上下，按帖记录的投递方式读取不重不漏；读时按当前计数分类会重复或遗漏
  note delivery: 760001=PUSH 760002=PUSH 760003=PULL 760004=PULL 760005=PUSH
  note count now=3 per-post delivery=[760005, 760004, 760003, 760002, 760001] classify-at-read=[760005, 760002, 760001] dup=0 missing=2
  ok   count=3 按帖投递方式读取：5 帖各一次 (expected=[760005, 760004, 760003, 760002, 760001], actual=[760005, 760004, 760003, 760002, 760001])
  ok   negative control exposes: count=3 读时分类遗漏 2 帖
  note count now=60 per-post delivery=[760005, 760004, 760003, 760002, 760001] classify-at-read=[760005, 760005, 760004, 760003, 760002, 760002, 760001, 760001] dup=3 missing=0
  ok   count=60 按帖投递方式读取：5 帖各一次 (expected=[760005, 760004, 760003, 760002, 760001], actual=[760005, 760004, 760003, 760002, 760001])
  ok   negative control exposes: count=60 读时分类重复 3 帖

CASE 31-I :: 边界：先发帖后关注；推送方案不回填旧帖，拉取方案能看到旧帖；之后的新帖三种方案一致
  note old posts visible after follow: {push=[], pull=[770002, 770001], h50=[770002], h200=[770002]}
  ok   push 不回填：关注前的两帖都不在 Feed (expected=[], actual=[])
  ok   pull 读时取作者时间线：两帖都在 (expected=[770002, 770001], actual=[770002, 770001])
  ok   h50 只看到拉取档作者 user-8812 的旧帖 (expected=[770002], actual=[770002])
  ok   缺失不是权限问题：AccessCheck 允许 770001
  ok   push 关注之后的新帖都在首页顶部 (expected=[770004, 770003], actual=[770004, 770003])
  ok   pull 关注之后的新帖都在首页顶部 (expected=[770004, 770003], actual=[770004, 770003])
  ok   h50 关注之后的新帖都在首页顶部 (expected=[770004, 770003], actual=[770004, 770003])
  ok   h200 关注之后的新帖都在首页顶部 (expected=[770004, 770003], actual=[770004, 770003])

CASE 31-J :: 恢复：推送方案中，热点作者的发布事务在扇出后、提交前被 pg_terminate_backend 终止；帖与收件箱行都不存在，重试一次扇出、再重试为 NOOP
  note inside tx: inbox rows written but uncommitted=1090
  note pg_terminate_backend(2343294, 5000ms)=true backendStillListed=0
  note publish failed (last exception seen by caller): 57P01 FATAL: terminating connection due to administrator command
  ok   提交前被终止的发布返回错误
  ok   帖子行不存在 (expected=0, actual=0)
  ok   收件箱行不存在 (expected=0, actual=0)
  note retry=PUSH inboxRows=1090 second retry=NOOP inboxRows=1090
  ok   重试后收件箱行数等于粉丝数 (expected=1090, actual=1090)
  ok   再次重试为 NOOP (expected=NOOP, actual=NOOP)
  ok   再次重试不新增收件箱行 (expected=1090, actual=1090)

SUMMARY passed=93 failed=0
