PAYATHON 2026

BigQuery total_slots_ms 估算成本与账单差异

支付阿杰

结论

total_slot_ms × 0.04 美元/slot-hour 得到的是作业实际消耗的 slot 时间所对应的金额,并不是 Standard Edition reservation 的实际账单金额。两者相差很大并不异常。

BigQuery Editions 的容量费用主要取决于计费期间 reservation 实际配置或由 autoscaler 提供的容量,具体包括:

  • 持续计费的 baseline slots;
  • autoscaling 已提供、但作业没有完全使用的 slots;
  • autoscaling 的计费粒度、最短计费时间和缩容延迟;
  • 使用该 reservation 的其他项目、文件夹或组织中的作业;
  • 区域价格、币种、折扣和 credits。

因此,total_slot_ms 适合衡量计算资源利用率,不能直接作为 reservation 的计费基数。实际金额应以 Cloud Billing 导出数据为准。

为什么 total_slot_ms 会低于账单

total_slot_ms 表示作业执行期间实际消耗的 slot 毫秒数。下面的单位换算没有问题:

slot-hours = total_slot_ms / (1000 × 60 × 60)

但 reservation 采用容量计费。假设某个 reservation 在一小时内有 500 个可计费 slots,而作业实际只使用了其中 100 个:

计费容量:500 slot-hours
作业消耗:100 slot-hours

通过 total_slot_ms 只能算出后者。其余空闲容量不会出现在作业的 total_slot_ms 中,但 baseline capacity 仍可能产生费用。

autoscaling 也不是严格按每个作业消耗的 slot 毫秒数结算。扩出的容量可能在作业结束后继续保留一段时间,并受到最短计费时间和容量调整粒度的影响。即使没有固定 baseline,所有作业的 total_slot_ms 相加,也不一定等于可计费的 slot 时间。

此外,0.04 不能作为所有场景下的固定单价。实际价格取决于 edition、region、购买方式、合同折扣和币种。使用 commitments 时,还要同时计算承诺容量的费用和 credits。

当前查询还有几个统计范围问题

1. 只统计了一个项目

查询使用的是:

FROM `region-us`.INFORMATION_SCHEMA.JOBS
WHERE project_id = 'open-bridge-bg'

一个 reservation 可以供多个项目使用,也可以通过 folder 或 organization assignment 共享。账单包含整个 reservation 的容量成本,而这条查询只能看到一个项目中的作业。

需要确认以下范围是否一致:

  • reservation 所在的 location;
  • reservation 的所有 assignments;
  • 使用该 reservation 的全部项目;
  • 查询时间范围和账单周期;
  • 账单使用的时区。

如果具备组织级权限,可以考虑使用相应区域的 JOBS_BY_ORGANIZATION。否则,需要分别收集所有相关项目的作业数据。

2. CROSS JOIN UNNEST 会排除空数组作业

下面的写法会直接排除 job_stages 或 timeline 为空的作业:

CROSS JOIN UNNEST(job_stages)
CROSS JOIN UNNEST(timeline)

同时展开两个数组还会产生笛卡尔积。当前的 GROUP BY 在部分情况下可以将结果重新聚合为一行,但这会增加扫描量和计算复杂度,后续修改查询时也容易造成重复统计。

计算总 slot 使用量不需要展开这两个数组。

3. 日期条件可能覆盖 31 个日期

下面的条件包含起止两端:

DATE(creation_time) BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
                        AND CURRENT_DATE()

它通常会覆盖今天和此前 30 个日期,共 31 个日历日期。如果账单按 UTC 或指定的账单时区结算,边界处还可能出现偏差。建议改用半开区间,并明确时区。

4. 脚本父作业可能导致重复统计

多语句查询可能同时产生 SCRIPT 父作业和子作业。按当前统计目的,应只计算子作业,避免把父作业汇总值与子作业再次相加。常见的过滤条件是:

WHERE statement_type != 'SCRIPT'
   OR statement_type IS NULL

先正确统计作业消耗

下面的查询用于计算某个项目在指定 UTC 时间范围内实际消耗的 slot。它不会展开 job_stages 和 timeline:

SELECT
  reservation_id,
  SUM(total_slot_ms) AS consumed_slot_ms,
  SAFE_DIVIDE(SUM(total_slot_ms), 1000 * 60 * 60) AS consumed_slot_hours,
  SAFE_DIVIDE(SUM(total_slot_ms), 1000 * 60 * 60) * 0.04
    AS consumption_value_at_004
FROM `region-us`.INFORMATION_SCHEMA.JOBS
WHERE project_id = 'open-bridge-bg'
  AND creation_time >= TIMESTAMP('2026-08-01 00:00:00+00')
  AND creation_time <  TIMESTAMP('2026-09-01 00:00:00+00')
  AND (statement_type != 'SCRIPT' OR statement_type IS NULL)
  AND total_slot_ms IS NOT NULL
GROUP BY reservation_id;

这里的 consumption_value_at_004 只能理解为:

假设每个实际消耗的 slot-hour 都按 0.04 美元计价,其对应的资源消耗价值。

它不能用来预测 reservation 账单。

如果只统计某个 reservation,还应添加精确的 reservation_id 条件,以免混入按需计算、其他 reservation 或不同 assignment 下的作业。

根据 reservation 容量估算

更接近账单的估算公式是:

估算费用
≈ baseline slot-hours × 对应 edition 单价
+ autoscaled slot-hours × 对应 edition 单价

其中:

baseline slot-hours
= Σ(baseline slots × 持续时间)

autoscaled slot-hours
= Σ(autoscaler 实际提供的额外 slots × 可计费持续时间)

这里应使用 reservation 时间线或监控指标中的“已配置或已提供容量”,而不是根据作业的 total_slot_ms 反推。可以查询所在区域的 reservation timeline 视图,先查看容量变化:

SELECT *
FROM `region-us`.INFORMATION_SCHEMA.RESERVATIONS_TIMELINE
WHERE period_start >= TIMESTAMP('2026-08-01 00:00:00+00')
  AND period_start <  TIMESTAMP('2026-09-01 00:00:00+00')
ORDER BY period_start;

该视图的具体字段和可见范围,应以当前项目返回的 schema 为准。计算时需要分别处理 baseline、autoscaling 上限和 autoscaler 实际提供的容量,不能把 autoscaling 上限直接视为实际计费容量。

根据相邻时间点计算持续时间时,还要处理以下情况:

  • 查询区间开始前已经生效的容量;
  • 区间结束时仍然生效的最后一条记录;
  • reservation 配置变更;
  • autoscaling 的计费规则;
  • 不同 edition 或 location 的单价变化。

这种方法可以用于分析容量利用率和估算预算,但不能作为账单对账的唯一依据。

使用 Billing Export 核对实际费用

最可靠的做法是启用 Cloud Billing 导出到 BigQuery,再按 BigQuery SKU 汇总费用和 credits。例如:

SELECT
  service.description AS service_description,
  sku.description AS sku_description,
  currency,
  SUM(cost) AS gross_cost,
  SUM((
    SELECT COALESCE(SUM(credit.amount), 0)
    FROM UNNEST(credits) AS credit
  )) AS credits,
  SUM(cost) +
  SUM((
    SELECT COALESCE(SUM(credit.amount), 0)
    FROM UNNEST(credits) AS credit
  )) AS net_cost
FROM `billing_project.billing_dataset.gcp_billing_export_v1_BILLING_ACCOUNT_ID`
WHERE service.description = 'BigQuery'
  AND usage_start_time >= TIMESTAMP('2026-08-01 00:00:00+00')
  AND usage_start_time <  TIMESTAMP('2026-09-01 00:00:00+00')
GROUP BY
  service_description,
  sku_description,
  currency
ORDER BY ABS(net_cost) DESC;

不要一开始就使用模糊的 SKU 名称过滤。先列出 sku.description,确认哪些记录对应:

  • BigQuery Standard Edition capacity;
  • commitments 或相关 credits;
  • analysis、storage、BI Engine、data transfer 等其他费用。

实际导出表名、可用字段和资源标签,取决于启用的是标准导出还是详细导出。

推荐的对账方式

先从 Billing Export 中取得同一账期内 reservation 相关 SKU 的净费用,再根据 reservation timeline 计算同期的可计费容量,最后通过作业的 total_slot_ms 计算利用率:

容量利用率
= 作业实际消耗的 slot-hours
  / reservation 可计费或已提供的 slot-hours

这两类数据回答的是不同问题:

  • total_slot_ms 表示作业实际使用了多少计算资源;
  • Billing Export 表示这些 reservation 容量实际产生了多少费用。

如果两者差距很大,应优先检查 baseline slots 是否长期空闲、autoscaling 是否利用不足、是否遗漏了其他项目、账期边界是否一致,以及折扣或 credits 是否已计入,而不是用 storage cost 补齐差额。

备注:内容仅供参考。