{"record":{"id":"4da63966086fad37","repo":"juicedata/juicefs","slug":"write-conflict-s-d-d","errorCode":null,"errorMessage":"write conflict: %s %d > %d","messagePattern":"write conflict: (.+?) (.+?) > (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/meta/tkv_mem.go","lineNumber":259,"sourceCode":"\t\tstore:    c,\n\t\tobserved: make(map[string]int),\n\t\tbuffer:   make(map[string][]byte),\n\t}\n\tif err := f(&kvTxn{tx, retry}); err != nil {\n\t\treturn err\n\t}\n\n\tif len(tx.buffer) == 0 {\n\t\treturn nil\n\t}\n\tc.Lock()\n\tdefer c.Unlock()\n\tfor k, ver := range tx.observed {\n\t\tit := c.get(k)\n\t\tif it == nil && ver != 0 {\n\t\t\treturn fmt.Errorf(\"write conflict: %s was version %d, now deleted\", k, ver)\n\t\t} else if it != nil && it.ver > ver {\n\t\t\treturn fmt.Errorf(\"write conflict: %s %d > %d\", k, it.ver, ver)\n\t\t}\n\t}\n\tif _, ok := tx.buffer[\"setting\"]; ok {\n\t\td, _ := json.Marshal(tx.buffer)\n\t\tif err := os.WriteFile(settingPath, d, 0644); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor k, value := range tx.buffer {\n\t\tc.set(k, value)\n\t}\n\treturn nil\n}\n\nfunc (c *memKV) scan(prefix []byte, handler func(key []byte, value []byte) bool) error {\n\tc.Lock()\n\tsnap := c.items.Clone()\n\tc.Unlock()","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/meta/tkv_mem.go#L241-L277","documentation":"Same commit-time optimistic-concurrency check in tkv_mem.go txn(): this variant fires when the key still exists but its current version (it.ver) is greater than the version the transaction observed (tx.observed[k]), meaning another transaction modified the key after this one read it. The transaction is rejected to preserve serializability.","triggerScenarios":"tx A reads key k at version 5 and buffers a write; tx B updates k (version becomes 6) and commits; tx A commits and the check `it.ver (6) > ver (5)` returns this error.","commonSituations":"Concurrent kvMeta transactions in tests over the mem engine; concurrent metadata updates to the same inode from two sessions; benchmark code racing writers.","solutions":["Retry the transaction: re-read the key at its new version and re-apply the update","Reduce contention by partitioning keys or serializing access to hot keys","In tests, join concurrent goroutines or use distinct key sets per transaction","Upgrade to a real engine (redis/sql/tkv) for production concurrency instead of mem store"],"exampleFix":"// before\nif err := tx.commit(); err != nil { return err }\n// after\nif err := tx.commit(); err != nil {\n  if strings.Contains(err.Error(), \"write conflict\") { return retryTxn() }\n  return err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := tx.commit(); err != nil {\n  if strings.Contains(err.Error(), \"write conflict\") { return retryWithFreshTxn() }\n  return err\n}","preventionTips":["Keep transactions short to reduce overlapping read/write windows on hot keys","Use distinct key sets per concurrent worker in tests","Treat any \"write conflict\" error as retryable by design"],"tags":["concurrency","transaction","optimistic-locking","testing"],"backgroundTag":"optimistic-concurrency-conflict","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}