weaviate/weaviate · error
concurrent transaction
Error message
concurrent transaction
What it means
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.
Source
Thrown at usecases/cluster/transactions_write.go:31
import (
"context"
"fmt"
"slices"
"sync"
"time"
enterrors "github.com/weaviate/weaviate/entities/errors"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type TransactionType string
var (
ErrConcurrentTransaction = errors.New("concurrent transaction")
ErrInvalidTransaction = errors.New("invalid transaction")
ErrExpiredTransaction = errors.New("transaction TTL expired")
ErrNotReady = errors.New("server is not ready: either starting up or shutting down")
)
type Remote interface {
BroadcastTransaction(ctx context.Context, tx *Transaction) error
BroadcastAbortTransaction(ctx context.Context, tx *Transaction) error
BroadcastCommitTransaction(ctx context.Context, tx *Transaction) error
}
type (
CommitFn func(ctx context.Context, tx *Transaction) error
ResponseFn func(ctx context.Context, tx *Transaction) ([]byte, error)
)
type TxManager struct {
sync.MutexView on GitHub (pinned to 75aa4b6d11)
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.
Example fix
// before
tx, err := client.OpenTransaction(ctx, txID, txPayload)
// after
var statusErr interface{ StatusCode() int }
_ = statusErr
err = backoff.Retry(func() error {
tx, err = client.OpenTransaction(ctx, txID, txPayload)
if errors.Is(err, cluster.ErrConcurrentTransaction) {
return err // retryable
}
return backoff.Permanent(err)
}, backoff.WithContext(ctx)) Defensive patterns
Strategy: retry
Try / catch
// Go
err := client.OpenTransaction(ctx, id, payload)
if errors.Is(err, cluster.ErrConcurrentTransaction) {
// HTTP 409 — retry with backoff after current tx finishes
return backoff.RetryNotify(op, backoff.NewExponentialBackOff(), nil)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- invalid transaction
- transaction TTL expired
- server is not ready: either starting up or shutting down
- export already active
- open transaction
AI-assisted analysis of weaviate/weaviate@75aa4b6d11 (2026-09-04).
Data as JSON: /api/errors/5e79e7fd2814234d.
Report an issue: GitHub.