Ignite 2.9.x子缓存父键索引配置与查询优化技术问询
解决方案:给Ignite子缓存的asset字段建索引并优化查询
没问题,完全可以给你的Production缓存的asset字段创建索引,这正是解决你当前扫描时间线性增长问题的核心方案。下面我会一步步讲清楚配置、查询修改以及亲和键的最佳实践:
1. 能否创建索引?当然可以!
你当前用的是本地扫描+过滤的方式,本质上是全缓存遍历,数据量越大速度越慢。给asset字段建索引后,Ignite就能直接定位到指定父键对应的所有子记录,避免全表扫描,性能会有质的提升。
2. 索引配置方式
Ignite支持两种配置索引的方式,你可以根据自己的部署习惯选择:
方式一:XML配置(适合静态缓存定义)
在你的Ignite配置文件中,给productions缓存添加queryEntities,指定asset字段为索引字段:
<bean class="org.apache.ignite.configuration.IgniteConfiguration"> <property name="cacheConfiguration"> <list> <bean class="org.apache.ignite.configuration.CacheConfiguration"> <property name="name" value="productions"/> <!-- 保留你原有的亲和键配置 --> <property name="queryEntities"> <list> <bean class="org.apache.ignite.cache.QueryEntity"> <!-- 子缓存的键类型 --> <property name="keyType" value="java.lang.Integer"/> <!-- 值类型对应你的Production实体类或BinaryObject --> <property name="valueType" value="com.yourcompany.Production"/> <property name="fields"> <map> <entry key="asset" value="java.lang.Integer"/> <entry key="timestamp" value="java.time.OffsetDateTime"/> <entry key="productionMwh" value="java.lang.Double"/> </map> </property> <!-- 给asset字段添加索引 --> <property name="indexes"> <list> <bean class="org.apache.ignite.cache.QueryIndex"> <constructor-arg value="asset"/> </bean> </list> </property> </bean> </list> </property> </bean> </list> </property> </bean>
方式二:代码配置(适合动态创建缓存)
如果是在代码中创建缓存,可以通过CacheConfiguration直接配置索引:
CacheConfiguration<Integer, BinaryObject> prodCacheCfg = new CacheConfiguration<>("productions"); // 保留你原有的亲和键关联逻辑 prodCacheCfg.setAffinityKeyMapper(new CacheAffinityKeyMapper() { @Override public Object affinityKey(Object key) { // 这里如果子缓存键是AffinityKey,返回父键;如果是普通键,可从value的asset字段取值 return key; } }); // 配置QueryEntity和索引 QueryEntity queryEntity = new QueryEntity(); queryEntity.setKeyType(Integer.class.getName()); queryEntity.setValueType(BinaryObject.class.getName()); // 定义缓存字段映射 Map<String, String> fields = new HashMap<>(); fields.put("asset", Integer.class.getName()); fields.put("timestamp", OffsetDateTime.class.getName()); fields.put("productionMwh", Double.class.getName()); queryEntity.setFields(fields); // 添加asset字段的索引 queryEntity.setIndexes(Collections.singletonList(new QueryIndex("asset"))); prodCacheCfg.setQueryEntities(Collections.singletonList(queryEntity)); // 创建缓存并启用BinaryObject模式 IgniteCache<Integer, BinaryObject> productions = ignite.getOrCreateCache(prodCacheCfg).withKeepBinary();
3. 查询语句的修改
原来的ScanQuery无法利用索引,我们需要换成SQL查询(SqlQuery或SqlFieldsQuery),这样就能用上asset字段的索引。推荐用SqlFieldsQuery直接在服务器端完成聚合,减少客户端和服务器之间的数据传输,性能更好:
优化后的查询代码(服务器端聚合)
IgniteCache<Integer, BinaryObject> productions = ignite.cache("productions").withKeepBinary(); // 用SQL直接按月汇总指定父键的生产数据 SqlFieldsQuery query = new SqlFieldsQuery( "SELECT DATE_TRUNC('month', timestamp) as month, SUM(productionMwh) " + "FROM productions " + "WHERE asset = ? " + "GROUP BY DATE_TRUNC('month', timestamp)" ); // 设置父键参数 query.setArgs(key); // 执行查询并处理结果 try (QueryCursor<List<?>> cursor = productions.query(query)) { Map<LocalDate, Double> totals = new HashMap<>(); for (List<?> row : cursor) { // 把服务器返回的时间戳转成当月第一天的LocalDate LocalDate month = ((Timestamp) row.get(0)).toLocalDateTime().toLocalDate().withDayOfMonth(1); Double sum = (Double) row.get(1); totals.put(month, sum); } // 直接使用汇总后的totals即可 }
如果需要遍历子记录(不做服务器端聚合)
用SqlQuery获取指定asset的所有子记录:
SqlQuery<BinaryObject, BinaryObject> sqlQuery = new SqlQuery<>(BinaryObject.class, "asset = ?"); sqlQuery.setArgs(key); try (QueryCursor<Cache.Entry<BinaryObject, BinaryObject>> cursor = productions.query(sqlQuery)) { Map<LocalDate, Double> totals = new HashMap<>(); for (Cache.Entry<BinaryObject, BinaryObject> entry : cursor) { OffsetDateTime timestamp = entry.getValue().field("timestamp"); double productionMwh = entry.getValue().field("productionMwh"); LocalDate month = timestamp.toLocalDate().withDayOfMonth(1); totals.put(month, totals.getOrDefault(month, 0.0) + productionMwh); } }
4. 亲和键的使用建议
你已经通过亲和键实现了父、子记录的共置,这一点非常好!这里补充两个最佳实践:
- 亲和键放在子缓存的键上:如果当前子缓存用的是普通
Integer键,建议改成AffinityKey<Integer, Integer>,其中第二个参数是父缓存的键(asset值)。这样Ignite会自动把同一父键的子记录放到同一个节点上。 - 自动利用亲和路由:当你查询指定父键的子记录时,Ignite会自动根据亲和键路由到对应的节点,只在该节点执行查询,不用遍历所有节点。上面的SQL查询会自动触发这个逻辑,进一步提升性能。
为什么这能解决性能问题?
原来的ScanQuery.setLocal(true)是在当前节点全量扫描本地缓存条目再过滤,数据量增大时时间线性增长。而用索引+SQL查询后:
- Ignite通过索引直接定位目标记录,避免全扫描
- 亲和路由让查询只在目标节点执行,不用跨节点操作
- 服务器端聚合减少了数据传输量,整体效率大幅提升
内容的提问来源于stack exchange,提问作者Gadi
相关产品推荐
相关产品推荐

