Skip to content

CASSANDRA-21671 Fix compressed scan read-ahead buffer Block leak and chunk-cache pollution by scans - #5168

Open
maedhroz wants to merge 5 commits into
apache:cassandra-5.0from
maedhroz:CASSANDRA-21671-5.0
Open

maedhroz wants to merge 5 commits into
apache:cassandra-5.0from
maedhroz:CASSANDRA-21671-5.0

Conversation

@maedhroz

@maedhroz maedhroz commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

This patch is essentially a 5.0 version of #5152, with a few, mostly superficial simplifications. The core is the same:

1.) We're no longer using thread-locals, which allows us to avoid an entire class of bookkeeping errors.
2.) Scans during compaction bypass the chunk cache to avoid needlessly evicting hot data.

rustyrazorblade and others added 5 commits September 15, 2026 17:41
…ution by scans

A shared thread-local read-ahead buffer allowed concurrent scans of one SSTable to collide and leak. Each compressed scan reader
now owns a per-scan buffer that it frees on close. Scans also polluted the chunk cache with one-shot chunks that evicted hot data.
The new ReadPattern concept allows us to route single-partition and partition-range reads through the cache while scans bypass it
and read ahead through their own buffer.

patch by Jon Haddad; reviewed by Caleb Rackliffe and Sam Lightfoot for CASSANDRA-21671

Co-authored-by: Jon Haddad <jon@jonhaddad.com>
Co-authored-by: Caleb Rackliffe <calebrackliffe@gmail.com>

Assisted-by: Claude Code:claude-opus-4-8
@maedhroz

Copy link
Copy Markdown
Contributor Author

}

public ThreadLocalReadAheadBuffer(ChannelProxy channel, int bufferSize, BufferType bufferType)
public ReadAheadBuffer(ChannelProxy channel, Supplier<ByteBuffer> bufferSupplier)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ctor not needed as the Direct I/O subclass is introduced in v6.

/** Single-partition reads use the chunk cache but do not read ahead. */
ROW_READ(true, false),

/** Range queries walking partitions in order use the chunk cache and can read ahead only if the cache is disabled. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra space after 'order'

/** Single-partition reads use the chunk cache but do not read ahead. */
ROW_READ(true, false),

/** Range queries walking partitions in order use the chunk cache and can read ahead only if the cache is disabled. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT

Suggested change
/** Range queries walking partitions in order use the chunk cache and can read ahead only if the cache is disabled. */
/** Range queries walking partitions in order use the chunk cache and can read ahead only if the cache is disabled. */

private final ReadAheadBuffer readAheadBuffer;

private ScanCompressedReader(ChannelProxy channel, CompressionMetadata metadata, int readAheadBufferSize)
private ScanCompressedReader(ChannelProxy channel, ThreadLocalByteBufferHolder bufferHolder,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I understand the signature change here, doesn't seem we gain anything. I would keep as it was before. It is functionally strictly the same.

ByteBuffer buffer = null;
int index = -1;
}
protected final ChannelProxy channel;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be private ?

Suggested change
protected final ChannelProxy channel;
private final ChannelProxy channel;

protected final ChannelProxy channel;

private final ChannelProxy channel;
private final Supplier<ByteBuffer> bufferSupplier;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be BufferType, we don't have any use case for supplier here

Suggested change
private final Supplier<ByteBuffer> bufferSupplier;
private final BufferType bufferType;

}

public ThreadLocalReadAheadBuffer(ChannelProxy channel, int bufferSize, BufferType bufferType)
public ReadAheadBuffer(ChannelProxy channel, Supplier<ByteBuffer> bufferSupplier)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this ctor is not needed

Comment on lines +84 to +85
doReads(f, metadata1, length, false);
int raReads = reads.getAndSet(0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean this should be true here instead? This is a RA read?

Suggested change
doReads(f, metadata1, length, false);
int raReads = reads.getAndSet(0);
doReads(f, metadata1, length, true);
int raReads = reads.getAndSet(0);

Comment on lines +86 to 88

doReads(f, metadata2, length, true);
int scanReads = reads.getAndSet(0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly shouldn't this be false here?

{
return this;
// Bypass the cache for patterns that read data once, so they don't evict hot data.
return pattern.usesCache() ? this : source.instantiateRebufferer(pattern);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when read ahead is disabled (i.e. compressed_read_ahead_buffer_size = 0) should we still use the cache?


block.index = -1;
if (block.buffer == null)
if (buffer == null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deallocate is always true , even in tests. Should we just remove this param?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at other places where we call org.apache.cassandra.io.sstable.format.SSTableReader#openDataReader() -> createReader() it looks like we do it in ForwardingSSTableReader, StorageAttachedIndexBuilder, SASIIndexBuilder, SortedTableScrubber, SortedTableVerifier. Might be worth thinking about those too.

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.

4 participants