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

  1. Retry the transaction from scratch after the conflict (re-read keys and re-apply changes)
  2. Ensure no concurrent transaction deletes keys another transaction has read
  3. In tests, avoid interleaving reset() with transactions that hold observed versions
  4. 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

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


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/ba0fa342d3e249f7. Report an issue: GitHub.