PAYATHON 2026

Azure Billing Usage API 聚合功能无法正常工作

数据小许

结论

aggregationGranularity=Daily 只说明用量记录按“天”划分时间区间,并不代表整个订阅每天只会返回一条记录。showDetails=false 也不会强制按 subscriptionId + meterId + usageStartTime + usageEndTime 进行唯一分组。

Azure Usage Aggregates API 还可能按资源实例、区域、计费属性等内部维度拆分记录。如果只检查上述四个字段,就可能看到貌似重复的数据。需要继续比较记录中的其他维度,再根据报表需求,在客户端或数据库中执行 GROUP BY 和 SUM(quantity)。

如果 showDetails=true 和 false 返回的完整记录完全一致,还要考虑这个 Preview API 可能没有按预期处理参数。此时不能依靠 showDetails 保证最终的汇总结果。

为什么会出现“重复记录”

aggregationGranularity=Daily 控制的是时间粒度。例如:

usageStartTime = 2016-03-02T00:00:00Z
usageEndTime   = 2016-03-03T00:00:00Z

即使日期和 meterId 相同,也可能返回多条记录,因为它们可能对应不同的:

  • 资源实例或资源 URI
  • Resource Group
  • Region
  • instanceData
  • infoFields
  • 计费属性或用量来源

因此,下面四个字段通常不能作为唯一键:

subscriptionId
meterId
usageStartTime
usageEndTime

应比较完整响应,尤其是 instanceData、infoFields、quantity、记录 ID 和资源相关字段。

showDetails=false 用于请求较高层级的用量汇总或减少实例明细,但不等同于:

GROUP BY subscriptionId, meterId, usageStartTime, usageEndTime

服务端使用的计费维度可能比响应中直接显示的字段更多,这种情况在旧版 Preview 接口中尤其需要留意。

推荐处理方式

1. 使用规范的时间参数

参数名建议采用文档中的大小写,时间则使用标准 URL 编码:

GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Commerce/UsageAggregates
    ?api-version=2015-06-01-preview
    &reportedStartTime=2016-03-02T00%3A00%3A00Z
    &reportedEndTime=2016-03-03T00%3A00%3A00Z
    &aggregationGranularity=Daily
    &showDetails=false

时间应使用 UTC。日期范围最好按左闭右开处理。例如,查询 3 月 2 日的数据时使用:

[2016-03-02T00:00:00Z, 2016-03-03T00:00:00Z)

2. 检查完整记录差异

不要只比较那四个字段。可以同时保存或输出以下内容:

id
name
subscriptionId
meterId
usageStartTime
usageEndTime
quantity
instanceData
infoFields

如果 instanceData 或资源 URI 不同,就不是重复数据,而是不同资源产生的同类计量用量。

3. 按实际报表维度重新汇总

如果需要得到“每个订阅、每天、每个 meter 的总用量”,可以在数据库中进行汇总:

SELECT
    subscription_id,
    meter_id,
    usage_start_time,
    usage_end_time,
    SUM(quantity) AS total_quantity
FROM azure_usage
GROUP BY
    subscription_id,
    meter_id,
    usage_start_time,
    usage_end_time;

如果不同记录可能使用不同的计量单位,还应把 unit 加入分组条件:

SELECT
    subscription_id,
    meter_id,
    unit,
    usage_start_time,
    usage_end_time,
    SUM(quantity) AS total_quantity
FROM azure_usage
GROUP BY
    subscription_id,
    meter_id,
    unit,
    usage_start_time,
    usage_end_time;

不同 meterId 或不同单位的 quantity 不能直接相加。

4. 处理分页并保证写入幂等

Usage API 的响应可能带有 nextLink。请求后续页面时,应直接使用服务端返回的 nextLink,不要自行重新拼接查询参数。

写入数据库时,也不能只根据日期和 meterId 判断记录是否重复。可以保存 API 返回的记录 ID。如果接口没有合适的稳定标识,则应根据完整的业务维度生成幂等键。

例如:

var dailyUsage = records
    .GroupBy(x => new
    {
        x.SubscriptionId,
        x.MeterId,
        x.Unit,
        Day = x.UsageStartTime.Date
    })
    .Select(group => new
    {
        group.Key.SubscriptionId,
        group.Key.MeterId,
        group.Key.Unit,
        group.Key.Day,
        Quantity = group.Sum(x => x.Quantity)
    })
    .ToList();

如何判断是维度拆分还是异常重复

可以根据以下情况判断:

  • 四个字段相同,但 instanceData、资源 URI 或其他属性不同:属于正常的维度拆分,可按实际需求汇总。
  • 完整记录相同,但来自不同分页:检查是否重复请求了第一页,或者是否错误拼接了 nextLink。
  • 完整记录相同,且来自同一次响应:可能是 Preview 服务的数据问题。应保存原始响应、请求时间和相关请求标识,再提交 Azure 支持工单。
  • showDetails=true 与 false 的完整响应始终完全相同:不要把这个参数作为汇总正确性的唯一依据,应用端仍需明确执行聚合。

注意事项

2015-06-01-preview 是较旧的 Preview 接口。新系统应优先评估当前受支持的 Azure Consumption Usage Details API 或 Cost Management Query API,并结合订阅类型、计费账户类型和 Azure 官方文档选择接口。迁移前应重新确认字段含义、分页方式、时间范围和聚合维度,不能假设新旧接口的行为相同。

备注:内容仅供参考。