apache/cassandra · error · InvalidRequestException

Range deletions must specify a single partition key in the…

Error message

Range deletions must specify a single partition key in the underlying table 

What it means

A range tombstone whose start and end bounds are both full partition keys but not identical covers more than one underlying partition; the virtual table only supports deletions mapping to a single partition key per tombstone, so it throws InvalidRequestException.

Solutions

  1. Issue one exact-key DELETE per row/partition instead of a spanning range.
  2. Restrict the range so start and end map to the same partition key (equal on all pk components).
  3. Perform the deletion on the underlying table directly on each owning node.
  4. If bulk removal is intended, use a partition-level or truncate operation (apply() supports partition deletion via truncate).

Example fix

// before
DELETE FROM vt WHERE ck >= 1 AND ck <= 5;  // spans multiple partitions
// after
DELETE FROM vt WHERE ck = 1; DELETE FROM vt WHERE ck = 2; // ...one per key
Defensive patterns

Strategy: validation

Validate before calling

// Range deletes must collapse to one partition key
if (!startBound.equals(endBound))
    throw new IllegalArgumentException("Range spans multiple partitions; use per-key deletes");

Try / catch

try { session.execute(delete); }
catch (InvalidRequestException e) {
    if (e.getMessage().contains("single partition key"))
        // split range into individual key deletes
}

Prevention

When it happens

Trigger: Range tombstone where start.size() == end.size() == pkCount but some component i has start.get(i) != end.get(i), i.e. the deletion spans multiple underlying partition keys (e.g. clustering BETWEEN a AND b with distinct mapped keys).

Common situations: DELETE with a BETWEEN/inequality range over clustering columns that map to different underlying partitions; batch statements mixing such deletes.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/a80eac0790a23929. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/db/virtual/RemoteToLocalVirtualTable.java:535

        PartitionUpdate.Builder builder = null;
        ArrayDeque<Promise<Void>> results = new ArrayDeque<>();

        if (deletionInfo.hasRanges())
        {
            Iterator<RangeTombstone> iterator = deletionInfo.rangeIterator(false);
            while (iterator.hasNext())
            {
                RangeTombstone rt = iterator.next();
                ClusteringBound start = rt.deletedSlice().start();
                ClusteringBound end = rt.deletedSlice().end();
                if (start.size() < pkCount || end.size() < pkCount)
                    throw new InvalidRequestException("Range deletions must specify a complete partition key in the underlying table " + metadata);

                for (int i = 0 ; i < pkCount ; ++i)
                {
                    if (0 != start.accessor().compare(start.get(i), end.get(i), end.accessor()))
                        throw new InvalidRequestException("Range deletions must specify a single partition key in the underlying table " + metadata);
                }

                DecoratedKey key = remoteClusteringToLocalPartitionKey(local, start, pkCount, pkBuffer);
                builder = maybeRolloverAndWait(key, builder, results, endpoint);
                if (start.size() == pkCount && end.size() == pkCount)
                {
                    builder.addPartitionDeletion(rt.deletionTime());
                }
                else
                {
                    start = ClusteringBound.create(start.kind(), Clustering.make(remoteClusteringToLocalClustering(start.clustering(), pkCount, ckBuffer)));
                    end = ClusteringBound.create(end.kind(), Clustering.make(remoteClusteringToLocalClustering(end.clustering(), pkCount, ckBuffer)));
                    builder.add(new RangeTombstone(Slice.make(start, end), rt.deletionTime()));
                }
            }
        }

        if (!update.staticRow().isEmpty())

View on GitHub (pinned to 88fd0f6a0e)