apache/cassandra · warning

Duplicate row detected in

Error message

Duplicate row detected in %s.%s: %s %s

What it means

The scrubber's merging row iterator (next) detects two rows with identical clustering in the same partition while iterating static/data rows. It merges them (Rows.merge) and logs a one-time warning per partition identifying keyspace, table, partition key, and clustering. This indicates the sstable contains duplicate row entries, which the write path should have prevented.

Solutions

  1. No data loss: rows are merged automatically; confirm the merge produced the intended reconciliation of timestamps/tombstones
  2. Run nodetool repair to reconcile this node's view with replicas
  3. Run scrub/compact to rewrite the sstable without duplicates
  4. If reproducible on new data, check Cassandra version for known duplicate-row bugs and upgrade
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Scrubbing an sstable where the same clustering key appears twice within one partition during UnfilteredRowIterator iteration; the iterator merges the rows and warns once (logged flag).

Common situations: Historical bugs that appended duplicate rows on compaction/repair; corruption duplicating a row block; sstables produced by faulty tooling or repairs from a defective version.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/io/sstable/format/SortedTableScrubber.java:523

            if (next.isRow())
            {
                boolean logged = false;
                while (wrapped.hasNext())
                {
                    Unfiltered peek = wrapped.next();
                    if (!peek.isRow() || !next.clustering().equals(peek.clustering()))
                    {
                        nextToOffer = peek; // Offer peek in next call
                        return computeFinalRow((Row) next);
                    }

                    // Duplicate row, merge it.
                    next = Rows.merge((Row) next, (Row) peek);

                    if (!logged)
                    {
                        String partitionKey = metadata().partitionKeyType.getString(partitionKey().getKey());
                        output.warn("Duplicate row detected in %s.%s: %s %s", metadata().keyspace, metadata().name, partitionKey, next.clustering().toString(metadata()));
                        logged = true;
                    }
                }
            }

            nextToOffer = null;
            return computeFinalRow((Row) next);
         }

         private Row computeFinalRow(Row next)
         {
             // If the row has overflowed let rows skip them unless we need to keep them for the overflow policy
             if (hasOverflowedLocalExpirationTimeRow(next) && !reinsertOverflowedTTLRows)
                 return null;
             else if (reinsertOverflowedTTLRows)
                 return rebuildTimestamptsForOverflowedRows(next);
             else
                 return next;

View on GitHub (pinned to 88fd0f6a0e)