PAYATHON 2026

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 错误日志。

  1. 只查询 teu。保留原来的维度、时间范围和过滤条件,确认普通预聚合查询是否成功。如果也失败,排查范围就不能只限定在同比计算。
  2. 查询 teu 和 teu_py。如果基础查询成功、加入 teu_py 后失败,重点检查时间偏移分支。
  3. 查询 teu 和 teu_yoy_pct。如果上一年值查询成功,只有同比百分比失败,重点检查组合 measure 生成的 JOIN、投影和计算表达式。
  4. 移除 teu >= 1000。原始 SQL 中,这个过滤条件参与了 HAVING 和最终 WHERE。如果移除后成功,可以继续缩小聚合过滤与多阶段计划的组合,但这并不代表过滤条件写错了。
  5. 逐个减少分组维度。例如先移除描述字段 por_country_nme,再测试 hs2_desc,检查投影列集合的变化是否影响报错。
  6. 对比直接查询 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:

  1. 使用相同的业务维度、src = OB 和年度粒度查询基础 TEU。
  2. 将取数范围扩展到 2023-01-01 至 2025-12-31,为 2024 年的同比保留 2023 年数据。
  3. 按相同分组键匹配年份 y 与 y - 1,然后计算同比。
  4. 计算后只保留 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 计划中的问题。现阶段不应将“列索引越界”直接归因于缺少某个物理字段,也没有依据要求重建预聚合或修改同比公式。

备注:内容仅供参考。