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.Mutex

View on GitHub (pinned to 75aa4b6d11)

Solutions

  1. Serialize transactional writes in the client — wait for the current transaction (commit/abort) before opening a new one.
  2. Treat HTTP 409 / errors.Is(err, ErrConcurrentTransaction) as retryable with backoff.
  3. 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

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


AI-assisted analysis of weaviate/weaviate@75aa4b6d11 (2026-09-04). Data as JSON: /api/errors/5e79e7fd2814234d. Report an issue: GitHub.