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

  1. Ensure the SSTable is not already loaded: check nodetool sstableinfo / table metrics before moving
  2. Run the move/import operation only once per descriptor; make jobs idempotent by checking existence first
  3. Stop the node or use offline tools only against a stopped instance to avoid racing live readers
  4. 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

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


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)