apache/cassandra · error · RuntimeException
Can't move and open a file that is already in use in the…
Error message
Can't move and open a file that is already in use in the table %s -> %s
What it means
moveAndOpenSSTable() checks the live SSTable set of the column family store and refuses to move/open a descriptor that is already tracked as live (either the old or the new descriptor equals a live reader's descriptor). This protects against double-ownership of an SSTable file, which would corrupt state if the same data file were moved under an active reader.
Solutions
- Ensure the SSTable is not already loaded: check nodetool sstableinfo / table metrics before moving
- Run the move/import operation only once per descriptor; make jobs idempotent by checking existence first
- Stop the node or use offline tools only against a stopped instance to avoid racing live readers
- Skip descriptors already present in the live set in your tooling before calling moveAndOpenSSTable
Example fix
// before: unconditional move
SSTableReader.moveAndOpenSSTable(cfs, oldDesc, newDesc, comps, false);
// after: skip if already live
boolean live = cfs.getLiveSSTables().stream()
.anyMatch(r -> r.descriptor.equals(oldDesc) || r.descriptor.equals(newDesc));
if (!live) SSTableReader.moveAndOpenSSTable(cfs, oldDesc, newDesc, comps, false); Defensive patterns
Strategy: validation
Validate before calling
// skip if already loaded
boolean live = cfs.getLiveSSTables().stream()
.anyMatch(r -> r.descriptor.equals(oldDesc) || r.descriptor.equals(newDesc));
if (live) { skip(); } Try / catch
try { SSTableReader.moveAndOpenSSTable(cfs, oldDesc, newDesc, comps, false); }
catch (RuntimeException e) {
if (e.getMessage().startsWith("Can't move and open a file that is already in use")) {
logger.warn("Skipping already-live sstable {}", oldDesc); // idempotent skip
} else throw e;
} Prevention
- Make import/move jobs idempotent by checking live descriptors first
- Never run offline SSTable tools against a live node
- Serialize move/import operations per table to avoid races
- Track processed descriptors in job state to avoid re-processing
When it happens
Trigger: Calling moveAndOpenSSTable with an oldDescriptor or newDescriptor that matches a descriptor in cfs.getLiveSSTables() — e.g. re-invoking an import/relocation job for an SSTable that was already opened, or racing concurrent move operations on the same file.
Common situations: Re-running sstable import/relocation scripts twice; concurrent bulk-load operations targeting the same table; offline tools run against a live (not stopped) node.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- 0x0000
- 3
- A range supplied to SSTableCursorReader ends before it…
- Aborting SSTable index flush for
- Allocated token already assigned to node . Is another node…
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/40df47a3e5a4d272.
Report an issue: GitHub.
Appendix: source
Thrown at src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java:1879
/**
* Moves the sstable in oldDescriptor to a new place (with generation etc) in newDescriptor.
* <p>
* All components given will be moved/renamed
*/
public static SSTableReader moveAndOpenSSTable(ColumnFamilyStore cfs, Descriptor oldDescriptor, Descriptor newDescriptor, Set<Component> components, boolean copyData)
{
if (!oldDescriptor.isCompatible())
throw new RuntimeException(String.format("Can't open incompatible SSTable! Current version %s, found file: %s",
oldDescriptor.getFormat().getLatestVersion(),
oldDescriptor));
boolean isLive = cfs.getLiveSSTables().stream().anyMatch(r -> r.descriptor.equals(newDescriptor)
|| r.descriptor.equals(oldDescriptor));
if (isLive)
{
String message = String.format("Can't move and open a file that is already in use in the table %s -> %s", oldDescriptor, newDescriptor);
logger.error(message);
throw new RuntimeException(message);
}
if (newDescriptor.fileFor(Components.DATA).exists())
{
String msg = String.format("File %s already exists, can't move the file there", newDescriptor.fileFor(Components.DATA));
logger.error(msg);
throw new RuntimeException(msg);
}
if (copyData)
{
try
{
logger.info("Hardlinking new SSTable {} to {}", oldDescriptor, newDescriptor);
hardlink(oldDescriptor, newDescriptor, components);
}
catch (FSWriteError ex)
{
logger.warn("Unable to hardlink new SSTable {} to {}, falling back to copying", oldDescriptor, newDescriptor, ex);View on GitHub (pinned to 88fd0f6a0e)