{"record":{"id":"4b7a03f23f5fc834","repo":"apache/pulsar","slug":"version-mismatch-actual-s-expect-s","errorCode":null,"errorMessage":"Version mismatch, actual=%s, expect=%s","messagePattern":"Version mismatch, actual=(.+?), expect=(.+?)","errorType":"exception","errorClass":"MetadataStoreException.BadVersionException","httpStatus":null,"severity":"error","filePath":"pulsar-metadata/src/main/java/org/apache/pulsar/metadata/impl/RocksdbMetadataStore.java","lineNumber":573,"sourceCode":"    }\n\n    @Override\n    protected CompletableFuture<Void> storeDelete(String path, Optional<Long> expectedVersion, Set<Option> opts) {\n        log.debug().attr(\"path\", path).attr(\"instanceId\", instanceId).log(\"storeDelete\");\n        try {\n            dbStateLock.readLock().lock();\n            if (isClosed()) {\n                return alreadyClosedFailedFuture();\n            }\n            try (Transaction transaction = db.beginTransaction(writeOptions)) {\n                byte[] pathBytes = toBytes(path);\n                byte[] oldValueData = transaction.getForUpdate(optionDontCache, pathBytes, true);\n                MetaValue metaValue = MetaValue.parse(oldValueData);\n                if (metaValue == null) {\n                    throw new MetadataStoreException.NotFoundException(String.format(\"path %s not found.\", path));\n                }\n                if (expectedVersion.isPresent() && !expectedVersion.get().equals(metaValue.getVersion())) {\n                    throw new MetadataStoreException.BadVersionException(\n                            String.format(\"Version mismatch, actual=%s, expect=%s\", metaValue.getVersion(),\n                                    expectedVersion.get()));\n                }\n                transaction.delete(pathBytes);\n                transaction.commit();\n                receivedNotification(new Notification(NotificationType.Deleted, path));\n                notifyParentChildrenChanged(path);\n                return CompletableFuture.completedFuture(null);\n            }\n        } catch (Throwable e) {\n            log.debug().attr(\"path\", path).exception(e).log(\"error in storeDelete\");\n            return FutureUtil.failedFuture(MetadataStoreException.wrap(e));\n        } finally {\n            dbStateLock.readLock().unlock();\n        }\n    }\n\n    @Override","sourceCodeStart":555,"sourceCodeEnd":591,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-metadata/src/main/java/org/apache/pulsar/metadata/impl/RocksdbMetadataStore.java#L555-L591","documentation":"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.","triggerScenarios":"Calling storeDelete/delete(path, expectedVersion) where another writer has modified the record since the caller read its version, so expectedVersion != metaValue.getVersion().","commonSituations":"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.","solutions":["Re-read the current version (get) and retry the delete with the fresh version.","Catch BadVersionException and implement compare-and-swap retry with backoff.","Serialize deletes for the same path through a single writer/coordinator.","Adopt the notification mechanism to invalidate cached versions promptly."],"exampleFix":"// before\nlong ver = getVersion(path);\nstore.delete(path, Optional.of(ver)).join(); // fails if path changed\n// after\nretryLoop: for (int i = 0; i < 5; i++) {\n    try {\n        store.get(path).thenApply(v -> v.map(Stat::getVersion))\n             .thenCompose(opt -> store.delete(path, opt)).join();\n        break;\n    } catch (CompletionException e) {\n        if (!(e.getCause() instanceof MetadataStoreException.BadVersionException)) throw e;\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    store.delete(path, Optional.of(expectedVersion)).join();\n} catch (CompletionException e) {\n    if (e.getCause() instanceof MetadataStoreException.BadVersionException) {\n        // re-read version and retry CAS delete\n    } else throw e;\n}","preventionTips":["Always re-read the version immediately before a versioned delete","Bound CAS retries with backoff","Avoid caching versions across multiple operations"],"tags":["rocksdb","optimistic-concurrency","version-conflict"],"backgroundTag":"bad-version","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}