apache/cassandra · warning

Received base materialized view mutation for key

Error message

Received base materialized view mutation for key {} that does not belong to this node. There is probably a range movement happening (move or decommission),but this node hasn't updated its ring metadata yet. Adding mutation to local batchlog to be replayed later.

What it means

When applying a base-table mutation that requires a materialized-view update, the coordinator computes which replica pair (base endpoint + view endpoint) should handle it. If no paired endpoint exists for this node's token, ring metadata is out of sync with an in-progress range movement (move/decommission/bootstrap). The mutation is written to the local batchlog for later replay so the view update is not lost.

Solutions

  1. Wait for ring convergence (nodetool ring/describering show consistent ownership) — batchlog replay handles the mutation automatically
  2. Ensure 'nodetool drain/ decommission' completed cleanly and all nodes agree on tokens
  3. Run repair afterwards to reconcile MV rows; the message itself recommends checking range movement state
  4. If persistent with no movement ongoing, check for gossip partitioning/firewall issues between nodes

Example fix

// before
$ nodetool decommission   # writes continuing elsewhere hit this warning

// after
$ nodetool netstats && nodetool describering -- keyspace   # confirm ring settled
$ nodetool repair -pr keyspace mv_table
Defensive patterns

Strategy: retry

Validate before calling

// before MV-heavy writes during topology changes, confirm ring stability
// (via JMX/metadata): pending ranges empty and all nodes agree on ownership
assert pendingRanges().isEmpty() : "range movement in progress; defer writes or expect batchlog replay";

Try / catch

try {
    executeMvWrite(mutation);
} catch (WriteFailureException | WriteTimeoutException e) {
    // mutation was added to local batchlog; verify replay, then repair
    scheduleRepair(keyspace, mvTable);
}

Prevention

When it happens

Trigger: applyAggregationOnBase likely called during MV write path when pairedEndpoint is absent and pendingReplicas is empty — i.e., the mutation key's token maps to no current+pending replica pair on this node, typically because token metadata hasn't refreshed after a ring change.

Common situations: Decommission, bootstrap, or range move in progress while writes continue; gossip/ring metadata propagation lag between nodes; client hitting a node that hasn't yet learned about new token ownership.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/service/StorageProxy.java:1160

                BatchlogCleanup cleanup = new BatchlogCleanup(mutations.size(),
                                                              () -> asyncRemoveFromBatchlog(localReplicaPlan, batchUUID, requestTime));

                // add a handler for each mutation - includes checking availability, but doesn't initiate any writes, yet
                for (Mutation mutation : mutations)
                {
                    String keyspaceName = mutation.getKeyspaceName();
                    Token tk = mutation.key().getToken();
                    Function<ClusterMetadata, Optional<Replica>> pairedEndpointSupplier = (cm) -> ViewUtils.getViewNaturalEndpoint(cm, keyspaceName, baseToken, tk);
                    Function<ClusterMetadata, VersionedEndpoints.ForToken>pendingReplicasSupplier = (cm) -> cm.pendingEndpointsFor(Keyspace.open(keyspaceName).getMetadata(), tk);

                    Optional<Replica> pairedEndpoint = pairedEndpointSupplier.apply(metadata);
                    VersionedEndpoints.ForToken pendingReplicas = pendingReplicasSupplier.apply(metadata);

                    // if there are no paired endpoints there are probably range movements going on, so we write to the local batchlog to replay later
                    if (!pairedEndpoint.isPresent())
                    {
                        if (pendingReplicas.isEmpty())
                            logger.warn("Received base materialized view mutation for key {} that does not belong " +
                                        "to this node. There is probably a range movement happening (move or decommission)," +
                                        "but this node hasn't updated its ring metadata yet. Adding mutation to " +
                                        "local batchlog to be replayed later.",
                                        mutation.key());
                        continue;
                    }

                    // When local node is the endpoint we can just apply the mutation locally,
                    // unless there are pending endpoints, in which case we want to do an ordinary
                    // write so the view mutation is sent to the pending endpoint
                    if (pairedEndpoint.get().isSelf() && StorageService.instance.isJoined()
                        && pendingReplicas.isEmpty())
                    {
                        try
                        {
                            mutation.apply(writeCommitLog);
                            nonLocalMutations.remove(mutation);
                            // won't trigger cleanup

View on GitHub (pinned to 88fd0f6a0e)