[Composite Engine] DataFormatAwareEngine.close() deadlocks with in-flight merges on index delete → wedges cluster applier
Summary
When an index is deleted while composite (columnar/Parquet) merges are in flight, the shard-close path in DataFormatAwareEngine can deadlock permanently. The close runs on the cluster-applier thread, so once it blocks the node stops applying cluster state entirely: it is LagDetector / follower-lag evicted, shards cannot initialize on it, and the deleted index's on-disk files are never reclaimed.
Environment
OpenSearch 3.5.x, composite/columnar data-format engine (engine_mode=analytics), segment remote-store enabled, JDK 25. Reproduced on a 40 data-node cluster (r8g.4xlarge).
Symptoms
- Cluster-manager
LagDetector:node [...] is lagging at cluster state version [0], although publication of version [N] completed [5m] ago. - The affected node shows
disk.used ≈ 483 GBwhiledisk.indices = 62 KB— orphaned files from the deleted index that were never garbage-collected; RAM ~99%. - A shard from an
auto_expand_replicas: 0-allindex (which places a copy on every node) is stuckINITIALIZINGon that node, leaving the clusteryellow.
Root cause (from thread dump)
The cluster-applier thread is blocked closing the deleted index's shard, waiting on the engine's close latch:
"opensearch[...][clusterApplierService#updateTask][T#1]"
java.util.concurrent.CountDownLatch.await(CountDownLatch.java:230)
org.opensearch.index.engine.DataFormatAwareEngine.awaitPendingClose(DataFormatAwareEngine.java:2339)
org.opensearch.index.engine.DataFormatAwareEngine.close(DataFormatAwareEngine.java:2103)
org.opensearch.common.util.io.IOUtils.close(IOUtils.java:81)
org.opensearch.index.shard.IndexShard.close(IndexShard.java:2795)
org.opensearch.index.IndexService.closeShard(IndexService.java:1038)
org.opensearch.index.IndexService.removeShard(IndexService.java:1014)
org.opensearch.index.IndexService.close(IndexService.java:610)
org.opensearch.indices.IndicesService.removeIndex(IndicesService.java:1520)
org.opensearch.indices.cluster.IndicesClusterStateService.deleteIndices(IndicesClusterStateService.java:374)
org.opensearch.indices.cluster.IndicesClusterStateService.applyClusterState(IndicesClusterStateService.java:304)
org.opensearch.cluster.service.ClusterApplierService.callClusterStateAppliers(ClusterApplierService.java:660)
org.opensearch.cluster.service.ClusterApplierService.applyChanges(ClusterApplierService.java:595)
org.opensearch.cluster.service.ClusterApplierService.runTask(ClusterApplierService.java:516)
org.opensearch.cluster.service.ClusterApplierService$UpdateTask.run(ClusterApplierService.java:212)The composite merge threads it is waiting on are themselves parked acquiring the DataFormatAwareEngine lock, so they never complete:
"opensearch[...][merge][T#4]"
java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:323)
org.opensearch.index.engine.DataFormatAwareEngine.lambda$new$0(DataFormatAwareEngine.java:327)
org.opensearch.be.lucene.index.LuceneCommitter.lambda$createIndexWriterConfig$0(LuceneCommitter.java:374)
org.apache.lucene.index.IndexWriter.mergeMiddle(IndexWriter.java:5488)
org.apache.lucene.index.IndexWriter.merge(IndexWriter.java:4778)
org.apache.lucene.index.MergeIndexWriter.executeMerge(MergeIndexWriter.java:86)
org.opensearch.be.lucene.merge.LuceneMerger.merge(LuceneMerger.java:167)
org.opensearch.composite.merge.CompositeMergeExecutor.mergeFormat(CompositeMergeExecutor.java:108)
org.opensearch.composite.merge.CompositeMergeExecutor.execute(CompositeMergeExecutor.java:61)
org.opensearch.composite.merge.CompositeMerger.lambda$merge$0(CompositeMerger.java:60)
org.opensearch.composite.merge.CompositeMerger.merge(CompositeMerger.java:57)
org.opensearch.index.engine.dataformat.merge.MergeHandler.doMerge(MergeHandler.java:257)
org.opensearch.index.engine.dataformat.merge.MergeScheduler.runMerge(MergeScheduler.java:383)
org.opensearch.index.engine.dataformat.merge.MergeScheduler.lambda$submitMergeTask$1(MergeScheduler.java:352)close() → awaitPendingClose() blocks on a CountDownLatch until in-flight merges drain, but those merges are blocked acquiring the DataFormatAwareEngine lock and never finish → the latch never reaches 0 → close() never returns → the single-threaded cluster applier is wedged permanently and applies no further cluster state.
Impact
The node becomes unresponsive to the cluster manager (lag-evicted), cannot host or initialize shards, and leaks the deleted index's disk. The only recovery is a node restart. Blast radius is worse if the node holds live primaries.
Trigger / repro
Delete an index while composite merges are in flight on a large shard (reproduced via repeated snapshot restore→delete of a ~481 GB shard).
Suggested fix direction
Break the close/merge lock cycle in DataFormatAwareEngine: abort/cancel in-flight merges before awaitPendingClose(), or make the merge-path lock acquisition interruptible/abortable on close, so close() cannot block indefinitely on merges that are themselves waiting on the engine lock.
Source: opensearch-project/OpenSearch