如何提升Spring Data Cassandra Reactive中ReactiveCrudRepository.save()的插入性能?
首先,你观察到的ReactiveCrudRepository.save()比自定义ReactiveCqlOperations.execute()慢近一倍的情况,主要不是Spring代理的开销导致的,而是通用save方法包含了更多额外逻辑,这些逻辑在你自定义的CQL操作里被省略了。下面我来拆解原因并给出优化方案:
为什么save()更慢?
- 动态CQL生成与实体映射开销:
save方法需要动态解析你的SnapshotRecord实体上的注解(@Table、@PrimaryKeyColumn等),生成对应的INSERT语句,还要完成实体属性到CQL参数的映射。而你自定义的saveViaCql直接用硬编码的CQL,省去了这些动态处理的步骤。 - 插入/更新判断逻辑:
save方法是通用的,它会尝试判断当前记录是新插入还是更新(即使你这里都是新数据,框架依然会执行一些轻量的检查逻辑),而自定义的INSERT语句直接执行插入,没有这部分开销。 - PreparedStatement缓存差异:自定义的固定CQL语句更容易被ReactiveCqlOperations缓存复用,而动态生成的语句可能缓存命中率较低,增加了重复解析Statement的开销。
优化方案
1. 启用PreparedStatement缓存
Spring Data Cassandra默认可能没有开启足够的Statement缓存,你可以在配置中增加:
spring.data.cassandra.prepared-statements.cache-size=1000
这样动态生成的INSERT语句会被缓存,减少重复解析的开销,提升后续操作的速度。
2. 改用ReactiveCassandraTemplate.insert()
通用的save方法兼顾了插入和更新场景,而ReactiveCassandraTemplate.insert()专门针对插入操作,省去了更新判断的逻辑,性能更接近你自定义的CQL操作。替换后的代码类似:
.flatMap(record -> reactiveCassandraTemplate.insert(record), 512, 2048)
3. 在Repository中定义自定义插入方法
通过@Query注解在Repository中定义固定的插入方法,让Spring Data生成可缓存的PreparedStatement,避免动态Query生成的开销:
public interface SnapshotRepository extends ReactiveCrudRepository<SnapshotRecord, Long> { @Query("INSERT INTO snapshot (id, market,slot,value) VALUES (:id, :market,:slot,:value) USING TIMESTAMP :timestamp;") Mono<Boolean> insert(@Param("id") long id, @Param("market") short market, @Param("slot") Instant slot, @Param("value") double value, @Param("timestamp") long timestamp); }
调用时直接传参,性能和你自定义的saveViaCql几乎一致。
4. 批量插入优化
如果业务允许,将多个记录批量插入可以大幅减少网络往返次数。比如先将Flux缓存成批量,再执行插入:
data.buffer(100) // 每100条记录为一批 .flatMap(batch -> reactiveCassandraTemplate.insert(batch), 512) ...
这会比单条并发插入的吞吐量更高。
5. 简化实体映射开销(可选)
虽然影响较小,但可以尝试去掉@Value、@Builder等Lombok注解,换成普通的POJO带getter方法,减少实体实例化和映射时的额外开销,测试是否能带来小幅性能提升。
总结
Spring代理的开销在这个场景下几乎可以忽略,主要是通用save方法的额外逻辑导致性能差异。通过上面的优化手段,你可以让Spring Data的插入性能接近自定义CQL的水平。
内容的提问来源于stack exchange,提问作者Igor Piddubnyi

