{"record":{"id":"7aa5713fa851c36a","repo":"apache/cassandra","slug":"term-s-belongs-to-more-than-d-keys-in-s-mod","errorCode":null,"errorMessage":"Term - '%s' belongs to more than %d keys in %s mode, which is not allowed.","messagePattern":"Term - '(.+?)' belongs to more than (.+?) keys in (.+?) mode, which is not allowed\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndexBuilder.java","lineNumber":621,"sourceCode":"\n        private final List<TokenTreeBuilder> containers = new ArrayList<>();\n        private TokenTreeBuilder combinedIndex;\n\n        public MutableDataBlock(AbstractType<?> comparator, Mode mode)\n        {\n            this.comparator = comparator;\n            this.mode = mode;\n            this.combinedIndex = initCombinedIndex();\n        }\n\n        protected void addInternal(InMemoryDataTerm term) throws IOException\n        {\n            TokenTreeBuilder keys = term.keys;\n\n            if (mode == Mode.SPARSE)\n            {\n                if (keys.getTokenCount() > MAX_KEYS_SPARSE)\n                    throw new IOException(String.format(\"Term - '%s' belongs to more than %d keys in %s mode, which is not allowed.\",\n                                                        comparator.getString(term.term.getBytes()), MAX_KEYS_SPARSE, mode.name()));\n\n                writeTerm(term, keys);\n            }\n            else\n            {\n                writeTerm(term, offset);\n\n                offset += keys.serializedSize();\n                containers.add(keys);\n            }\n\n            if (mode == Mode.SPARSE)\n                combinedIndex.add(keys);\n        }\n\n        protected int sizeAfter(InMemoryDataTerm element)\n        {","sourceCodeStart":603,"sourceCodeEnd":639,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/index/sasi/disk/OnDiskIndexBuilder.java#L603-L639","documentation":"SASI's OnDiskIndexBuilder refuses to build an index segment in SPARSE mode when a single term maps to more than MAX_KEYS_SPARSE keys. SPARSE mode uses a compact token-tree encoding that assumes low cardinality per term, so a term exceeding the key cap would produce an oversized, inefficient structure; the builder throws IOException instead of silently degrading the index format.","triggerScenarios":"Calling OnDiskIndexBuilder.add() many times with the same term value (e.g. a boolean-like or low-cardinality column) in SPARSE mode, then calling build() so the term's TokenTreeBuilder exceeds MAX_KEYS_SPARSE keys when the segment is serialized.","commonSituations":"Indexing a low-cardinality column (status flags, booleans) where thousands of rows share one term; also happens when MAX_KEYS_SPARSE was reduced via config in custom builds and a previously-indexable column now exceeds the cap.","solutions":["Remove the low-cardinality column from SASI indexing or index it with a different kind/mode so it is not built in SPARSE mode","Reduce the number of rows sharing that term per index segment (smaller memtable/sstable, more frequent flushes)","Check MAX_KEYS_SPARSE in OnDiskIndexBuilder and confirm no local modification lowered it below your data's cardinality","Rebuild the affected SASI index after fixing the data or configuration"],"exampleFix":"// before\nCREATE CUSTOM INDEX ON ks.tbl (status) USING 'org.apache.cassandra.index.sasi.SASIIndex';\n// data has 10M rows with status='ACTIVE' -> SPARSE cap exceeded\n// after\n// drop and re-create without indexing the low-cardinality column, or use a regular (non-SASI) index:\nCREATE INDEX ON ks.tbl (status);","handlingStrategy":"validation","validationCode":"// Before creating a SASI index on a column, estimate per-term cardinality\ncqlsh> SELECT COUNT(*) FROM ks.tbl WHERE status = 'ACTIVE';\n// If counts per value approach MAX_KEYS_SPARSE, do not index in SPARSE mode.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only SASI-index columns with reasonably distributed cardinality; use plain secondary indexes for low-cardinality flags","Test index builds on a data sample matching production cardinality before rollout","After version/config changes, re-verify MAX_KEYS_SPARSE has not changed"],"tags":["sasi","index-builder","sparse-mode","cardinality"],"backgroundTag":"value-out-of-range","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}