{"record":{"id":"ba0fa342d3e249f7","repo":"juicedata/juicefs","slug":"write-conflict-s-was-version-d-now-deleted","errorCode":null,"errorMessage":"write conflict: %s was version %d, now deleted","messagePattern":"write conflict: (.+?) was version (.+?), now deleted","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/meta/tkv_mem.go","lineNumber":257,"sourceCode":"func (c *memKV) txn(ctx context.Context, f func(*kvTxn) error, retry int) error {\n\ttx := &memTxn{\n\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()","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/meta/tkv_mem.go#L239-L275","documentation":"The in-memory TKV client (tkv_mem.go) implements optimistic concurrency: at commit, txn() replays the transaction's observed key versions against the store's current state. This error is returned when a key the transaction observed with a nonzero version no longer exists at commit time, i.e. another transaction deleted it in between. The transaction is rejected so stale writes are not applied.","triggerScenarios":"Two concurrent transactions on the mem store: tx A reads a key (records its version in tx.observed), tx B deletes that key and commits; tx A then commits and the conflict check finds get(k) == nil while ver != 0.","commonSituations":"Unit/integration tests of kvMeta over the mem engine exercising concurrent transactions; a `reset` racing with an in-flight transaction; test code that mutates the store between tx creation and commit.","solutions":["Retry the transaction from scratch after the conflict (re-read keys and re-apply changes)","Ensure no concurrent transaction deletes keys another transaction has read","In tests, avoid interleaving reset() with transactions that hold observed versions","Serialize transactions over the shared keys if optimistic retry is not implemented"],"exampleFix":"// before: commit stale tx\nerr := tx.commit()\n// after: retry loop on conflict\nfor {\n  err := tx.commit()\n  if err == nil || !strings.Contains(err.Error(), \"write conflict\") { break }\n  tx = store.txn()\n  /* redo reads/updates */\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for i := 0; i < maxRetries; i++ {\n  err := tx.commit()\n  if err == nil || !strings.Contains(err.Error(), \"write conflict\") { return err }\n  time.Sleep(backoff)\n  /* rebuild tx */\n}","preventionTips":["Design transactions to retry on conflict rather than assume success","Avoid sharing/deleting keys across concurrent transactions in tests","Don't interleave reset() with live transactions"],"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-14T00:17:10.932Z"}