数据成本优化项目:从月消费 3 万降到 8 千的完整路径
一、老板问"为什么这个月数据成本又涨了"的那一刻
上个月收到云服务账单,月消费 3.2 万。老板在群里 @ 我说:"我们数据量又没翻倍,成本怎么一直在涨?"我打开账单一看,好家伙,有几台 ClickHouse 节点的 CPU 使用率才 15%,一些没人用的离线表还在按天跑。
那一刻我就知道,成本优化这个项目躲不掉了。
经过 4 周的集中优化,我们把月成本从 3.2 万压到了 0.85 万,压缩率 73%。今天就把整个过程完整复盘出来。
成本优化项目的四阶段路径:
二、第一阶段:资源优化——砍掉浪费
2.1 账单拆解
第一步是把 3.2 万的账单按服务拆开,找出成本大头:
| 服务 | 月费用 | 占比 | 问题诊断 |
|---|---|---|---|
| ClickHouse 集群 | ¥12,000 | 37.5% | 4 节点集群,CPU 均值 18% |
| ECS 服务器(调度/ETL) | ¥6,500 | 20.3% | 若干机器常年低负载 |
| MaxCompute/EMR | ¥5,800 | 18.1% | 任务调度效率低下 |
| Kafka 集群 | ¥3,200 | 10.0% | 副本数过多 |
| OSS 存储 | ¥2,800 | 8.8% | 大量历史数据未归档 |
| 其他 | ¥1,700 | 5.3% | 杂项 |
2.2 ClickHouse 缩容
ClickHouse 是最大的成本黑洞。当前是 4 节点 16C64G 的配置,但我们分析了一周的 CPU 监控:
-- 分析 ClickHouse 各节点的实际负载 -- 发现 node-3 和 node-4 的 CPU 峰值都没超过 40% SELECT hostname, ROUND(AVG(cpu_usage_percent), 1) AS avg_cpu, ROUND(MAX(cpu_usage_percent), 1) AS max_cpu, ROUND(AVG(memory_usage_gb), 1) AS avg_memory_gb, ROUND(AVG(disk_read_mb_per_sec), 1) AS avg_disk_read, ROUND(SUM(query_count), 0) AS total_queries FROM clickhouse_metrics WHERE event_time >= NOW() - INTERVAL 7 DAY GROUP BY hostname ORDER BY avg_cpu DESC; -- 结果: -- node-1: avg_cpu=42.3%, max_cpu=78.5% -- node-2: avg_cpu=38.1%, max_cpu=65.2% -- node-3: avg_cpu=12.7%, max_cpu=35.1% ← 严重浪费! -- node-4: avg_cpu=15.2%, max_cpu=40.8% ← 严重浪费!果断缩容到 2 节点,月费从 12,000 降到 6,500。为了应对突发流量,加了个弹性策略:CPU 持续 > 70% 超过 5 分钟就自动扩容。
import pandas as pd import numpy as np def analyze_resource_waste(df_metrics, cpu_threshold_pct=30): """分析各服务节点的资源浪费情况 根据 CPU 使用率识别低负载的节点,给出缩容建议 参数: df_metrics: 各节点 7 天的监控指标数据 cpu_threshold_pct: CPU 使用率低于此值视为浪费 返回: 浪费分析和建议 """ # 按服务分组统计平均 CPU 使用率 service_stats = df_metrics.groupby(['service', 'node_id']).agg({ 'cpu_usage_pct': ['mean', 'max', 'std'], 'memory_usage_pct': 'mean', 'monthly_cost': 'first' # 每个节点的月费用 }).round(1) service_stats.columns = ['avg_cpu', 'max_cpu', 'cpu_std', 'avg_mem', 'monthly_cost'] service_stats = service_stats.reset_index() # 识别低负载节点 waste_nodes = service_stats[ (service_stats['avg_cpu'] < cpu_threshold_pct) & (service_stats['max_cpu'] < cpu_threshold_pct * 2) ] total_waste = waste_nodes['monthly_cost'].sum() print(f"=== 资源浪费分析 ===") print(f"低负载节点数: {len(waste_nodes)}") print(f"可节省月费: ¥{total_waste:,.0f}") print(f"\n建议缩容节点:") for _, row in waste_nodes.iterrows(): print(f" {row['service']}/{row['node_id']}: " f"平均CPU={row['avg_cpu']}%, 月费=¥{row['monthly_cost']:,.0f}") return waste_nodes, total_waste三、第二阶段:存储优化——冷热分层
冷热数据分层的逻辑很简单:访问频率高的数据用 SSD/高性能存储,一年前的数据迁到便宜的 OSS 归档。但分层的难点不在技术实施,而在准确判断哪些数据才是真正的"冷数据",一旦误判把热数据归档,业务查询就会严重受影响。
3.1 数据访问频率分析
-- 按表的最后访问时间分类:热/温/冷 -- 帮助决策哪些表可以迁移到低成本存储 SELECT table_schema, table_name, ROUND(data_length / 1024 / 1024 / 1024, 2) AS size_gb, DATEDIFF(NOW(), MAX(last_access_time)) AS days_since_access, MAX(last_access_time) AS last_access, CASE WHEN DATEDIFF(NOW(), MAX(last_access_time)) <= 7 THEN '热数据' WHEN DATEDIFF(NOW(), MAX(last_access_time)) <= 30 THEN '温数据' WHEN DATEDIFF(NOW(), MAX(last_access_time)) <= 90 THEN '冷数据' ELSE '极冷数据' END AS data_tier, -- 估算月存储成本(假设 SSD ¥0.8/GB/月, OSS ¥0.12/GB/月) ROUND(data_length / 1024 / 1024 / 1024 * 0.8, 0) AS current_monthly_cost, -- 如果迁移到OSS后的成本 ROUND(data_length / 1024 / 1024 / 1024 * 0.12, 0) AS oss_monthly_cost FROM information_schema.table_usage_stats ORDER BY data_length DESC;通过这次分析我们发现:60% 的存储空间被超过 3 个月未访问的数据占据。保守起见,我们只迁移了超过 180 天未访问的"极冷数据"到 OSS,月存储成本从 2,800 降到 900。
from datetime import datetime, timedelta def lifecycle_policy_generator(table_info_df): """自动生成数据生命周期管理策略 根据表的访问频率和业务重要性,自动建议保留周期 参数: table_info_df: 包含表名、大小、最后访问时间等信息 返回: 每张表的生命周期策略建议 """ policies = [] for _, row in table_info_df.iterrows(): days_inactive = (datetime.now() - row['last_access']).days size_gb = row['size_gb'] # 策略生成规则 if days_inactive > 180 and size_gb > 100: policy = { 'action': '归档到OSS', 'retention_days': 730, # OSS 保留 2 年 'estimated_saving': round(size_gb * 0.68, 2), # 价差 ¥0.68/GB 'risk_level': '低' # 长期不访问的数据,归档风险低 } elif days_inactive > 90: policy = { 'action': '压缩存储(ZSTD Level 3)', 'retention_days': 365, 'estimated_saving': round(size_gb * 0.3 * 0.8, 2), 'risk_level': '低' } elif days_inactive > 30: policy = { 'action': '监控观察,进入候选清单', 'retention_days': -1, # 暂不设上限 'estimated_saving': 0, 'risk_level': '中' } else: policy = { 'action': '保持不变', 'retention_days': -1, 'estimated_saving': 0, 'risk_level': '-' } policy['table_name'] = f"{row['schema']}.{row['table_name']}" policies.append(policy) policy_df = pd.DataFrame(policies) total_saving = policy_df['estimated_saving'].sum() print(f"预计总节省存储成本: ¥{total_saving:,.0f}/月") return policy_df四、第三、四阶段:计算优化与治理常态化
4.1 计算任务优化
- 任务合并:把 3 个凌晨跑的日报表合并成一个大任务,减少 ECS 调度次数
- SQL 调优:发现 2 个核心查询没有走索引,加了之后耗时从 40 分钟降到 8 分钟
- 数据裁剪下沉:在 ODS 层就过滤掉无用的系统日志字段,减少数据量 30%
- 空跑任务清理:发现 12 个任务已经没人关注结果了,直接在调度系统上下线
4.2 成本治理常态化
成本优化不是一次性的,得有持续机制:
成本看板的核心指标设计:
| 指标 | 说明 | 告警阈值 |
|---|---|---|
| 总消费 | 当月累计费用 | 超出预算 80% 告警 |
| 日环比 | 与前一日费用对比 | 波动 > 20% 告警 |
| 周同比 | 与上周同日对比 | 波动 > 30% 告警 |
| 资源利用率 | CPU/内存均值 | 持续 < 20% 告警 |
| 存储增长率 | 每日存储增量 | 增长 > 5% 告警 |
五、总结
从 3.2 万到 0.85 万,核心逻辑不是"砍预算",而是消除浪费:
- 账单拆解是第一步,不知道钱花在哪就无从优化
- 资源优化见效最快:缩容低负载节点、降配过度配置的服务,一周就能出效果
- 存储是慢性成本:不设生命周期的话,数据只增不减,成本线性增长
- 计算优化要排优先级:先优化成本最高、跑得最慢的几个任务
- 成本治理要常态化:一次优化是止痛片,持续监控才是健康的生活方式
数据成本优化没有银弹,但有通用方法论:盘点 → 分析 → 优化 → 监控 → 复盘,循环迭代。你们的月数据成本大概多少?有没有做过类似的优化?欢迎评论区交流省钱妙招~
最后提醒一点:这个方案在上生产之前建议先用灰度流量验证一周,确认资源消耗在预期范围内再全量推送。我们在实际项目中因为跳过了这步,有一次把缓存集群打挂了,教训深刻。