juicedata/juicefs · warning
write conflict: %s was version %d, now deleted
Error message
write conflict: %s was version %d, now deleted
What it means
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.
Source
Thrown at pkg/meta/tkv_mem.go:257
func (c *memKV) txn(ctx context.Context, f func(*kvTxn) error, retry int) error {
tx := &memTxn{
store: c,
observed: make(map[string]int),
buffer: make(map[string][]byte),
}
if err := f(&kvTxn{tx, retry}); err != nil {
return err
}
if len(tx.buffer) == 0 {
return nil
}
c.Lock()
defer c.Unlock()
for k, ver := range tx.observed {
it := c.get(k)
if it == nil && ver != 0 {
return fmt.Errorf("write conflict: %s was version %d, now deleted", k, ver)
} else if it != nil && it.ver > ver {
return fmt.Errorf("write conflict: %s %d > %d", k, it.ver, ver)
}
}
if _, ok := tx.buffer["setting"]; ok {
d, _ := json.Marshal(tx.buffer)
if err := os.WriteFile(settingPath, d, 0644); err != nil {
return err
}
}
for k, value := range tx.buffer {
c.set(k, value)
}
return nil
}
func (c *memKV) scan(prefix []byte, handler func(key []byte, value []byte) bool) error {
c.Lock()View on GitHub (pinned to c9a67b23e8)
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
Example fix
// before: commit stale tx
err := tx.commit()
// after: retry loop on conflict
for {
err := tx.commit()
if err == nil || !strings.Contains(err.Error(), "write conflict") { break }
tx = store.txn()
/* redo reads/updates */
} Defensive patterns
Strategy: retry
Try / catch
for i := 0; i < maxRetries; i++ {
err := tx.commit()
if err == nil || !strings.Contains(err.Error(), "write conflict") { return err }
time.Sleep(backoff)
/* rebuild tx */
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- write conflict: %s %d > %d
- concurrent staging limit reached
- object key cannot be empty
- multipart upload error: %s
- errUnstableCoLimit
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/ba0fa342d3e249f7.
Report an issue: GitHub.