Skip to content

CASSANDRA-21474: NoSpamLogger uses unbounded cache that could lead to memory exhaustion - #4993

Open
viktoriiakotovets wants to merge 2 commits into
apache:trunkfrom
viktoriiakotovets:CASSANDRA-21474/trunk
Open

CASSANDRA-21474: NoSpamLogger uses unbounded cache that could lead to memory exhaustion#4993
viktoriiakotovets wants to merge 2 commits into
apache:trunkfrom
viktoriiakotovets:CASSANDRA-21474/trunk

Conversation

@viktoriiakotovets

@viktoriiakotovets viktoriiakotovets commented Aug 4, 2026

Copy link
Copy Markdown

https://issues.apache.org/jira/browse/CASSANDRA-21474): NoSpamLogger uses unbounded cache that could lead to memory exhaustion

Terminating due to class java.lang.OutOfMemoryError/Java heap space
     java.lang.OutOfMemoryError: Java heap space
     	at org.cliffc.high_scale_lib.NonBlockingHashMap$CHM.resize(NonBlockingHashMap.java:863)
     	at org.cliffc.high_scale_lib.NonBlockingHashMap$CHM.access$200(NonBlockingHashMap.java:713)
     	at org.cliffc.high_scale_lib.NonBlockingHashMap.putIfMatch(NonBlockingHashMap.java:649)
     	at org.cliffc.high_scale_lib.NonBlockingHashMap.putIfMatch(NonBlockingHashMap.java:354)
     	at org.cliffc.high_scale_lib.NonBlockingHashMap.putIfAbsent(NonBlockingHashMap.java:321)
     	at org.apache.cassandra.utils.NoSpamLogger.getStatement(NoSpamLogger.java:275)
     	at org.apache.cassandra.utils.NoSpamLogger.getStatement(NoSpamLogger.java:266)
     	at org.apache.cassandra.utils.NoSpamLogger.log(NoSpamLogger.java:246)
     	at org.apache.cassandra.utils.NoSpamLogger.warn(NoSpamLogger.java:226)
     	at org.apache.cassandra.utils.NoSpamLogger.warn(NoSpamLogger.java:231) 

What is the issue

NoSpamLogger uses unbounded cache that could lead to memory exhaustion

What does this PR fix and why was it fixed

This PR replaces the previous NonBlockingHashMap based caching implementation in NoSpamLogger with Caffeine cache to prevent unbounded memory growth and improve cache management

kathirsvn and others added 2 commits August 3, 2026 22:54
…port from CNDB-17505)

### What is the issue
NoSpamLogger uses unbounded cache that could lead to memory exhaustion

### What does this PR fix and why was it fixed
This PR replaces the previous `NonBlockingHashMap` based caching
implementation in `NoSpamLogger` with Caffeine cache to prevent
unbounded memory growth and improve cache management
Comment on lines +274 to +295
.expireAfter(new Expiry<String, NoSpamLogStatement>()
{
@Override
public long expireAfterCreate(String key, NoSpamLogStatement value, long currentTime)
{
return value.expiry();
}

@Override
public long expireAfterUpdate(String key, NoSpamLogStatement value,
long currentTime, long currentDuration)
{
return value.expiry();
}

@Override
public long expireAfterRead(String key, NoSpamLogStatement value,
long currentTime, long currentDuration)
{
return currentDuration;
}
})

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.

fwiw, you might prefer

expireAfter(Expiry.writing((String key, NoSpamLogStatement value) -> Duration.ofNanos(value.expiry()))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

sure, but that might need upgrade to 3.2.2 first

@viktoriiakotovets
viktoriiakotovets marked this pull request as ready for review August 5, 2026 21:44
@viktoriiakotovets
viktoriiakotovets marked this pull request as draft August 7, 2026 15:01
@viktoriiakotovets
viktoriiakotovets marked this pull request as ready for review August 7, 2026 15:01
}
})
.ticker(TICKER)
.executor(ForkJoinPool.commonPool())

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.

@viktoriiakotovets I am not completely sure about this executor here. If you look what executors we use for Caffeine caches we never used this one. It would be appropriate if you did some basic research for the justification why we should use this executor specifically or change it to something more fitting.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

right, we don't want to use commonPool() (which can be limited in threads and easily starved).

@smiklosovic , which existing executor would you recommend ? or should we create a new one ?

@smiklosovic smiklosovic Aug 10, 2026

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.

If we want a dedicated executor for this then I would go with org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory().sequential("NoSpamLogger") otherwise I would go for ScheduledExecutors.optionalTasks or ImmediateExecutor.INSTANCE.

I lean towards a dedicated one because I do not like the fact that optionalTasks / INSTANCE would be used for executing stuff for a logger while that executor is used quite intensively for Cassandra itself and I do not want to mix it.

I would also most probably use executorFactory().withJmxInternal().sequential("NoSpamLogger"), withJMXInternal should give us the way how to query metrics of this executor via JMX so we have a visibility into what it is doing which is not a must but still a nice to have.

That being said, we would need to walk an extra mile here to be sure that we shutdown the executor upon shutdown of a node. AFAIK Caffeine is not shutting down the executor we hand it so we would need to be sure that we shut it down when not used anymore, likely in something like StorageService.drain() or similar.

btw isnt ForkJoinPool.commonPool() the default when we dont set it? So us setting it here is actually redundant. https://github.com/ben-manes/caffeine/blob/v3.1.8/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java#L337

public void testNoSpamLogStatementCacheBounded()
{
int maxStatementsPerLogger = 10;
System.setProperty("cassandra.nospam_logger.max_statements_per_logger", String.valueOf(maxStatementsPerLogger));

@smiklosovic smiklosovic Aug 10, 2026

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.

@viktoriiakotovets

could you rewrite this to more idiomatic Cassandra style of:

    try (WithProperties properties = new WithProperties().set(CassandraRelevantProperties.NOSPAM_LOGGER_MAX_STATEMENTS_PER_LOGGER,
                                                              String.valueOf(maxStatementsPerLogger)))
    {
        // test here, you do not need to reset manually, 
        // WithProperties will reset in AutoCloseable
    }
    finally
    {
        NoSpamLogger.clearWrappedLoggersForTest();
    }

@smiklosovic
smiklosovic self-requested a review August 10, 2026 11:39
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.

5 participants