{"record":{"id":"5e79e7fd2814234d","repo":"weaviate/weaviate","slug":"concurrent-transaction","errorCode":null,"errorMessage":"concurrent transaction","messagePattern":"concurrent transaction","errorType":"exception","errorClass":null,"httpStatus":409,"severity":"error","filePath":"usecases/cluster/transactions_write.go","lineNumber":31,"sourceCode":"\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\tenterrors \"github.com/weaviate/weaviate/entities/errors\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype TransactionType string\n\nvar (\n\tErrConcurrentTransaction = errors.New(\"concurrent transaction\")\n\tErrInvalidTransaction    = errors.New(\"invalid transaction\")\n\tErrExpiredTransaction    = errors.New(\"transaction TTL expired\")\n\tErrNotReady              = errors.New(\"server is not ready: either starting up or shutting down\")\n)\n\ntype Remote interface {\n\tBroadcastTransaction(ctx context.Context, tx *Transaction) error\n\tBroadcastAbortTransaction(ctx context.Context, tx *Transaction) error\n\tBroadcastCommitTransaction(ctx context.Context, tx *Transaction) error\n}\n\ntype (\n\tCommitFn   func(ctx context.Context, tx *Transaction) error\n\tResponseFn func(ctx context.Context, tx *Transaction) ([]byte, error)\n)\n\ntype TxManager struct {\n\tsync.Mutex","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/weaviate/weaviate/blob/75aa4b6d11f8818305aafd4440b4e32794f7ca04/usecases/cluster/transactions_write.go#L13-L49","documentation":"ErrConcurrentTransaction signals that a distributed (2PC-style) transaction cannot be opened because another transaction is already in progress on the coordinator/peer. It is sentinel-matched over the wire: the REST cluster API translates it to HTTP 409, and remote clients re-create it when they receive 409.","triggerScenarios":"Calling OpenTransaction (or IncomingBeginTransaction / remote begin) while another write transaction is active — e.g. two concurrent streaming batch imports, or a client POSTing to /cluster/transactions/ when a transaction with a different ID is already open.","commonSituations":"Two clients starting batch writes to the same shard concurrently; a stale previous transaction not yet committed/aborted blocking a new one; retry logic racing with the original request after a timeout.","solutions":["Serialize transactional writes in the client — wait for the current transaction (commit/abort) before opening a new one.","Treat HTTP 409 / errors.Is(err, ErrConcurrentTransaction) as retryable with backoff.","Check for stuck transactions (expired TTL cleanup) and ensure commit/abort is always called, even on client failure."],"exampleFix":"// before\ntx, err := client.OpenTransaction(ctx, txID, txPayload)\n// after\nvar statusErr interface{ StatusCode() int }\n_ = statusErr\nerr = backoff.Retry(func() error {\n  tx, err = client.OpenTransaction(ctx, txID, txPayload)\n  if errors.Is(err, cluster.ErrConcurrentTransaction) {\n    return err // retryable\n  }\n  return backoff.Permanent(err)\n}, backoff.WithContext(ctx))","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Go\nerr := client.OpenTransaction(ctx, id, payload)\nif errors.Is(err, cluster.ErrConcurrentTransaction) {\n    // HTTP 409 — retry with backoff after current tx finishes\n    return backoff.RetryNotify(op, backoff.NewExponentialBackOff(), nil)\n}","preventionTips":["Serialize transactional (batch/streaming) writes per shard in your client.","Always commit or abort transactions, including on error paths.","Match sentinels with errors.Is, not string comparison."],"tags":["cluster","transactions","concurrency","http-409"],"backgroundTag":"concurrent-transaction-conflict","analyzedSha":"75aa4b6d11f8818305aafd4440b4e32794f7ca04","analyzedAt":"2026-09-04T14:58:20.392Z","contentChangedAt":"2026-09-04T14:58:20.392Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}