apache/pulsar · error · MetadataStoreException.BadVersionException

Version mismatch, actual=%s, expect=%s

Error message

Version mismatch, actual=%s, expect=%s

What it means

storeDelete supports optimistic concurrency: when a caller passes an expected version, the record's current version read inside the transaction must equal it. If it differs, a MetadataStoreException.BadVersionException with 'Version mismatch, actual=%s, expect=%s' is thrown and the delete is aborted.

Source

Thrown at pulsar-metadata/src/main/java/org/apache/pulsar/metadata/impl/RocksdbMetadataStore.java:573

    }

    @Override
    protected CompletableFuture<Void> storeDelete(String path, Optional<Long> expectedVersion, Set<Option> opts) {
        log.debug().attr("path", path).attr("instanceId", instanceId).log("storeDelete");
        try {
            dbStateLock.readLock().lock();
            if (isClosed()) {
                return alreadyClosedFailedFuture();
            }
            try (Transaction transaction = db.beginTransaction(writeOptions)) {
                byte[] pathBytes = toBytes(path);
                byte[] oldValueData = transaction.getForUpdate(optionDontCache, pathBytes, true);
                MetaValue metaValue = MetaValue.parse(oldValueData);
                if (metaValue == null) {
                    throw new MetadataStoreException.NotFoundException(String.format("path %s not found.", path));
                }
                if (expectedVersion.isPresent() && !expectedVersion.get().equals(metaValue.getVersion())) {
                    throw new MetadataStoreException.BadVersionException(
                            String.format("Version mismatch, actual=%s, expect=%s", metaValue.getVersion(),
                                    expectedVersion.get()));
                }
                transaction.delete(pathBytes);
                transaction.commit();
                receivedNotification(new Notification(NotificationType.Deleted, path));
                notifyParentChildrenChanged(path);
                return CompletableFuture.completedFuture(null);
            }
        } catch (Throwable e) {
            log.debug().attr("path", path).exception(e).log("error in storeDelete");
            return FutureUtil.failedFuture(MetadataStoreException.wrap(e));
        } finally {
            dbStateLock.readLock().unlock();
        }
    }

    @Override

View on GitHub (pinned to 820761864e)

Solutions

  1. Re-read the current version (get) and retry the delete with the fresh version.
  2. Catch BadVersionException and implement compare-and-swap retry with backoff.
  3. Serialize deletes for the same path through a single writer/coordinator.
  4. Adopt the notification mechanism to invalidate cached versions promptly.

Example fix

// before
long ver = getVersion(path);
store.delete(path, Optional.of(ver)).join(); // fails if path changed
// after
retryLoop: for (int i = 0; i < 5; i++) {
    try {
        store.get(path).thenApply(v -> v.map(Stat::getVersion))
             .thenCompose(opt -> store.delete(path, opt)).join();
        break;
    } catch (CompletionException e) {
        if (!(e.getCause() instanceof MetadataStoreException.BadVersionException)) throw e;
    }
}
Defensive patterns

Strategy: retry

Try / catch

try {
    store.delete(path, Optional.of(expectedVersion)).join();
} catch (CompletionException e) {
    if (e.getCause() instanceof MetadataStoreException.BadVersionException) {
        // re-read version and retry CAS delete
    } else throw e;
}

Prevention

When it happens

Trigger: Calling storeDelete/delete(path, expectedVersion) where another writer has modified the record since the caller read its version, so expectedVersion != metaValue.getVersion().

Common situations: Concurrent updates between read and delete in multi-writer setups; stale version cached after another instance's notification; app retry loops reusing an old version instead of re-reading.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/4b7a03f23f5fc834. Report an issue: GitHub.