上周帮一家月订单量过千万的电商客户排查数据看板性能问题,一个小型指标模块每天要跑 47 分钟,导致销售团队下午三点才能看到昨日的渠道转化。技术负责人第一句话是:“要不要把我们 Spark 集群再扩四个节点?” 我看了看任务的 Spark UI,发现 shuffle 阶段有 800 多个空文件,单条 SQL 里有四层不必要的子查询,事实表被反复扫描了 6 次。这不是算力不足,是计算效率太低。
数据分析算力不足,绝大多数情况下不是机器数量不够,而是计算过程里塞满了无效负担。优化计算效率,核心不是继续堆资源,而是先压掉浪费,再让每一核 CPU 都花在真正必要的数据变换上。下面是我在十几个项目中反复验证过的方法论、误区和取舍。
我的核心结论很简单:遇到算力不足,先做减法,再做调度,最后才考虑加机器。减法包括去重读、去重算、去重复存储;调度是把高峰期任务挪走或分级;加机器是最后一个杠杆,而且往往加了也不解决问题。
做过上百次算力优化后,我发现效果最明显的动作集中在三个方向:
这三者的优先级不是并列的。从成本收益看,减少扫描量通常是投入产出比最高的,因为它直接改变了 SQL 执行计划里的运算行数。下面这张图展示了我在优化项目中统计到的效率提升来源分布,可以帮你理解为什么第一步应该是数据裁剪。

如果只给你一分钟,记住一句话:在优化算力时,先花 80% 精力观察数据量和执行计划,再花 20% 精力去调资源。下面从真实场景讲起。
“算力不足”在数据分析场景里并不是一个均匀分布的问题。它通常表现为四种形态,每种形态的解决思路完全不同。
这是最常见的场景:整个集群平均 CPU 使用率 35%,但总有几个 SQL 要跑 40 分钟以上,并持续占用大量资源。因为引擎默认是公平调度,长任务和短任务抢在一个队列里,日活看板和月季度分析互相踩踏。我见过一个数据团队,为了跑一个 15 亿行的用户留存分析,每天早上九点定时触发,导致九点到十点所有 BI 报表都变慢,前端同事天天投诉。
业务方喜欢把几百个字段都存在一张表中,所有分析任务都直接扫全表。有些表其实只有 20% 的字段被高频使用,但每个查询都要读全部 300 个列的 parquet 文件。这类浪费在存储上不明显,但在计算和 IO 上非常致命。曾经有个客户一张 3TB 的表,实际单次查询用到的数据只有不到 800GB,每次跑批等于浪费了三分之二的数据读取。
很多平台采用了共享 Hadoop/Spark 集群,业务部门各自提交任务。没有资源池和优先级管理时,大任务占满队列,小任务只能排队。结果就是“算力不足”其实是“并发管理不足”。最典型的信号是:队列中 waiting 时间占总运行时间的比例超过 40%,但 CPU 使用率并不高。
这类问题最常见,也最隐蔽。我们分析一个用户的订单明细和用户维表做 JOIN,由于长尾用户(比如测试账号,或注册异常用户)占据了 90% 的订单记录,reduce 端某个节点处理了 2 亿行数据,其他 300 个节点却都在等它。任务卡了 2 小时,其实就是倾斜键拖垮了整一次计算。这种场景下,加节点不但没有用,倾斜节点反而更慢。
以上四类场景,前两类适合从 SQL 和数据模型层面优化,后两类更需要从调度、Join 策略和参数层面处理。我建议你先把慢任务归类,再选用对应的方法。下面这张表可以帮你快速判断。
| 表面现象 | 真实瓶颈 | 首选优化方向 |
|---|---|---|
| 固定报表每天跑很久 | 全表扫描或无效子查询 | 分区裁剪 + 改写SQL |
| 并发一高全线变慢 | 队列争抢、缺乏资源池 | 任务错峰 + 分级调度 |
| 任务卡住不动 | 数据倾斜或单个热键 | Join加盐 + 小表广播 |
| 跑批和查询同时崩 | IO/网络带宽耗尽 | 资源隔离 + 限流 |
这些场景有很强的共性。我建议你在做任何扩容决策之前,先花半天时间盯一次真实任务日志,区分清楚是 CPU 不够、内存不够、IO 不够,还是队列等待时间太长。否则很可能又是“扩完容发现也没快多少”的结局。

算力优化容易陷入几个反复出现的坑。很多团队花了大价钱做性能治理,结果仍然没有改观,是因为方向错了。
我在前面已经提到,很多系统瓶颈是结构性的,比如重复扫描、无效 Join、倾斜。扩容虽然能让单次执行时间和并发能力提升,但那是用成本掩盖低效。更麻烦的是,扩容后执行计划数据量没变,资源一多,CBO(基于成本的优化器)可能选择更“激进”的执行方式,反而让某些任务更慢。例如,我们曾经把一个 Spark 集群从 20 台扩到 50 台,结果某个任务因为广播超时频繁失败,最后发现根本不是节点不够,而是缺少绑定执行。
团队往往找最慢的一条 SQL 来优化。但如果平台上每天跑 2000 条任务,最慢的那条可能只占到整体运行时间的 8%。把精力放到了边际效应最低的地方,不如先看任务运行时长分布,找到那些“累计耗时最多”的任务组。我们需要追问:是任务数量多,还是单个任务慢?前者需要减少重复计算,后者需要分析执行计划。
一个任务在集群里显示 CPU 利用率 60% 以上,但整体任务是 shuffle-heavy,大量时间花在序列化和网络传输上。此时你的优化重点应该放在减少 shuffle 数据量,而不是盲目增加计算线程。
看到某些开源 OLAP 引擎强调“性能提升十倍”,就把核心链路迁移过去。迁移后却又发现数据实时性、SQL 兼容性、权限管理跟不上,甚至因为语法兼容问题导致线上事故。效率优化不是技术追新,是用最简洁的方式移除瓶颈。如果你当前场景是批量大宽表扫描,换个查询引擎可能有用;但如果是任务调度和依赖治理问题,换引擎等于换汤不换药。
有些表面上的算力不足,其实是历史数据膨胀。一张明细表过去 3 个月的数据就能满足业务需求,但系统里保留 5 年数据,且每次查询都未指定时间分区,导致每次全量扫描。这种问题,定期归档或切割分区,比任何引擎优化都便宜。
这些误区的共同点是:大家在“解决方案”上找答案,而不是在“问题定义”上找答案。算力不足听起来像资源问题,但绝大多数情况是计算效率问题。计算效率不提高,加钱买机器只会得到一台更贵更空闲的机器。

当你说“算力不足”时,我建议你按下面的路径来判断,而不是直接跳到选型或买机器。
打开真实任务日志,记录每个任务在“等待资源”“读取数据”“计算”“shuffle”“写结果”五个阶段分别消耗多少时间。如果等待资源时间占比大,说明资源管理有问题;如果读取数据时间占比大,说明数据裁剪有问题;如果 shuffle 时间占比大,说明关联逻辑有缺陷。这一步不是凭经验猜,而是看数据。
我曾经帮一家银行做批量跑批优化,发现整体耗时 40% 的阶段是写中间结果到 HDFS,再读出来的过程。那就不是 CPU 问题,而是数据落盘和序列化的问题。后来通过调整缓存策略和 parquet 压缩格式,直接节省了 30% 时间。
优化优先级依次为:
这个顺序非常重要。先做前三步,你会发现计算量可能已经降到原来的十分之一,这时再调并行度才有意义。第四步能起到的效果通常只有 20%,30%,而前两步常常是 5 倍到 20 倍。

判断一个优化动作是否值得做,看两个数字:优化涉及的 CPU/内存成本节省,以及所需投入人天。比如把 10 张报表改成复用同一个中间表,需要 3 天开发加一周测试,换来的是每天减少 3 小时计算,这个投入在大多数情况下值得;但如果你只为了优化一条每月跑一次的年度审计任务,投入 5 天人力去改写 SQL,那就可能不值得,完全可以接受它跑 40 分钟。
每次改完执行计划,都要有可对比的基准:同一数据集、同一集群配置、同一并发水平。我习惯把每个核心任务的执行时长、扫描字节数、shuffle 字节数记录到一个监控表里。任何改动后,如果扫描量上升或耗时超过基线 20%,立刻回滚。没有基线的优化,基本靠运气。
判断逻辑总结为一句话:先界定瓶颈是在 IO、CPU、内存、网络,还是调度;再按“裁剪数据→减少计算→调并发”的顺序推进;最后,永远用基线数字验证成果。
| 判断维度 | 问题 | 最终指向 |
|---|---|---|
| 资源利用率 | CPU低但任务慢 | IO/等待/倾斜 |
| 执行计划 | 扫描行数远超结果行数 | 缺少过滤和预聚合 |
| stage耗时 | 某个stage独高 | 数据倾斜或热点 |
| 任务排队 | waiting时间 > 运行时间 | 调度/资源池问题 |
下面分享三个我实际经历的项目,它们分别代表“查询优化”“数据倾斜”和“数据架构调整”三类问题。
这是一家年 GMV 约 80 亿的零售企业,使用 Hive/Spark 跑每日销售趋势报表。原始 SQL 思路很朴素:先 JOIN 订单明细、门店表和商品表,再按天聚合。碰巧这三张表都是全量快照表,只取近 30 天数据却还是扫了整年分区。我做的优化非常简单:
任务运行时间从 120 分钟缩短到 8 分钟,扫描数据量缩减了 94%。整个过程中没有扩一台机器。这说明:计算效率优化最有效的杠杆,往往是最初级的过滤条件。
另一家互金公司需要每天计算每个用户的资金流水汇总。他们的支付流水表中,约 0.1% 的“对公账户”贡献了 80% 的流水。直接按 user_id 做 GROUP BY 时,少数 reducer 承载了 80% 数据,执行时间长达 3 小时。优化办法是:
最终运行时间从 3 小时降到 40 分钟,集群峰值内存下降一半。注意,这里没有增加节点,只是把数据分布搞均匀了。

业务方经常要在 20 亿条埋点日志中按用户、页面、渠道做各种钻取,但实际很多问的是指标趋势,并不需要明细。团队最初把全部明细实时保留在 ClickHouse 中,高峰期查询并发达到 30 时,CPU 飙到 90%,慢查询频发。后来我们将明细数据按小时做预聚合,生成“访问会话汇总表”,对天级报表使用汇总数据,明细只允许近 3 天查询。这样 p95 查询时间从 55 秒降到 3 秒,同时允许更多同时查询。核心不是算得更快,而是算得更少。
这些案例透露出一个共同的规律:优化前先问“这个计算真的需要每次从明细开始吗?”绝大多数指标不需要实时从原始数据重算。预聚合、增量计算、中间结果复用,才是持续稳定提效的基础。
我统计过去两年参与过的 14 个优化项目,发现收益来源的分布大致为:
这组数字说明,绝大多数算力不足问题,核心是“读太多没用的数据”和“反复重复计算”。而市面上很多团队却把注意力放在第 4 类“调参数”上,方向错了。
算力优化没有万能药。下面按四种常见场景,给出具体的动作清单。
为了让你快速选择,我列了一张“场景 × 推荐动作”的对照表。
| 场景 | 首要动作 | 次要动作 | 反面动作 |
|---|---|---|---|
| BI报表慢 | 预聚合结果表 | 查询限流 | 增加报表服务器节点 |
| 批处理积压 | 合并重复查询 | 任务错峰 | 横向扩容 |
| 实时作业延迟 | 状态TTL拆解 | 异步维表Join | 增大并行度 |
| 特征计算昂贵 | 离线批特征 | 特征分桶 | 增加GPU |

优化算力不是无脑追求“最快”,而是要在准确性、开发成本、架构复杂度之间做取舍。我总结出四组最常见的取舍。
预聚合可以让天级指标秒开,但维度一旦需要临时拆解到省份或商品,预聚合表不覆盖,你只能回源。采样同理,用 1% 样本估算,误差可能在 2% 到 8% 之间,对于趋势型分析足够,但对于财务对账不行。我的建议是区分指标类型:趋势型指标接受采样和预聚合;对账型指标必须保留全量精确计算。
我们经常为了快速交付,让下游直接读取最底层的明细表。一次两次没问题,但时间长了每个人都这么干,底层明细表变为全公司的共享查询源,算力自然不够。建立中间表需要额外开发、存储和调度成本,却能让长期计算量大幅下降。我的原则是:同一个中间结果如果被 3 个以上任务依赖,就值得物化。

当团队既有明细查询,又有聚合报表时,有人会引入一个新 OLAP 引擎。这确实能解决当前瓶颈,但会对数据同步、权限、监控、备份提出新要求。小团队维护两套引擎很容易顾此失彼。如果一个 SQL 改写就能减少 80% 计算量,那就完全没有必要换引擎。
如果你今天需要快速交付结果,短期方案可以是用更多资源硬扛,但前提是你知道“欠下的优化债”需要在未来两周内还清。否则,硬扛的时间越长,系统越脆弱。每次性能事故背后,都是长期遗留的计算设计问题。
我们常常碰到分析师写了一个没有时间过滤条件的 count(distinct user_id) 扫描全表。限制这类查询会损伤分析自由度,不限制则算力永远不够。我的取舍是:建立“查询扫描量预算”机制,允许自由写,但超过阈值时降级或预警。这样做既不影响紧急分析,又能避免单用户拖垮平台。
核心原则:所有取舍都要放在“成本、速度、准确性、复杂度”四个维度的框架里评估。你没有的永远是免费的东西,所以必须做选择。
如果你现在正被算力不足困扰,按照下面的顺序推进,两周内就能见到明显变化。
选出 20 个最耗时的运行任务,记录它们的输入数据量、运行时长、峰值内存和 shuffle 数据量。无需手动收集,很多调度平台本身就带日志采集功能,如果没有,可以写一段脚本从 YARN 或 Spark History Server 拉取指标。没有基线,后续优化无法判断好坏。
从基线数据中找出三个可改进的迹象:一是扫描行数超过结果行数 100 倍以上的任务;二是重复读取同一张明细表的任务;三是等待资源时间占比高于 40% 的任务。把这三类挑出来,先解决它们。
针对扫描行数超标的任务,尝试增加分区过滤和列裁剪。针对重复读取,建立一个公共数据集市表。针对等待资源,给高峰期的任务设置调度延迟。这些改动通常不需要重写核心代码,改动风险较低。
再次运行任务,对比基线数据。把优化收益记录下来,向团队展示“某个任务从 100 分钟降到 10 分钟”的事实。然后把这些优化模式固化到开发规范里,让后续新任务天然避免这些坑。
你需要记住:算力优化永远是一个持续过程,而不是一次性项目。数据量在增长,业务 query 模式在变化,每周花 4 小时做一次核心任务性能检查,比每季度做一周大重构更有价值。
这也是我想强调的独特经验:真正的算力充足,不是你有一个多大的集群,而是你的每一个计算任务都尽量只处理它需要的最小数据量。把“少算”练成习惯,比把“加速器”堆成大机柜,更接近效率的真相。
下一步,打开你系统的任务监控页面,找出今天最慢的三个任务,从“减少扫描数据”这个角度试着改进一次。相信我,你很快就能看到比“加机器”更好的效果。
我跑一个几千万行的分组聚合,耗时很长,但不确定是CPU不够还是内存不足,盲目加配置又怕浪费钱,怎么定位真正的算力瓶颈?
定位瓶颈不是靠感觉,而是靠数据。我曾经在一台8核16G的虚拟机上处理2亿行订单数据,任务跑了47分钟。用top看CPU使用率只有28%,但用iostat看磁盘util高达92%,说明瓶颈在磁盘IO而不是CPU。
具体排查步骤:先用top/htop看整体负载和CPU核数,再用free -h看内存是否吃紧,用iostat -x 1看磁盘利用率和await,用vmstat看swap和上下文切换,最后用iftop或nload看网络流量。
我踩过的坑是:一开始以为CPU算力不足,直接买了更高主频的机器,结果只提升了5%。后来发现数据文件是未压缩的CSV,且每天增量读全量,加上磁盘是普通云硬盘。改成列式Parquet格式并用ZSTD压缩后,读数据量减少了70%以上,同样的机器上任务降到了12分钟。
我的经验是:数据分析慢,优先检查内存和磁盘,而不是CPU。CPU使用率高不一定算力不足,可能是代码有无效循环;CPU使用率低但慢,大概率是IO或锁等待。
我经常处理比内存大好几倍的数据集,用Pandas直接读入会内存溢出,用Dask或Modin又感觉没快多少,有没有更高效的方案?
当数据量超过内存时,第一反应不该是分布式,而是改变数据存储和计算方式。我曾处理过150GB的日志数据,机器只有32GB内存。直接read_csv直接OOM杀手,用chunksize分块后依然慢,因为每块都要重复解析日期和字符串。
我最后用PyArrow把CSV转成Parquet,按天做分区,并只保留需要的列。计算时用pyarrow.parquet的过滤谓词,只读取当天数据,再配合Polars的groupby操作,内存占用降到4GB,耗时从原来的45分钟降到6分钟。关键思路是降低数据读取量:列式存储、分区裁剪、压缩编码。
列式存储让只读取需要的列成为可能;分区裁剪避免全表扫描;压缩编码减少IO字节数。实测Parquet+ZSTD比CSV体积小约6倍,读取时间缩至1/8。如果你不想改代码,可以试试DuckDB。它直接查询Parquet/CSV,支持sql,内部自动做向量化和外部存储,在单机上也能处理远超内存的数据。
我测试过DuckDB对1亿行聚合,比Pandas快近10倍。
公司让我优化数据分析效率,有人建议上Spark集群,但搭建、运维成本很高,我们团队只有两个人,是不是用更好的单机方案就够了?
我的判断是:先别急着上分布式。分布式集群带来的网络shuffle、调度和故障恢复开销,在小数据量下反而更慢。我见过一个团队用5台机器的Spark处理100GB数据,任务跑了3小时,后来说是因为数据倾斜和大量join未优化。我把数据复制到单机,用DuckDB做同样的聚合,只用了15分钟。
是否上分布式,要看三个条件:数据量超过单机磁盘容量且无法通过分区裁剪减少;计算需求需要低延迟支持多用户并发;单机优化后仍无法达到性能目标。否则,把单机性能压榨到极限,性价比高得多。
我建议的路径是:先做存储层优化(列存、压缩、分区),再做查询层优化(谓词下推、向量化、预聚合),然后考虑增加CPU核数或内存条。这些通常能带来10倍以上的提升。只有当数据量达到TB级且需要小时级响应甚至实时响应时,再考虑Spark或类似引擎。另外,如果真要上分布式,也要避免用默认配置。
至少要设置合理的分区数、使用合适的key分布、开启广播join,才能榨出性能。
我预算有限,不能随便买新机器,有没有一些低成本甚至免费的优化手段,能让现有的数据分析任务跑得更快?
低成本优化,很多时候比加机器更有效。我曾在没有增加任何硬件的情况下,把一段数据处理脚本从3小时优化到20分钟,具体做了三件事:将CSV转成Parquet格式并启用ZSTD压缩,将Python脚本换成Polars,用ProcessPoolExecutor把多天的数据并行处理。第一,格式和压缩成本最低。
Parquet的快不是魔法,它天然支持列裁剪、索引和编码优化,把随机读变成顺序读。我测过同样的数据,CSV读取带宽约150MB/s,Parquet过滤后只需读取1/8的字节,实际速度提升接近8倍。第二,选择正确的计算库。Pandas是内存型库,不适合超大数据。
Polars底层用Rust写,重写了所有算子,支持惰性执行和查询优化,在不牺牲易用性的前提下,性能普遍是Pandas的3-10倍。我在多组groupby和join上做过对比,Polars耗时约为Pandas的1/4。第三,利用多核并行。
数据分析瓶颈往往在单核,Python的GIL会让多线程失效,但多进程可以。用concurrent.futures.ProcessPoolExecutor把任务按天分片,每进程处理一个分片,8核机器就提升了约6倍,前提是避免进程间共享大对象。最后,别忘了查询本身。
如果你的数据在数据库里,加索引、物化视图、避免select *、用近似算法(如HyperLogLog)都能大幅提速。这些都是低成本手段,值得先于硬件采购去尝试。


读者评论
文章提到的“先减后加”思路很实在,我们团队就是盲目扩容,结果成本上去了,慢查询依旧。后来发现是分区裁剪没做,改完SQL直接从40分钟降到6分钟,和文中数据吻合。
看完很有共鸣,特别是关于长尾查询和资源争抢的分析。我们的BI看板就是一到早上就卡死,一直以为是机器差,现在才明白是队列调度问题,文章提到的方法是可行的,准备试试错峰执行。
作者对误区总结得很到位,尤其是只看CPU利用率不看IO和网络这点。我们之前排查性能一上来就调并行度,后来用工具发现shuffle数据量巨大,优化完JOIN逻辑后耗时减少一半,这类实战经验比理论讲解有价值。