3. 成本数据管道工程

当成本治理从“看账单”进入“做工程”阶段,第一块真正的地基不是预算告警,而是成本数据管道。AWS 提供的原始成本数据是 Cost and Usage Report(CUR),它按小时、按资源、按用量维度记录账户中每一次计费事件。CUR 2.0 在原有列式结构上引入了更规范的列命名、更细的账单周期语义,以及 Data Exports 交付方式,使数据可以直接以 Parquet 格式落入 S3,而不再必须依赖 CSV 加 Glue 转换。理解这条管道的每一段,决定了后续分摊、归因、异常检测能否稳定运行。

CUR 2.0 的数据格式与关键列

CUR 2.0 的本质是一张宽表:每一行代表“某个账单周期内、某个账户、某个资源、某个用量类型、某个定价维度”下的一次计费记录。它既包含用量(usage),也包含费用(cost),还包含资源标签、定价条款、折扣、摊销与预留实例覆盖等信息。理解 CUR 时,最容易被忽略的是费用列之间的语义差异:line_item_unblended_cost 是未混合的原始费用,line_item_blended_cost 是组织内混合后的费用,reservation_effective_cost 与 savings_plan_effective_cost 则把预付和承诺折扣按小时摊销到实际用量上。做成本归因时,如果只看 unblended,会低估 RI/SP 覆盖资源的真实单位成本;如果只看 blended,又无法定位具体账户的采购差异。

另一个关键维度是 line_item_line_item_type,它区分 Usage、Tax、Fee、Credit、Refund、RIFee、SavingsPlanCoveredUsage 等记录类型。很多“成本对不上”的问题,根源就是把 RIFee 或 Credit 行误当成普通用量行参与聚合。CUR 2.0 还通过 bill_billing_period_start_date 与 line_item_usage_start_date 明确区分账单周期与用量发生时间,这对跨月摊销和按天回溯至关重要。

CUR 2.0 成本数据管道:从账单交付到 Athena 查询 ① 账单源 Billing & Cost Mgmt Data Exports CUR 2.0 Parquet / CSV 按小时粒度 ② S3 数据湖 s3://finops-cur/ billing-period=2025-06/ account_id=111122223333/ region=ap-southeast-1/ part-000.parquet ③ Glue 目录 Crawler 扫描 Database: finops_cur Table: cur2_parquet 分区: billing_period account_id, region ④ Athena 查询 SQL on Parquet 按分区裁剪 列式扫描 按标签/账户聚合 输出成本视图 CUR 2.0 关键列分组(查询与归因时最常使用) 身份与时间 bill_payer_account_id line_item_usage_account_id usage_start_date 资源与用量 line_item_resource_id line_item_usage_type line_item_operation 费用与折扣 unblended_cost reservation_effective_cost savings_plan_effective_cost 标签与产品 resource_tags_user_* product_servicecode line_item_line_item_type 分区裁剪效果(示例) 未分区:扫描全量 30 天 Parquet ≈ 扫描 720 个分区文件,扫描量大、费用高 按 billing_period + account_id 分区:仅扫描目标账户目标周期,扫描量可下降一个数量级

S3 数据湖落地与目录设计

Data Exports 可以直接把 CUR 2.0 以 Parquet 格式投递到指定 S3 前缀。落地时最重要的不是“存下来”,而是目录结构是否与查询模式一致。Athena 按分区裁剪文件,如果目录按 billing-period=2025-06/account_id=111122223333/ 组织,那么“查某账户某月成本”只需扫描少量文件;如果所有文件平铺在一个前缀下,每次查询都要扫描全量,成本会随数据增长线性上升。

一个可用的落地约定是:s3://finops-cur/cur2/billing-period=YYYY-MM/account_id=.../region=.../。其中 billing-period 用账单周期而非用量日期,保证跨月摊销记录落在正确周期;account_id 用 line_item_usage_account_id,便于按业务账户归因;region 用于区域成本对比。需要注意,Data Exports 的 Parquet 文件本身已带列式压缩,单文件不宜过小,否则会产生大量小文件,拖慢 Athena 规划阶段。若发现单分区下文件数过多,可用 Glue ETL 或 S3 Batch 做小文件合并。

Glue 爬虫与 Athena 查询

Parquet 落地后,用 Glue Crawler 扫描 S3 前缀,自动推断 schema 并写入 Glue Data Catalog。Crawler 的关键配置是分区识别:如果目录名采用 key=value 形式,Crawler 会将其识别为分区列;否则只会当成普通路径。爬取完成后,在 Athena 中即可用标准 SQL 查询。下面是一段按账户与产品聚合当月摊销成本的示例查询:

SELECT
  line_item_usage_account_id AS account_id,
  product_servicecode        AS service,
  SUM(COALESCE(reservation_effective_cost, 0)
    + COALESCE(savings_plan_effective_cost, 0)
    + COALESCE(line_item_unblended_cost, 0)) AS amortized_cost
FROM finops_cur.cur2_parquet
WHERE billing_period = '2025-06'
  AND line_item_line_item_type IN ('Usage','SavingsPlanCoveredUsage','RIFee')
GROUP BY 1, 2
ORDER BY amortized_cost DESC
LIMIT 50;

这段查询刻意把 RI/SP 有效成本与未混合成本相加,是为了让被承诺折扣覆盖的资源也体现真实摊销成本。若只写 line_item_unblended_cost,被 RI 覆盖的 EC2 用量会显示为 0,导致成本被严重低估。查询时始终带上 billing_period 条件,是触发分区裁剪、控制扫描量的最低要求。

Parquet 分区优化与常见判断

分区不是越多越好。分区列基数过高(例如把 resource_id 作为分区)会产生海量小分区,反而拖慢查询。合理的分区列应满足:查询条件中高频出现、基数适中、每个分区下有足够大的文件。对 CUR 而言,billing_period、account_id、region 是较稳妥的组合。若业务需要按标签归因,标签应作为普通列而非分区列,通过 Athena 的 resource_tags_user_* 列过滤。

判断分区是否生效,可在 Athena 控制台查看查询的“扫描数据量”。同一查询在分区前后扫描量若没有明显下降,通常说明 WHERE 条件未命中分区列,或 Crawler 未把目录识别为分区。
CUR 2.0 的 Parquet 文件由 AWS 直接生成,列名与旧版 CSV 存在差异。迁移旧查询时,务必核对 line_item_usage_start_date、bill_billing_period_start_date 等列名,避免因列不存在导致查询失败或结果为空。

把这条管道跑通后,成本数据才真正成为可查询、可聚合、可归因的资产。后续的分摊规则、异常检测、单位成本指标,都建立在这张按小时粒度、带标签、带摊销成本的 CUR 2.0 表之上。管道稳定,治理才有可靠的数据底座。