Cube.js 同比查询触发 CubeStore 列索引越界,如何排查?
结论
从错误信息看,查询在 CubeStore 内部的 DataFusion ProjectionPushdown 优化阶段失败。应优先检查 multi-stage 同比查询生成的执行计划,以及 CubeStore 使用的优化器实现。
目前可以确定报错发生的阶段,但无法确定是哪个 SQL 节点触发了缺陷,也无法确认某个 CubeStore 版本已经修复。time_shift、measure 过滤条件和多层 JOIN 都需要分别验证。
先缩小查询,找出最小失败条件。如果暂时无法修复,可以在基础年度聚合查询成功的前提下,将上一年匹配和同比计算移到应用层。
“6 列却访问索引 6”说明什么?
如果一个 schema 有 6 列,按从 0 开始的索引访问,有效范围就是 0–5。错误说明优化器处理的列引用与当时的 schema 不一致。
报错中的 schema 不一定是 pre-aggregation 物理表的 schema,也可能是投影、聚合或 JOIN 后的中间 schema。
给出的模型包含 5 个普通维度、1 个时间维度和 teu measure;生成的 SQL 也明确读取了 biz_mkt_volume__teu。因此,不能仅凭错误中的 “6 columns” 认定预聚合表只有列出的 6 个维度字段,也不能据此通过“补一列”修复问题。
cte_0 恰好输出 4 个分组维度、1 个时间表达式和 1 个聚合值,共 6 列。这可以作为排查线索,但不能证明越界发生在这里。
按顺序缩小失败条件
保留原始失败请求作为基线,每次只修改一个因素,同时保存 /cubejs-api/v1/sql 的 SQL、参数和 CubeStore 错误日志。
- 只查询
teu。保留原来的维度、时间范围和过滤条件,确认普通预聚合查询是否成功。如果也失败,排查范围就不能只限定在同比计算。 - 查询
teu和teu_py。如果基础查询成功、加入teu_py后失败,重点检查时间偏移分支。 - 查询
teu和teu_yoy_pct。如果上一年值查询成功,只有同比百分比失败,重点检查组合 measure 生成的 JOIN、投影和计算表达式。 - 移除
teu >= 1000。原始 SQL 中,这个过滤条件参与了HAVING和最终WHERE。如果移除后成功,可以继续缩小聚合过滤与多阶段计划的组合,但这并不代表过滤条件写错了。 - 逐个减少分组维度。例如先移除描述字段
por_country_nme,再测试hs2_desc,检查投影列集合的变化是否影响报错。 - 对比直接查询 cube 和通过 view 查询。将相关 member 前缀一致地改为
biz_mkt_volume,比较生成的 SQL 和执行结果。如果存在差异,再调查 view 展开是否参与触发问题。
这些是定位实验,不能提前认定某一步一定能绕过错误。减少维度或移除过滤条件也会改变结果,不能直接当作业务查询的最终修复。
一个用于检查时间偏移分支的请求
下面的请求保留原始分组和时间范围,查询基础值与上一年值,暂时移除 measure 过滤。它用于诊断,不等同于原始完整请求。
{
"query": {
"dimensions": [
"market_volume_fixed_period_comparison.por_country_cde",
"market_volume_fixed_period_comparison.por_country_nme",
"market_volume_fixed_period_comparison.hs2_code",
"market_volume_fixed_period_comparison.hs2_desc"
],
"measures": [
"market_volume_fixed_period_comparison.teu",
"market_volume_fixed_period_comparison.teu_py"
],
"timeDimensions": [
{
"dimension": "market_volume_fixed_period_comparison.dep_arr_date_ts",
"dateRange": ["2024-01-01", "2025-12-31"],
"granularity": "year"
}
],
"filters": [
{
"member": "market_volume_fixed_period_comparison.src",
"operator": "equals",
"values": ["OB"]
}
],
"limit": 20
}
}
实际进行单变量对比时,应先仅改变 measures,再单独移除 teu >= 1000。
原始请求示例没有给字符串加引号,模型示例的 YAML 缩进也没有体现层级。实际发送 JSON 和加载模型时需要使用合法格式。不过,既然已经生成 SQL 并进入 CubeStore 优化阶段,这些粘贴格式问题无法解释当前的 ProjectionPushdown 内部错误。
如何检查版本修复是否适用?
需要确认失败的最小 SQL、修复该问题的代码和实际运行的 CubeStore 构建三者是否对应。
问题提供的 Cube issue #10439 和 DataFusion issue #14679、issue #17679 可以作为核对入口,但现有材料不足以确认修复版本。错误文本相似,也不等于同一个缺陷。
如果尝试通过升级解决,应在隔离环境中使用相互兼容的 Cube server 和 CubeStore 版本,确认实际运行的构建,再执行最小复现和原始完整查询。验证时既要检查是否还报错,也要核对 TEU、上一年值、同比结果和过滤行为。
CubeStore 内部使用的 DataFusion 实现需要随相应构建更新。仅在应用项目中安装或升级独立的 DataFusion 包,不能据此认为 CubeStore 的优化器已经改变。也不要直接套用其他 DataFusion 应用的优化器开关,除非已确认当前 CubeStore 版本提供对应入口。
临时方案:查询年度基础值,再计算同比
如果只查询 teu 可以正常执行,可以暂时避开 multi_stage 和 time_shift:
- 使用相同的业务维度、
src = OB和年度粒度查询基础 TEU。 - 将取数范围扩展到
2023-01-01至2025-12-31,为 2024 年的同比保留 2023 年数据。 - 按相同分组键匹配年份
y与y - 1,然后计算同比。 - 计算后只保留 2024 年至 2025 年的结果,再按当年
teu >= 1000过滤,最后执行排序和分页。
计算表达式应保留原模型的规则:
CASE
WHEN teu_py > 0
THEN (teu - teu_py) * 100.0 / teu_py
ELSE NULL
END
读取历史基础值时,不能先对所有年份应用 teu >= 1000,否则上一年低于 1000 的记录会被提前丢弃,导致同比错误。也不能直接沿用取数阶段的 limit: 20 来保证结果完整;应通过适当的分页或取数范围取得计算所需的全部分组记录。
匹配键需要保持一致。原始查询使用国家代码、国家名称、HS2 code 和 HS2 描述共同分组;如果名称或描述跨年变化,这些记录不会自动成为同一组。改成只按稳定代码匹配属于业务口径调整,需要另行确认。缺少上一年数据,或上一年为 0、负数时,应继续返回 NULL,不要擅自改成 0%。
提交可定位的复现材料
可补充最小失败模型、严格 JSON 请求、完整 SQL 与参数、CubeStore 完整堆栈、实际版本或镜像标识,以及每次单变量修改后的结果。如果可以安全取得失败计划,还应附上相关节点的输入和输出 schema。
这些材料有助于区分时间表达式处理、投影列映射、聚合过滤和 JOIN 计划中的问题。现阶段不应将“列索引越界”直接归因于缺少某个物理字段,也没有依据要求重建预聚合或修改同比公式。
备注:内容仅供参考。
