Skip to content

CASSANDRA-21423: CQLSSTableWriter produces empty bloom filter#4929

Open
liuisaac wants to merge 4 commits into
apache:trunkfrom
liuisaac:liuisaac/CASSANDRA-21423/trunk
Open

CASSANDRA-21423: CQLSSTableWriter produces empty bloom filter#4929
liuisaac wants to merge 4 commits into
apache:trunkfrom
liuisaac:liuisaac/CASSANDRA-21423/trunk

Conversation

@liuisaac

Copy link
Copy Markdown

Issue: CASSANDRA-21423

Purpose

CQLSSTableWriter does not know the key size on INSERT and thus produces near-empty Filter.db files. This saturates every flushed SSTable's Bloom Filter and recalculation never gets triggered, causing unnecessary disk reads for every partition key lookup to tables written via CQLSSTableWriter.

Context

CQLSSTableWriter allows callers to generate SSTable entries outside of a running Cassandra process.

CQLSSTableWriter initializes an AbstractSSTableSimpleWriter writer that gets instantiated to either SSTableSimpleWriter or an SSTableSimpleUnsortedWriter. Either writer implementation calls AbstractSSTableSimpleWriter.createWriter() which hardcodes a partition count of 0, as neither path knows the true key count.

Problem

Hardcoded 0 flows into FilterFactory.getFilter(0, fpChance), which allocates a filter of 0 elements + BITSET_EXCESS (20 bits). Resulting Filter.db is ~16 bytes in size. Effectively every bloom filter check will fall into a disk read.

Solution

Add SSTableReaderLoadingBuilder.rebuildFilter(), which pulls totalRows (upper bound of partition count) from Statistics.db, then walk the on-disk primary index to repopulate the filter with every key, and overwrites Filter.db.

AbstractSSTableSimpleWriter implementations call rebuildFilter() after each SSTable is produced before notifying sstableProducedListener. Because finish(openResult) may already have opened an SSTableReader holding a shared in-memory copy of the original (broken) filter, copied in via getFilterCopy() before this method runs, independent of what gets written to disk afterward, the fix also swaps the corrected filter into any already-opened readers via cloneAndReplace, releasing the original.

Skips entirely when bloom filtering is disabled for the table. On rebuild failure, failed Filter.db is deleted so a node will safely build on load instead.

liuisaac added 4 commits July 12, 2026 18:05
Rebuilds the Bloom filter for sstables produced by CQLSSTableWriter after
the writer completes, since the writer sizes its filter from a hardcoded
partition count of 0 (the real count is unknown up front), producing a
negligibly small, saturated filter regardless of actual data written.

Adds SSTableReaderLoadingBuilder#rebuildFilter, which reuses the existing
per-format key-reading infrastructure to size the filter from the real
partition count in Statistics.db and repopulate it from the on-disk index.
AbstractSSTableSimpleWriter invokes this after each produced sstable and
swaps the corrected filter into any already-opened reader via
cloneAndReplace, so callers using openSSTableOnProduced also get the
corrected filter rather than the writer's original in-memory copy.

Patch by Isaac Liu; reviewed by TBD for CASSANDRA-21423
Guard rebuildBloomFilter against applying one sstable's filter to a
different sstable's reader. Safe today, finish() only ever
returns a singleton here, but would silently corrupt filters if reused
elsewhere. Fails loudly via Preconditions.checkState instead.

Fix rebuildFilter's catch block to delete the broken Filter.db on any
failure, not just failures during save(). Previously a failure during
the index walk left the original broken file untouched.

Add a test forcing multiple sstable rotations, verifying every
produced Filter.db is rebuilt, not just the first.

Patch by Isaac Liu; reviewed by TBD for CASSANDRA-21423
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant