prestodb/presto · error · PrestoException

MULTI_CATALOG_WRITE_CONFLICT

MULTI_CATALOG_WRITE_CONFLICT

Error message

Multi-catalog writes not supported in a single transaction. Attempt write to catalog %s, but already wrote to catalog %s

What it means

Thrown by TransactionMetadata.checkConnectorWrite when a single transaction attempts to write through two different connectors. Presto does not support multi-catalog (cross-connector) writes in one transaction: writtenConnectorId uses compareAndSet to record the first writing catalog, and any subsequent write to a different catalog fails.

Source

Thrown at presto-main-base/src/main/java/com/facebook/presto/transaction/InMemoryTransactionManager.java:589

        {
            if (connector instanceof InternalConnector) {
                return ((InternalConnector) connector).beginTransaction(transactionId, isolationLevel, readOnly);
            }
            else {
                return connector.beginTransaction(isolationLevel, autoCommitContext, readOnly);
            }
        }

        public synchronized void checkConnectorWrite(ConnectorId connectorId)
        {
            checkOpenTransaction();
            ConnectorTransactionMetadata transactionMetadata = connectorIdToMetadata.get(connectorId);
            checkArgument(transactionMetadata != null, "Cannot record write for connector not part of transaction");
            if (readOnly) {
                throw new PrestoException(READ_ONLY_VIOLATION, "Cannot execute write in a read-only transaction");
            }
            if (!writtenConnectorId.compareAndSet(null, connectorId) && !writtenConnectorId.get().equals(connectorId)) {
                throw new PrestoException(
                        MULTI_CATALOG_WRITE_CONFLICT,
                        format(
                                "Multi-catalog writes not supported in a single transaction. Attempt write to catalog %s, but already wrote to catalog %s",
                                connectorId,
                                writtenConnectorId.get()));
            }
            if (transactionMetadata.isSingleStatementWritesOnly() && !autoCommitContext) {
                throw new PrestoException(AUTOCOMMIT_WRITE_CONFLICT, "Catalog " + connectorId + " only supports writes using autocommit");
            }
        }

        public synchronized ListenableFuture<?> asyncCommit()
        {
            if (!completedSuccessfully.compareAndSet(null, true)) {
                if (completedSuccessfully.get()) {
                    // Already done
                    return immediateFuture(null);
                }

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Split the work into separate transactions, one per catalog.
  2. Restrict the transaction's writes to a single catalog.
  3. Use non-transactional (autocommit) execution for the extra-catalog writes.
  4. Redesign the pipeline (e.g. stage data externally) since Presto cannot atomically commit across catalogs.

Example fix

// before
START TRANSACTION;
INSERT INTO hive.t SELECT * FROM src;
INSERT INTO mysql.t SELECT * FROM src;
COMMIT;

// after: separate transactions per catalog
START TRANSACTION; INSERT INTO hive.t SELECT * FROM src; COMMIT;
START TRANSACTION; INSERT INTO mysql.t SELECT * FROM src; COMMIT;
Defensive patterns

Strategy: validation

Validate before calling

// Track which catalogs already received writes in this transaction
Set<ConnectorId> written = new HashSet<>();
written.add(firstConnectorId);
if (!written.contains(targetConnectorId)) {
    // would trigger MULTI_CATALOG_WRITE_CONFLICT — commit first, start new transaction
}

Type guard

boolean isSameCatalog(ConnectorId writtenConnectorId, ConnectorId next) {
    return writtenConnectorId == null || writtenConnectorId.equals(next);
}

Try / catch

try {
    transactionMetadata.checkConnectorWrite(connectorId);
} catch (PrestoException e) {
    if (e.getErrorCode().getCode() == StandardErrorCode.MULTI_CATALOG_WRITE_CONFLICT.toErrorCode().getCode()) {
        // split: finish current transaction, open a new one for this catalog
        transactionManager.asyncCommit(txId);
        txId = transactionManager.beginTransaction(false);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Two statements in the same transaction write to catalogs with different ConnectorIds, so the second checkConnectorWrite finds writtenConnectorId already set to another catalog and the CAS fails.

Common situations: Query writing to two different catalogs (e.g. INSERT into hive then INSERT into mysql) inside one explicit transaction; a federated query with side-effects on multiple connectors; plugin-generated writes to a second catalog.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/fe6b057ee3aa6afb. Report an issue: GitHub.