You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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查询后:

  1. Ignite通过索引直接定位目标记录,避免全扫描
  2. 亲和路由让查询只在目标节点执行,不用跨节点操作
  3. 服务器端聚合减少了数据传输量,整体效率大幅提升

内容的提问来源于stack exchange,提问作者Gadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:34:30