总述
基于
requestId、taskId、attemptId、applicationId建立全链路成本归因链路,把租户、用户、业务线、数据集和 SQL 指纹与实际 CPU、内存、存储、网络等资源消耗关联起来。
不同情况不同计算
- 不同引擎的资源指标通过 EngineConn 适配层统一,再按照企业资源单价计算任务成本。
- 对于重试任务,我会区分 Task 总成本、每次 Attempt 成本和失败重试成本;
- 对于共享集群,则按实际资源使用量进行分摊。
最终将成本、SLA、执行耗时和成功率反哺路由层,让平台在满足 SLA 的前提下优先选择成本更低、复用率更高的执行环境。
论述
成本归因要解决的问题是:
- 查询执行消耗了多少计算资源,
- 这些资源应该归属到哪个租户、用户、业务线、数据集和 SQL 上
- 能够进一步判断哪些查询成本过高、哪些引擎或集群更划算。
我会把成本归因建立在全链路任务标识之上,而不是只根据最终结果或集群总账单估算。
整体归因分析链路是:
查询请求
-> Task
-> ExecutionAttempt
-> RouteDecision
-> EngineConn
-> 外部 Application
-> 资源指标
-> 成本计算
-> 业务归因
第一,建立归因维度
提交请求时,统一入口需要补齐归因上下文:
tenantId
userId
businessLine
application
dataset
sqlFingerprint
taskId
attemptId
routeDecisionId
engine
cluster
queue
其中:
tenantId:成本归属哪个租户
businessLine / application:成本归属哪个业务线或应用
dataset:成本由哪些数据集产生
sqlFingerprint:哪类 SQL 消耗成本最高
taskId / attemptId:哪一次任务、哪一次执行尝试产生了成本
这些信息要一路透传到 Linkis、EngineConn 和外部计算引擎,最后通过 applicationId 和资源监控系统关联起来。
第二,采集真实资源消耗
对于一次查询,不能只记录执行时长,还要采集实际资源:
queueTime
executeTime
cpuTime
memoryUsage
executorCores
executorMemory
executorCount
scanBytes
shuffleBytes
diskBytes
例如 Spark 任务需要采集:
applicationId
vcoreSeconds
memorySeconds
executor 数量
Executor 运行时长
Shuffle 数据量
Trino 或 JDBC 可能没有完全相同的指标,因此需要在引擎适配层统一成平台模型:
cpuCost
memoryCost
storageCost
networkCost
engineCost
这样上层不需要感知 Spark、Trino 和 JDBC 指标的差异。
第三,建立成本计算模型
成本不一定直接等于执行时间,应该根据企业实际资源单价计算。
例如:
计算成本 =
CPU 使用量 × CPU 单价
+ 内存使用量 × 内存单价
+ 存储成本
+ 网络成本
+ 引擎或集群附加成本
Spark 可以按:
vcoreSeconds
memorySeconds
计算。
Trino 可以按:
CPU 时间
内存使用时间
查询执行时间
计算。
如果企业暂时没有精确的云账单,也可以先建立内部成本模型:
CPU:0.01 元 / core-minute
内存:0.005 元 / GB-minute
存储:0.02 元 / GB
例如一次查询实际消耗:
CPU:40 core-minutes
内存:80 GB-minutes
那么:
CPU 成本 = 40 × 0.01 = 0.4 元
内存成本 = 80 × 0.005 = 0.4 元
总成本 = 0.8 元
重点是先保证归因口径统一,后续再替换成云厂商或集群真实计费价格。
第四,处理重试和共享资源
这里要特别区分:
Task 成本
ExecutionAttempt 成本
如果第一次路由到 Trino 失败,第二次切换到 Spark:
Task:查询订单金额
Attempt 1:Trino,消耗 0.2 元,失败
Attempt 2:Spark,消耗 1.5 元,成功
这次查询的总成本应该是:
Task 总成本 = Attempt 1 成本 + Attempt 2 成本
= 1.7 元
但在分析时还要区分:
业务查询成本:1.7 元
失败重试成本:0.2 元
有效结果成本:1.5 元
否则无法判断成本高是因为查询本身复杂,还是因为路由错误导致重复执行。
对于共享集群,也不能把整个集群成本直接归给某个租户,而应该按资源使用量进行分摊:
租户成本 =
租户实际 CPU / 集群总 CPU × 集群成本
+
租户实际内存 / 集群总内存 × 集群成本
如果无法精确获得资源使用量,可以根据:
队列
Executor
执行时长
CPU 和内存配置
进行近似分摊,但要明确这是估算成本。
第五,建立成本归因链路
成本归因简单说就是:
一次查询消耗了多少 CPU、内存和存储资源,这笔成本最后算到哪个租户、哪个业务线、哪条 SQL 上。
最终要把成本信息和查询链路关联起来:
requestId
-> taskId
-> attemptId
-> routeDecisionId
-> engineConnId
-> applicationId
-> sqlFingerprint
-> costRecord
成本记录可以包含:
tenantId
businessLine
dataset
sqlFingerprint
taskId
attemptId
engine
cluster
cpuCost
memoryCost
storageCost
networkCost
totalCost
isRetry
status
这样可以回答:
哪个租户成本最高?
哪个业务线增长最快?
哪类 SQL 最耗资源?
哪个引擎执行成本最低?
哪些查询经常因为错误路由产生重试?
第六,成本如何反哺路由
成本归因不是为了做报表,而是要反过来影响路由决策。
例如同一个 SQL 指纹:
Trino:
执行 10 秒
成本 0.8 元
SLA 命中率 95%
Spark:
执行 60 秒
成本 2.5 元
SLA 命中率 98%
如果用户要求 10 秒返回,系统可以优先选择 Trino。
如果查询范围扩大后:
Trino:
成本 8 元
经常内存不足
Spark:
成本 3 元
执行稳定
这时虽然 Trino 是交互式引擎,但它已经不是更优选择,路由层应该切换到 Spark。
因此路由评分可以加入成本因素:
routeScore =
性能得分
+ SLA 得分
+ 稳定性得分
- 资源成本
- 排队成本
同时需要设置成本预算:
单次查询成本上限
租户每日成本上限
业务线月度预算
超预算后的降级策略
如何证明成本归因有效
我会通过以下指标验证:
成本归因完整率
成本归因准确率
租户成本可见率
单位查询成本
重复查询节省成本
失败重试成本
查询结果复用率
SLA 成本
例如:
结果复用率提高
重复扫描次数下降
单位查询成本下降
错误路由导致的重试成本下降
这些指标可以证明成本归因不仅能解释“钱花在哪里”,还推动了查询性能和引擎复用率的优化。
其他
成本归因的思路
成本归因的关键是通过任务 ID 把业务上下文和外部引擎的资源消耗关联起来。任务创建时记录租户、业务线、数据集和 SQL 指纹;每次执行生成独立的 Attempt;Linkis 提交 Spark 或 Trino 后,再把外部 applicationId 和 attemptId 关联起来。
外部引擎只提供某个 applicationId 消耗了多少 CPU、内存和存储,平台再通过 applicationId -> attemptId -> taskId 向上找到租户和业务线,最终生成 costRecord。如果一个任务因为路由错误执行了两次,就把两个 Attempt 的成本相加作为 Task 总成本,同时单独统计失败重试成本。
这样就能回答哪个租户、业务线、数据集和 SQL 指纹成本最高,也能识别错误路由造成了多少资源浪费。
1504

被折叠的 条评论
为什么被折叠?



