apache/cassandra · error · InvalidRequestException

Cannot create more than one storage-attached index on the sa

Error message

Cannot create more than one storage-attached index on the same column: 

What it means

SAI (Storage-Attached Index) rejects CREATE INDEX when another SAI index already exists on the same target column. During index option validation the metadata's existing indexes are filtered to SAI indexes, their targets are parsed, and if more than one matches the requested target the request is invalid. This prevents duplicate indexes that would waste space and confuse query planning.

Source

Thrown at src/java/org/apache/cassandra/index/sai/StorageAttachedIndex.java:274

        }

        Pair<ColumnMetadata, IndexTarget.Type> target = TargetParser.parse(metadata, targetColumn);

        if (target == null)
        {
            throw new InvalidRequestException("Failed to retrieve target column for: " + targetColumn);
        }

        // In order to support different index targets on non-frozen map, ie. KEYS, VALUE, ENTRIES, we need to put index
        // name as part of index file name instead of column name. We only need to check that the target is different
        // between indexes. This will only allow indexes in the same column with a different IndexTarget.Type.
        //
        // Note that: "metadata.indexes" already includes current index
        if (metadata.indexes.stream().filter(index -> index.getIndexClassName().equals(StorageAttachedIndex.class.getName()))
                            .map(index -> TargetParser.parse(metadata, index.options.get(IndexTarget.TARGET_OPTION_NAME)))
                            .filter(Objects::nonNull).filter(t -> t.equals(target)).count() > 1)
        {
            throw new InvalidRequestException("Cannot create more than one storage-attached index on the same column: " + target.left);
        }

        Map<String, String> analysisOptions = AbstractAnalyzer.getAnalyzerOptions(options);
        if (target.left.isPrimaryKeyColumn() && !analysisOptions.isEmpty())
        {
            throw new InvalidRequestException(ANALYSIS_ON_KEY_COLUMNS_MESSAGE + new CqlBuilder().append(analysisOptions));
        }

        IndexTermType indexTermType = IndexTermType.create(target.left, metadata.partitionKeyColumns(), target.right);
        AbstractAnalyzer.fromOptions(indexTermType, analysisOptions);
        IndexWriterConfig config = IndexWriterConfig.fromOptions(null, indexTermType, options);

        // If we are indexing map entries we need to validate the subtypes
        if (indexTermType.isComposite())
        {
            for (IndexTermType subType : indexTermType.subTypes())
            {
                if (!SUPPORTED_TYPES.contains(subType.asCQL3Type()) && !subType.isFrozen())

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Use CREATE CUSTOM INDEX IF NOT EXISTS to skip creation when the index already exists.
  2. Query system_schema.indexes to check for an existing SAI index on the column before creating one.
  3. If different options are needed, DROP the existing index first, then create the new one.
  4. Index a different column or use a different index implementation if you genuinely need a second index.

Example fix

// before
CREATE CUSTOM INDEX ON ks.tbl (col) USING 'StorageAttachedIndex';
// after
CREATE CUSTOM INDEX IF NOT EXISTS ON ks.tbl (col) USING 'StorageAttachedIndex';
Defensive patterns

Strategy: validation

Validate before calling

SELECT column_name FROM system_schema.indexes WHERE keyspace_name='ks' AND table_name='tbl' AND options CONTAINS 'StorageAttachedIndex'; // then check column in app code before CREATE INDEX

Try / catch

try { session.execute(createIndexCql); } catch (InvalidRequestException e) { if (e.getMessage().startsWith("Cannot create more than one storage-attached index")) log.info("index already exists, ignoring"); else throw e; }

Prevention

When it happens

Trigger: Calling CREATE CUSTOM INDEX ... USING 'StorageAttachedIndex' on a column that already has an SAI index (the new index is already present in metadata.indexes, so the count of matching parsed targets exceeds 1).

Common situations: Re-running a schema migration/idempotent CREATE INDEX script without IF NOT EXISTS; creating a second index on the same column hoping for different analyzer options; copying index DDL across keyspaces.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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