main.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. package main
  2. // =============================================================================
  3. // txn_crash_chaos — 事务混沌压测
  4. //
  5. // 验证事务语义在随机断电下的正确性, 不需要清单文件 (manifest):
  6. // - 每个事务写 txnSize 行 (列: id / txn / seq / 校验和 / 负载);
  7. // - commit 还是 rollback 由 decisionFor(txn) 确定性决定 (同一 txn 双方各自
  8. // 重算, 父进程由此得知"哪些事务必须可见" —— 子进程不写任何旁路记录);
  9. // - 父进程在随机时刻强杀子进程 (大量杀点落在事务中途 / Commit 中途),
  10. // 重开后从库内容反推并断言:
  11. // 1. 原子性: 每个可见事务必须完整 (seq 恰为 0..txnSize-1, 一行不多
  12. // 不少) —— 半截事务 = 提交边界被破坏;
  13. // 2. 持久性: 每个已决提交的事务 (≤ 最大可见事务号) 必须全部可见;
  14. // 3. 回滚: 任何回滚事务的行必须一行都不剩; 被杀时正在执行的事务
  15. // (最大可见事务号 +1) 必须零行;
  16. // 4. 行完整性: 每行负载长度 + XXH32 校验和必须对上 (半条/错位/旧值);
  17. // 5. 元数据: TotalRows 与实际可见行一致; 缺失的 id 只能属于回滚
  18. // 事务或被杀的在途事务 (id 按 (txn-1)*txnSize+seq+1 连续发放)。
  19. //
  20. // 默认 60 轮 (env TSDB_CRASH_ROUNDS), 种子可复现 (env TSDB_CRASH_SEED)。
  21. // 失败保留原始现场 (evidence 副本) 并打印复现命令。
  22. // =============================================================================
  23. import (
  24. "encoding/binary"
  25. "fmt"
  26. "io"
  27. "math/rand"
  28. "os"
  29. "os/exec"
  30. "path/filepath"
  31. "strconv"
  32. "time"
  33. "git.ycl.pub/yl32940/TsDB2/DB/core"
  34. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  35. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  36. )
  37. const (
  38. envChild = "TSDB_CRASH_CHAOS_CHILD"
  39. envDB = "TSDB_CRASH_DB"
  40. envRounds = "TSDB_CRASH_ROUNDS"
  41. envSeed = "TSDB_CRASH_SEED"
  42. envWindow = "TSDB_CRASH_WINDOW"
  43. envClean = "TSDB_CRASH_CLEAN_TXNS"
  44. txnSize = 5 // 每个事务的行数
  45. endGap = 32 // 连续多少个 id 缺失判定为扫描终点 (回滚一次最多浪费 txnSize 个)
  46. )
  47. var killWindow = envInt(envWindow, 300)
  48. func main() {
  49. if os.Getenv(envChild) == "1" {
  50. runChild()
  51. return
  52. }
  53. runParent()
  54. }
  55. // =============================================================================
  56. // 确定性决策与负载: 父子进程各自重算, 不写任何旁路记录
  57. // =============================================================================
  58. // decisionFor 某事务是提交还是回滚: 12.5% 回滚。splitmix64 纯位运算, 跨进程一致。
  59. func decisionFor(txn uint64) (rollback bool) {
  60. s := txn*0x9E3779B97F4A7C15 + 0xD1B54A32D192ED03
  61. s ^= s >> 12
  62. s ^= s << 25
  63. s ^= s >> 27
  64. s *= 0x2545F4914F6CDD1D
  65. return s%8 == 0
  66. }
  67. func payloadFor(id uint64) []byte {
  68. buf := make([]byte, 128)
  69. s := id*0x9E3779B97F4A7C15 + 0xD1B54A32D192ED03
  70. for i := 0; i < len(buf); i += 8 {
  71. s ^= s >> 12
  72. s ^= s << 25
  73. s ^= s >> 27
  74. binary.LittleEndian.PutUint64(buf[i:], s*0x2545F4914F6CDD1D)
  75. }
  76. return buf
  77. }
  78. func checksumFor(id uint64) uint64 { return uint64(core.XXH32(payloadFor(id))) }
  79. // =============================================================================
  80. // 子进程: 循环 "Begin → 插 txnSize 行 → 按决策 Commit/Rollback", 直到被强杀
  81. // =============================================================================
  82. func runChild() {
  83. path := os.Getenv(envDB)
  84. db, err := core.Open(path)
  85. if err != nil {
  86. fmt.Fprintln(os.Stderr, "open:", err)
  87. os.Exit(2)
  88. }
  89. db.SetAutoCommitDelay(0) // 提交边界完全由显式 Commit 决定
  90. tbl, err := db.CreateTable("t", []types.FieldInfo{
  91. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  92. {FieldType: types.FieldType_Uint64, FieldName: []byte("txn")},
  93. {FieldType: types.FieldType_Uint64, FieldName: []byte("seq")},
  94. {FieldType: types.FieldType_Uint64, FieldName: []byte("c")},
  95. {FieldType: types.FieldType_String, Flag: types.WidthCodeFor(128), FieldName: []byte("p")},
  96. })
  97. if err != nil {
  98. fmt.Fprintln(os.Stderr, "create table:", err)
  99. os.Exit(2)
  100. }
  101. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  102. fmt.Fprintln(os.Stderr, "create index:", err)
  103. os.Exit(2)
  104. }
  105. var id uint64
  106. for txn := uint64(1); ; txn++ {
  107. tx := db.BeginTransaction()
  108. for seq := uint64(0); seq < txnSize; seq++ {
  109. id++
  110. if _, err := tbl.InsertRowWithID(id, []types.FieldValue{
  111. {FieldName: []byte("id"), Value: id},
  112. {FieldName: []byte("txn"), Value: txn},
  113. {FieldName: []byte("seq"), Value: seq},
  114. {FieldName: []byte("c"), Value: checksumFor(id)},
  115. {FieldName: []byte("p"), Value: string(payloadFor(id))},
  116. }); err != nil {
  117. fmt.Fprintln(os.Stderr, "insert fail: id", id, "txn", txn, "seq", seq, "err:", err)
  118. _ = tx.Rollback()
  119. os.Exit(2)
  120. }
  121. }
  122. if decisionFor(txn) {
  123. if err := tx.Rollback(); err != nil {
  124. fmt.Fprintln(os.Stderr, "rollback txn", txn, ":", err)
  125. os.Exit(2)
  126. }
  127. } else {
  128. if err := tx.Commit(); err != nil {
  129. fmt.Fprintln(os.Stderr, "commit txn", txn, ":", err)
  130. os.Exit(2)
  131. }
  132. }
  133. // 干净模式: 跑满 N 个事务后正常关闭, 校验装置本身
  134. if clean := envInt(envClean, 0); clean > 0 && int(txn) == clean {
  135. if err := db.Close(); err != nil {
  136. fmt.Fprintln(os.Stderr, "close:", err)
  137. os.Exit(2)
  138. }
  139. os.Exit(0)
  140. }
  141. }
  142. }
  143. // =============================================================================
  144. // 父进程
  145. // =============================================================================
  146. func runParent() {
  147. rounds := envInt(envRounds, 60)
  148. baseSeed := int64(envInt(envSeed, 1))
  149. rng := rand.New(rand.NewSource(baseSeed))
  150. fmt.Printf("事务混沌压测: %d 轮, 种子 %d\n", rounds, baseSeed)
  151. for r := 1; r <= rounds; r++ {
  152. if err := oneRound(rng); err != nil {
  153. fmt.Printf("❌ 第 %d 轮失败 (种子 %d): %v\n", r, baseSeed, err)
  154. fmt.Printf(" 复现: %s=%d %s=%d go run main.go\n", envSeed, baseSeed, envRounds, r)
  155. os.Exit(1)
  156. }
  157. if r%20 == 0 {
  158. fmt.Printf(" 进度 %d/%d 通过\n", r, rounds)
  159. }
  160. }
  161. fmt.Printf("✅ 事务混沌压测通过: %d 轮 —— 事务原子性/持久性/回滚在随机断电下全部成立\n", rounds)
  162. }
  163. func oneRound(rng *rand.Rand) error {
  164. dir, err := os.MkdirTemp("", "tsdb-txn-chaos-")
  165. if err != nil {
  166. return err
  167. }
  168. path := filepath.Join(dir, "txn.db")
  169. exe, err := os.Executable()
  170. if err != nil {
  171. os.RemoveAll(dir)
  172. return err
  173. }
  174. cmd := exec.Command(exe)
  175. cmd.Env = append(os.Environ(), envChild+"=1", envDB+"="+path)
  176. cmd.Stderr = os.Stderr // 子进程诊断直接透传 (干净模式排障用)
  177. if clean := envInt(envClean, 0); clean > 0 {
  178. cmd.Env = append(cmd.Env, envClean+"="+strconv.Itoa(clean))
  179. }
  180. if err := cmd.Start(); err != nil {
  181. os.RemoveAll(dir)
  182. return err
  183. }
  184. if envInt(envClean, 0) > 0 {
  185. if err := cmd.Wait(); err != nil {
  186. os.RemoveAll(dir)
  187. return fmt.Errorf("干净模式子进程异常退出: %v", err)
  188. }
  189. } else {
  190. time.Sleep(time.Duration(20+rng.Intn(killWindow-20)) * time.Millisecond)
  191. _ = cmd.Process.Kill()
  192. _ = cmd.Wait()
  193. }
  194. if _, err := os.Stat(path); os.IsNotExist(err) {
  195. os.RemoveAll(dir)
  196. return nil
  197. }
  198. evidence := filepath.Join(dir, "txn_evidence.db")
  199. if err := copyFile(path, evidence); err != nil {
  200. return err
  201. }
  202. if err := verify(path, dir); err != nil {
  203. return err
  204. }
  205. return os.RemoveAll(dir)
  206. }
  207. // =============================================================================
  208. // 恢复后校验: 原子性 + 持久性 + 回滚 + 行完整性 + 元数据
  209. // =============================================================================
  210. type row struct {
  211. txn uint64
  212. seq uint64
  213. c uint64
  214. p []byte
  215. }
  216. func verify(path, dir string) error {
  217. db, err := core.Open(path)
  218. if err != nil {
  219. return fmt.Errorf("崩溃后重开失败: %w (现场: %s)", err, dir)
  220. }
  221. defer db.Close()
  222. tbl, ok := db.FindTable("t")
  223. if !ok {
  224. return nil // 建表前被杀, 无对象可验
  225. }
  226. // 逐 id 探测收集全部可见行 (id 连续发放, 回滚/在途事务造成洞)
  227. rows := map[uint64]*row{}
  228. var missRun, maxID uint64
  229. for id := uint64(1); ; id++ {
  230. vals, err := tbl.FindByPK(id)
  231. if err != nil {
  232. if missRun++; missRun >= endGap {
  233. break
  234. }
  235. continue
  236. }
  237. missRun = 0
  238. maxID = id
  239. var rn row
  240. for _, fv := range vals {
  241. switch string(fv.FieldName) {
  242. case "txn":
  243. rn.txn, _ = fv.Value.(uint64)
  244. case "seq":
  245. rn.seq, _ = fv.Value.(uint64)
  246. case "c":
  247. rn.c, _ = fv.Value.(uint64)
  248. case "p":
  249. switch v := fv.Value.(type) {
  250. case string:
  251. rn.p = []byte(v)
  252. case []byte:
  253. rn.p = v
  254. }
  255. }
  256. }
  257. rows[id] = &rn
  258. }
  259. // 行完整性: 负载长度 + 校验和
  260. for id, rn := range rows {
  261. if len(rn.p) != 128 || rn.c != checksumFor(id) || uint64(core.XXH32(rn.p)) != rn.c {
  262. return fmt.Errorf("行 id=%d 数据损坏 (len=%d c=%d want=%d) (现场: %s)",
  263. id, len(rn.p), rn.c, checksumFor(id), dir)
  264. }
  265. }
  266. // 按事务分组, 校验原子性 / 决策一致性
  267. byTxn := map[uint64]map[uint64]uint64{} // txn → seq → id
  268. var maxVisibleTxn uint64
  269. for id, rn := range rows {
  270. if byTxn[rn.txn] == nil {
  271. byTxn[rn.txn] = map[uint64]uint64{}
  272. }
  273. if _, dup := byTxn[rn.txn][rn.seq]; dup {
  274. return fmt.Errorf("事务 %d seq=%d 重复行 (id=%d) (现场: %s)", rn.txn, rn.seq, id, dir)
  275. }
  276. byTxn[rn.txn][rn.seq] = id
  277. if rn.txn > maxVisibleTxn {
  278. maxVisibleTxn = rn.txn
  279. }
  280. }
  281. for txn, seqs := range byTxn {
  282. if decisionFor(txn) {
  283. return fmt.Errorf("回滚事务 %d 仍有 %d 行可见 (回滚失效) (现场: %s)", txn, len(seqs), dir)
  284. }
  285. if txn == maxVisibleTxn+1 {
  286. return fmt.Errorf("在途事务 %d 在恢复后可见 %d 行 (提交边界被破坏) (现场: %s)", txn, len(seqs), dir)
  287. }
  288. // 原子性: 已决事务必须完整 (seq 恰为 0..txnSize-1)
  289. if len(seqs) != txnSize {
  290. return fmt.Errorf("事务 %d 不完整: %d/%d 行可见 (原子性被破坏) (现场: %s)", txn, len(seqs), txnSize, dir)
  291. }
  292. for seq := uint64(0); seq < txnSize; seq++ {
  293. if _, ok := seqs[seq]; !ok {
  294. return fmt.Errorf("事务 %d 缺 seq=%d (原子性被破坏) (现场: %s)", txn, seq, dir)
  295. }
  296. }
  297. }
  298. // 持久性: 每个已决提交且 ≤ 最大可见事务号的事务都必须可见
  299. for txn := uint64(1); txn <= maxVisibleTxn; txn++ {
  300. if !decisionFor(txn) {
  301. if _, ok := byTxn[txn]; !ok {
  302. return fmt.Errorf("已提交事务 %d 不可见 (持久性被破坏) (现场: %s)", txn, dir)
  303. }
  304. }
  305. }
  306. // 元数据: TotalRows 与实际可见行一致
  307. if total := tbl.TotalRows(); total != uint64(len(rows)) {
  308. return fmt.Errorf("TotalRows=%d 但实际可见=%d (现场: %s)", total, len(rows), dir)
  309. }
  310. // id 连续性: 每个缺失的 id 只能属于回滚事务或被杀的在途事务
  311. for id := uint64(1); id <= maxID; id++ {
  312. if rows[id] != nil {
  313. continue
  314. }
  315. txn := (id-1)/txnSize + 1
  316. if txn == maxVisibleTxn+1 {
  317. continue // 在途事务, 允许整段缺失
  318. }
  319. if decisionFor(txn) {
  320. continue // 回滚事务, 允许整段缺失
  321. }
  322. return fmt.Errorf("已提交事务 %d 的 id=%d 缺失 (现场: %s)", txn, id, dir)
  323. }
  324. return nil
  325. }
  326. func copyFile(src, dst string) error {
  327. in, err := os.Open(src)
  328. if err != nil {
  329. return err
  330. }
  331. defer in.Close()
  332. out, err := os.Create(dst)
  333. if err != nil {
  334. return err
  335. }
  336. defer out.Close()
  337. _, err = io.Copy(out, in)
  338. return err
  339. }
  340. func envInt(key string, def int) int {
  341. if v := os.Getenv(key); v != "" {
  342. var n int
  343. if _, err := fmt.Sscanf(v, "%d", &n); err == nil && n > 0 {
  344. return n
  345. }
  346. }
  347. return def
  348. }