-
Notifications
You must be signed in to change notification settings - Fork 310
Description
Summary
Enabling observation for cassandra queries via ObservableCqlSessionFactoryBean
breaks prepared statements caching in Datastax driver.
For each DML statement execution, a PREPARE statement is also executed, negatively affecting performance.
Affected versions
To create this issue, I used spring-data-cassandra:4.5.3
(Spring Boot 3.5.5), but the issue was present on earlier versions too.
I also encountered it in 4.2.5 (Boot 3.2.5), but perhaps it was introduced even earlier.
Example Project
This sample project might be helpful to reproduce the issue. It has some instructions in its readme file.
Detailed description of the problem
By default, without observation enabled, prepared statements are cached by the Datastax driver in com.datastax.oss.driver.internal.core.cql.CqlPrepareAsyncProcessor.process
.
In CqlPrepareAsyncProcessor.java:147
, a cache lookup is performed, which uses equals
and hashCode
of a SimpleStatement
(wrapped in DefaultPrepareRequest
) to find a cached PreparedStatement
instance.
The official doc suggests to enable Micrometer instrumentation for a CqlSession by registering an
org.springframework.data.cassandra.observability.ObservableCqlSessionFactoryBean
bean.
This leads to the CqlSession being wrapped in a org.springframework.data.cassandra.observability.CqlSessionObservationInterceptor
proxy.
When any of the SyncCqlSession#prepare
methods are called, they are intercepted by the CqlSessionObservationInterceptor
.
It delegates the prepare
calls to the SyncCqlSession#prepare(com.datastax.oss.driver.api.core.cql.SimpleStatement)
method.
Before calling the delegate, it wraps SimpleStatement
argument with a org.springframework.data.cassandra.observability.ObservationStatement
proxy (CqlSessionObservationInterceptor.java:94
).
When an ObservationStatement
object reaches the cache lookup, it's hashCode
and equals
methods are called, which
are delegated to the JdkDynamicAopProxy
implementations. The hashCode
implementation doesn't have a problem. For two
delegates with the same hashCode
output, their proxies will also return equal hash codes.
The equals
method, however, uses org.springframework.aop.framework.AopProxyUtils.equalsInProxy
, which checks for
advisors equality, which leads to the call of ObservationStatement.equals
, which, in turn, uses a default equals
implementation.
Because of the advisors inequality, proxy obects are deemed not equal, prepared statements are not returned from the cache and are executed again for each DML query.
Possible fix
Adding an equals
method (and hashCode
for consistency) to the ObservationStatement
class seems to fix the problem without breaking any tests.