Files
scylladb/docs/dev/secondary_index.md
Karol Nowacki 4dc28dfa52 index: test: secondary index target option serialization test
Target option serialization must remain stable for backward compatibility.
The index is restored from this property on startup, so unintentional
changes to the serialization schema can break indexes after upgrade.
2026-03-30 16:46:47 +02:00

3.0 KiB

Secondary indexes in Scylla

Secondary indexes can currently be either global (default) or local. Global indexes use the indexed column as its partition key, while local indexes share their partition key with their base table, which ensures local lookup.

The distinction is stored in index target, which is a string kept in index's options map under the key "target". Example of a global and local indexes on the same table and column:

SELECT * FROM system\_schema.indexes;
 keyspace\_name | table\_name | index\_name | kind       | options
----------------+-------------+-------------+------------+----------------------
         demodb |           t |  local_t_v1 | COMPOSITES | {'target': '{"pk":["p"],"ck":["v1"]}'}
         demodb |           t |    t_v1_idx | COMPOSITES | {'target': 'v1'}

Default naming

By default, index names are generated from table name, column name and "_idx" postfix. Because index names, like table names, must be word characters (letters, digits, or underscore), any characters in the column name which aren't word characters are dropped before constructing the index name.

If the name is taken (e.g. because somebody already created a named index with the exact same name), "_X" is appended, where X is the smallest number that ensures name uniqueness.

Default name for an index created on table t and column v1 is thus t_v1_idx, but it can also become t_v1_idx_1 if the first one was already taken. Both global and local indexes share the same default naming conventions.

When in doubt, DESCRIBE index_name or SELECT * FROM system_schema.indexes commands can be leveraged to see more details on index targets and type.

Global index

Global index's target is usually just the indexed column name, unless the index has a specific type. All supported types are:

  • regular index: v
  • full collection index (for frozen collection): FULL(v)
  • index on map keys: KEYS(v)
  • index on map, set or list values: VALUES(v)
  • index on map entries: ENTRIES(v)

Their serialization uses lowercase type names as prefixes, except for full which is serialized as just the column name (without any prefix): "v", "keys(v)", "values(v)", "entries(v)" are valid targets; a frozen full collection index on column v is stored simply as "v" (same as a regular index).

If the column name contains characters that could be confused with the above formats (e.g., a name containing parentheses or braces), it is escaped using the CQL quoted-identifier syntax (column_identifier::to_cql_string()), which wraps the name in double quotes and doubles any embedded double-quote characters. For example, a column named hEllo is stored as "hEllo", and a column named keys(m) is stored as "keys(m)".

Local index

Local index's target consists of explicit partition key followed by indexed column definition. Currently the partition key must match the partition key of base table.

Their serialization is a string representing primary key in JSON. Examples: { "pk": ["p1", "p2", "p3"], "ck": ["v"] }

{ "pk": ["p"], "ck": ["v"] }