{"record":{"id":"93df8ddceae4fbd0","repo":"apache/cassandra","slug":"you-can-t-mix-sstables-from-different-directories","errorCode":null,"errorMessage":"You can't mix sstables from different directories in a compaction","messagePattern":"You can't mix sstables from different directories in a compaction","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java","lineNumber":1229,"sourceCode":"    }\n\n    private void validateForCompaction(Iterable<SSTableReader> input)\n    {\n        readLock.lock();\n        try\n        {\n            SSTableReader firstSSTable = Iterables.getFirst(input, null);\n            assert firstSSTable != null;\n            boolean repaired = firstSSTable.isRepaired();\n            int firstIndex = compactionStrategyIndexFor(firstSSTable);\n            boolean isPending = firstSSTable.isPendingRepair();\n            TimeUUID pendingRepair = firstSSTable.getSSTableMetadata().pendingRepair;\n            for (SSTableReader sstable : input)\n            {\n                if (sstable.isRepaired() != repaired)\n                    throw new UnsupportedOperationException(\"You can't mix repaired and unrepaired data in a compaction\");\n                if (firstIndex != compactionStrategyIndexFor(sstable))\n                    throw new UnsupportedOperationException(\"You can't mix sstables from different directories in a compaction\");\n                if (isPending && !pendingRepair.equals(sstable.getSSTableMetadata().pendingRepair))\n                    throw new UnsupportedOperationException(\"You can't compact sstables from different pending repair sessions\");\n            }\n        }\n        finally\n        {\n            readLock.unlock();\n        }\n    }\n\n    public CompactionTasks getMaximalTasks(final long gcBefore, final boolean splitOutput, int permittedParallelism, OperationType operationType)\n    {\n        maybeReloadDiskBoundaries();\n        // runWithCompactionsDisabled cancels active compactions and disables them, then we are able\n        // to make the repaired/unrepaired strategies mark their own sstables as compacting. Once the\n        // sstables are marked the compactions are re-enabled\n        return cfs.runWithCompactionsDisabled(() -> {\n            List<AbstractCompactionTask> tasks = new ArrayList<>();","sourceCodeStart":1211,"sourceCodeEnd":1247,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java#L1211-L1247","documentation":"validateForCompaction also requires all sstables in one compaction task to belong to the same compaction strategy holder index, which in disk-boundary mode corresponds to the same data directory. Compacting sstables from different directories in one task would break the per-directory split that keeps directory-level disk usage and lifecycle balanced, so UnsupportedOperationException is thrown.","triggerScenarios":"User-defined compaction (nodetool compact --sstable or getUserDefinedTasks) given sstable readers residing under different data_file_directories on a JBOD setup with disk boundaries in effect.","commonSituations":"JBOD/multi-directory deployments where ops scripts compact across the whole table by listing all sstables; after adding a disk, sstables span directory holders; tooling written before the disk-boundary split existed.","solutions":["Group requested sstables by their directory (compactionStrategyIndexFor / sstable directory) and compact each group separately","Compact per directory: nodetool compact on sstables sharing one data_file_directory","Disable split-disk-boundaries (data_file_directories to a single dir) if JBOD split is not required, then compact","Upgrade Cassandra - later versions improved user-defined compaction handling across disk boundaries"],"exampleFix":"// before\ngetUserDefinedTasks(allSstables, gcBefore); // spans directories\n// after\nMap<Integer, List<SSTableReader>> byIdx = allSstables.stream().collect(Collectors.groupingBy(csm::compactionStrategyIndexFor));\nbyIdx.values().forEach(g -> getUserDefinedTasks(g, gcBefore));","handlingStrategy":"validation","validationCode":"long dirs = sstables.stream().map(s -> s.descriptor.directory).distinct().count();\nif (dirs > 1) throw new IllegalArgumentException(\"compact per data_file_directory, not across directories\");","typeGuard":null,"tryCatchPattern":"try { getUserDefinedTasks(input, gcBefore); }\ncatch (UnsupportedOperationException e) {\n  Map<String, List<SSTableReader>> byDir = groupByDirectory(input);\n  byDir.values().forEach(g -> getUserDefinedTasks(g, gcBefore));\n}","preventionTips":["Group user-defined compaction inputs by sstable directory","On JBOD, script compaction per directory","Avoid adding/removing data directories around compaction windows","Keep sstable selection tooling aware of disk-boundary holders"],"tags":["compaction","jbod","unsupported-operation","directories"],"backgroundTag":"unsupported-operation","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}