ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MPP数据库实战经验:性能调优、编译避坑与运维工具全解

MPP数据库实战经验:性能调优、编译避坑与运维工具全解 MPP 系列连载到第七篇的时候群里最多的提问已经从“MPP 是什么”变成了“查询又慢了怎么办”“编译怎么老报错”“这个工具到底怎么用”。这篇就把这半年里最零散也最值钱的经验打包整理一下涉及性能、注意事项、工具、编译和 FAQ 五块。每块单拎出来都能写一篇长文但实际工作中它们是揉在一起的——一个查询慢了你既要会看执行计划也要知道是不是编译安装时参数没配好还要能翻日志、上 GDB 定位是哪个 segment 进程出了问题。如果你已经对 MPP 有基础了解正在把它用于实际业务或者正准备从 Oracle/MySQL 体系迁过来又或者正在折腾 MPP 的源码编译这篇应该能帮你少走不少弯路。下面按五个主题展开全是实操里长出来的经验不是教科书。1. 性能MPP 的“快”是分布出来的也是分布毁掉的外界对 MPP 的第一印象都是“快”但只有真正用过的人才知道MPP 的快是有前提的。它的性能第一性原理不是 CPU 核数多而是“数据分布”。数据按照分布键 hash 到各个 segment 节点上理想情况下每台机器拿到的数据量和计算量是均匀的。一旦这个前提被破坏再多的节点也是白搭。1.1 分布键选错后面全白搭我见过太多用户把 MPP 当单机数据库用建表时随便指定一个分布键甚至干脆用默认随机分布结果跑起来比单机还慢。分布键的选择直接决定了 join 时要不要重分布redistribute motion也决定了每个 segment 上的数据是否均匀。选分布键的原则很简单优先选高基数字段比如订单号、用户 ID这种字段 hash 后散得开。优先选高频 join 的关联字段两个大表 join 时如果分布键一致数据不需要在网络间搬移直接在本地 segment 完成 join。避免选低基数字段比如性别、状态、省份这种取值很少的列很容易把数据压到少数几个 segment 上。如果表很小比如维度表直接用复制表replicated每个 segment 都放一份完整数据避免广播。建表之后再换分布键非常痛苦需要重写整张表所以在建表阶段就要想清楚。如果怀疑已有的表已经倾斜可以用这条 SQL 快速检查SELECT gp_segment_id, count(*) AS cnt FROM your_table GROUP BY gp_segment_id ORDER BY gp_segment_id;正常情况下每个 segment 的 count 应该差不多。如果发现某个 segment 的数据量是平均值的几倍甚至十几倍那就是典型的倾斜。我还习惯再算一个比值SELECT max(cnt)::numeric / nullif(min(cnt), 0) AS skew_ratio FROM ( SELECT gp_segment_id, count(*) AS cnt FROM your_table GROUP BY gp_segment_id ) t;skew_ratio 超过 1.2 就值得警惕了超过 1.5 基本意味着这个表的设计有问题。倾斜问题在分布式数据库里是绕不开的识别得越早代价越小。1.2 执行计划里藏着的真相在单机数据库上调优重点看索引有没有被用上、join 顺序是否合理在 MPP 里调优重点变成了执行计划里的 Motion 节点。所谓 Motion就是数据在 segment 之间流动的方式大概分两类Redistribute Motion数据按新 key 重新分布和 Broadcast Motion把小表广播给所有 segment。用 explain 看执行计划时我通常关注三个东西有没有不必要的 redistribute motion。两个表 join如果分布键相同不应该有 redistribute如果出现了说明 join 条件里的字段和建表分布键不一致。广播的是不是小表。Broadcast 一个 100 行的维度表没问题但如果是广播一个几千万行的大表执行计划基本废了。有没有“单点聚集”节点。比如 order by 最终要在 coordinator 上做归并排序数据量特别大时这个单点会成为瓶颈。实际调过几个慢查询之后你会发现大量算子对硬件性能的挑战往往不是 CPU而是网络。每一个 redistribute 动作都是一次全网 shuffle数据量一大万兆网卡都能被打满。所以 MPP 优化的核心原则就一句话能不 shuffle 就不 shuffle。这也是为什么我会反复强调分布键——它在建表那一刻就决定了未来 SQL 的命运。Oracle/MySQL 里常见的“加索引”“改 hint”“调整 join 顺序”这些手段在 MPP 里能发挥的空间小很多。这是因为 MPP 的优化器比如 GPORCA会自动选择执行计划SQL 写完了计划基本已经定了人为干预的余地不多。反而“怎么建表”“怎么设计分布键”“怎么裁剪分区”这些 DDL 阶段的工作影响远比单机数据库大。1.3 写入性能这是 OLAP别用 OLTP 姿势很多从 MySQL 迁移过来的业务方第一步就把应用原封不动搬过来还是那条链路应用一条条 INSERT每笔订单一行。结果 MPP 集群跑起来比 MySQL 还慢一度让我很头疼。原因在于 MPP 的架构——每个 INSERT 都要经过 coordinator 分发到对应 segmentsegment 还要写 WAL 日志一条条插入的事务开销是单机数据库的上百倍。MPP 天生是为批量分析设计的不是为点写设计的。正确的写入姿势是用 COPY 命令批量装载或者用 gpfdist 配合外部表并行导入几十 GB 的数据可以在几分钟内灌进去。应用层面把数据攒批比如攒够 10 万行或者 100MB 再提交一次效果立竿见影。尽量别做高频 UPDATE/DELETE。MPP 的更新逻辑是先标记旧版本再插入新版本频繁更新会快速制造表膨胀性能随之恶化。索引不是不能建但每建一个索引批量写时就要多维护一棵 B 树。分析型的查询往往全表扫描索引的存在感远低于 OLTP。记住MPP 的分析性能是拿写入灵活性换的。能用批量装载解决的问题就不要在应用层一条条插。1.4 资源管理与并发小查询被大查询饿死的真相MPP 集群最容易出现的故障不是宕机而是“看起来没死但所有查询都卡住”。最常见的原因就是没有做资源隔离一个大查询把集群所有 segment 的 CPU、内存、IO 全部占满其他小查询全部排队。老版本的资源队列Resource Queue功能比较粗只能限制并发数新版本推荐用资源组Resource Group可以细粒度地控制每个组的 CPU 份额、内存上限和并发度。我的实践经验是把业务按重要性分成几个资源组核心报表组CPU 份额最高并发适中保证重点任务不被挤。即席查询组并发限制得严一些防止有人跑大查询把集群打满。后台批处理组放在低峰期执行内存上限给足。资源配比不是一次调好的要靠监控数据反复修正。刚开始我习惯把 CPU 份额配成比例制后来发现更重要的是内存上限因为 OOM 杀进程比 CPU 排队可怕得多。执行以下语句可以看资源组的使用状态SELECT * FROM gp_toolkit.gp_resgroup_status;从这能看出每个组的并发使用量、排队语句数、CPU 使用率。如果你发现一个组里大量语句在排队说明这个组的并发配额太小如果 CPU 打满、内存见底那就是内存配额的问题。这套体系和单机数据库的差别非常大需要时间来适应。下表是我总结的 MPP 与单机数据库在调优关注点上的差异调优关注点Oracle / MySQLMPP 数据库核心瓶颈CPU、IO、锁、索引结构数据分布、网络 shuffle、Motion执行计划索引使用、join 顺序、hint分布键、广播/重分布代价写入模型小事务、高并发、行锁批量装载、COPY、外部表扩容方式垂直扩容/只读备库水平扩容、数据重分布调优时机SQL 执行阶段建表阶段就决定了大半2. 注意事项这些坑文档里不会明说MPP 用起来最大的风险不是某个功能不会而是你以为它和单机数据库差不多按单机思维去设计、运维最后在某个深夜收到告警。以下几个坑是我在真实环境里反复踩过的。2.1 连接数不是你想的那个数单机 PostgreSQL 设置 max_connections 500那就是 500 个连接。MPP 不一样一个客户端连接打到 coordinator 上coordinator 要为这个会话在每个 segment 上都启动一个后端进程。也就是说如果集群有 48 个 segment500 个并发连接就意味着集群里同时存在 500 × 48 24000 个 segment 后端进程。刚开始我完全没意识到这个问题直到有一次应用侧用长连接池把连接数打到上限集群内存直接告警coordinator 日志里全是“sorry, too many clients already”。从那以后我学乖了所有应用必须走连接池pgbouncer 或 odyssey并严格控制池里的连接数推荐值是单节点 CPU 核数的 2 到 3 倍而不是应用并发数。max_connections 参数调大之前先算算集群总进程数会不会把内存撑爆。定期清理 idle 状态的会话。写个定时任务把空闲超过 30 分钟的连接断掉能省出大量内存。这个坑是所有从单机数据库迁到 MPP 的人必踩的只能说提前知道就能少一次深夜值班。2.2 表膨胀和事务回卷PG 系的“宿命”基于 PostgreSQL 的 MPP 数据库无论商业版还是开源版都继承了 MVCC 机制。这意味着 UPDATE、DELETE 不会真正物理删除旧数据而是标记旧版本。如果不定期清理表会持续膨胀查询扫描的数据块越来越多IO 性能明显下降磁盘占用也会悄悄往上爬。我遇到过一次“io性能明显下降了”的排查磁盘 IO 等待时间高得离谱查了半天发现不是磁盘故障而是一张每天被频繁更新的维度表膨胀到了原始数据的 5 倍。从那以后我把 VACUUM 和 ANALYZE 直接写进了例行维护脚本每周固定跑一次。可以用下面这条 SQL 找出膨胀最严重的表SELECT schemaname, relname, n_live_tup, n_dead_tup, n_dead_tup::float8 / greatest(n_live_tup, 1) AS dead_ratio FROM pg_stat_user_tables WHERE n_dead_tup 10000 ORDER BY dead_ratio DESC;dead_ratio 超过 0.2 的表就该安排了。同时别忘了 ANALYZE统计信息过期比膨胀更危险它会直接让优化器选出错误的执行计划性能可能下降一个数量级。2.3 扩容缩容不是加个节点就完事MPP 的优势是水平扩展但扩容不是“加一台机器”那么简单。扩容过程中要做数据重分布也就是把已有的数据按新的分布策略重新打散到所有节点。这个过程的耗时取决于数据总量几十 TB 的集群扩容十几个小时是常态期间集群 IO 和网络压力很大业务查询会明显变慢。缩容更麻烦需要把被缩节点的数据先迁移出去再下线节点流程复杂且风险高。所以我的建议是扩容前做好容量预估尽量让集群一步到位如果一定要扩选业务低峰期执行并提前通知业务方接受性能波动。2.4 功能兼容性别把 MPP 当 Oracle/MySQL 用MPP 确实兼容标准 SQL支持窗口函数、复杂 join、分析函数性能也很强。但它在某些地方是有明确短板的点查能力弱。按主键查一行数据也要经过 coordinator 路由到对应 segment比单机数据库的主键索引慢不少。支持的唯一约束、外键约束通常很鸡肋因为全局约束检查代价极高很多 MPP 实现干脆不推荐使用。触发器、存储过程的生态远不如单机数据库丰富。高并发小事务不是它的菜。如果你的业务特征是大量小事务、点查、强约束那应该优先考虑分布式 OLTP 数据库而不是 MPP。MPP 适合的是数据量在 TB 级以上、以复杂分析查询为主的场景。产品选型阶段想清楚定位比后期调优省心一万倍。3. 工具链从 explain 到 GDB 的组合拳MPP 的日常运维离不开一套趁手的工具。许多人以为数据库运维就是打开一个图形化客户端看看表、跑跑 SQL 就行实际真正遇到性能问题、进程崩溃的时候图形界面什么忙都帮不上。我平时的排障工具链由四层组成从 SQL 层到进程层逐级递进。3.1 SQL 层诊断psql 加系统视图命令行永远是第一选择。psql 里的 \timing 打开之后每个查询的耗时一目了然是判断一个 SQL 是否异常的直观手段。执行计划分析用 explain但要注意 explain analyze 会真实执行查询生产环境跑在大表上会影响业务建议先 explain 看计划确认有把握再加 analyze。MPP 维护文档里非常实用的一组视图我几乎天天用pg_stat_activity看当前所有会话和正在执行的 SQL定位长时间运行的查询。gp_toolkit.gp_size_of_disk_data看每个节点的磁盘占用分布快速发现数据倾斜。gp_toolkit.gp_resgroup_status看资源组使用与排队情况。gp_toolkit.gp_workfile_usage看查询是否用了大量临时文件往往是内存不足或排序过大。我最常用的排查语句长这样能直接列出运行超过 5 分钟的 SQLSELECT pid, usename, state, now() - query_start AS running_time, query FROM pg_stat_activity WHERE state active AND now() - query_start interval 5 minutes AND query NOT ILIKE %pg_stat_activity% ORDER BY query_start;这套组合拳能解决 80% 的性能问题定位——剩下的 20% 需要往下一层走。3.2 图形化工具看拓扑可以调优别依赖很多团队喜欢给 DBA 配上图形化工具类似 SQLServer 的客户端或者 Navicat 这种。MPP 生态里 peer 级的图形工具也不少pgAdmin、DBeaver 都能连商业发行版还自带 Web 控制台例如 Greenplum Command Center。我的看法是图形工具适合看集群整体状态、segment 健康情况和历史监控曲线真正做性能调优时用命令行加 SQL 视图效率反而更高。因为图形工具看到的执行计划是“格式化”过的缺少很多 Motion 细节而 MPP 的调优点恰恰就在这些细节里。就跟你用 SQLServer 的图形工具看执行计划时会切换到“估计的执行计划”窗口一样最终你还是要看底层算子不是看那些花花绿绿的箭头。3.3 进程层定位GDB 是最后的底牌SQL 层解决不了问题时往往已经发生 segment 进程崩溃或卡死。这时候要看什么第一是日志第二就是 core dump。具体做法是先查 pg_log 下的 segment 日志找到崩溃进程的 PID然后看有没有生成 core 文件。有 core 文件的话用 GDB 加载分析gdb which postgres /path/to/core.pid进入 GDB 之后先执行 bt 看调用栈再执行 thread apply all bt 看所有线程的堆栈。如果 core 文件显示某个函数在等待锁那基本可以判断是死锁或资源等待。如果进程还活着但手感不对可以用 gdb -p 直接附加到进程上gdb -p 12345同样用 bt 看堆栈。需要提醒的是生产环境附加进程要非常谨慎不少公司禁止在业务高峰期执行这类操作因为 gdb 附加会短暂挂起进程。正确的做法是在测试环境复现或者把问题现场先留证等低峰期再分析。日志和 GDB 的组合是我排查疑难问题的标准流程先看日志找到方向和进程号再用 GDB 深入内部看锁和内存状态。这套方法帮我在不少案例里定位到了核心问题也让我真正理解了 MPP 内部进程的工作方式。3.4 运维脚本与生态工具效率翻倍的关键如果集群规模超过 10 台手工 SSH 上去逐台检查就不现实了。我建议把监控和巡检脚本化、平台化基础监控用 node_exporter 加 Prometheus数据库指标用 postgres_exporter再通过 Grafana 展示。重点监控项包括segment 数据偏差、磁盘空间、连接数、CPU、内存、网络吞吐。例行巡检脚本用 shell 写一个循环逐节点执行磁盘、会话、日志检查输出摘要。大数据生态集成方面MPP 通常支持通过外部表访问 HDFS 数据实现与 Hadoop 体系的联动这就不多展开了。说到“工具类”这个话题我在团队里一直强调一句话工具是拿来用的不是拿来看的。引入一个图形控制台、一套监控系统最终目的都是缩短排查时间。如果你的工具链不能让一个新手在 10 分钟内定位到问题方向那这套工具就是摆设。4. 源码编译从 tar 包到可运行集群的完整记录编译 MPP 源码这件事很多使用者会觉得没必要——直接用官方安装包不好吗我的观点是自己完整编译一遍收益远超想象。编译过程能让你理解 MPP 的目录结构、依赖关系、构建选项对最终行为的影响排错时多了一层直觉。而且某些高级特性只有在编译时显式打开才生效直接装二进制包往往享受不到。这一节以基于 PostgreSQL 的 MPP 为例我自己的环境是 Ubuntu 服务器源码从 GitHub 拉取。4.1 环境准备依赖、内存、磁盘编译 MPP 是个体力活对机器有三点硬性要求内存至少 8GB低于这个数在 make 阶段很容易触发 OOMcc1 进程被内核杀掉报错信息看着像编译器 bug其实是内存不足。磁盘剩余空间至少 20GB源码树、中间产物和安装目录加起来挺占地方。依赖包必须装齐。Ubuntu 上一般需要这组sudo apt-get install build-essential bison flex libreadline-dev zlib1g-dev libssl-dev libxml2-dev libkrb5-dev如果你没有 root 权限只能在用户目录下编译也可以就是需要先把依赖装到自己的目录里。读一下 readline 或者 zlib 的源码包按老套路装./configure --prefix$HOME/.local make -j4 make install然后导出环境变量让后续的 configure 能找到这些库export PATH$HOME/.local/bin:$PATH export LD_LIBRARY_PATH$HOME/.local/lib:$LD_LIBRARY_PATH export CPPFLAGS-I$HOME/.local/include export LDFLAGS-L$HOME/.local/lib这个问题在官方编译文档里通常只有一句话实际上坑过不少人。如果你照着网上的教程在无 sudo 的机器上编十有八九是这步没做对。4.2 编译步骤与关键参数以 Greenplum 系 MPP 为例编译流程大致是git clone https://github.com/greenplum-db/gpdb.git cd gpdb ./configure --prefix/usr/local/gpdb --with-python --with-perl --with-libxml make -j8 make install source /usr/local/gpdb/greenplum_path.shconfigure 这一步值得仔细看。--prefix 指定安装目录--with-python 和 --with-perl 开启 PL/Python、PL/Perl 过程语言支持--with-libxml 开启 XML 相关功能。需要什么功能在编译前就要想清楚编译完了再改就要全部重来。make 的并行度控制也有讲究。我见过有人直接 make -j32结果内存吃满机器直接卡死。稳妥的做法是根据内存调整8GB 内存用 -j416GB 用 -j832GB 以上再考虑 -j16。并行度不是越高越好尤其在这个编译过程会触发大量 C 编译器的场景下内存才是真正的瓶颈。编译日志一定要保留。不要用 nohup 把输出丢弃正确做法是make -j8 build.log 21 tail -f build.log编译失败后先看 build.log 最后几百行绝大多数问题在日志里都有明确线索比到处搜报错快。4.3 编译报错实录三个典型问题这些年编译 MPP 遇到的报错归根结底就是三类说不上多高级但每一个都让我折腾了好几个小时。第一类互相冲突的版本或找不到头文件。configure 阶段报错最常见类似 configure: error: readline library not found。这基本就是依赖没装或者路径不对。装好 libreadline-dev 之后重新 configure 即可无 sudo 场景就检查 CPPFLAGS/LDFLAGS 有没有导出到位。第二类编译过程中 cc1 进程被 OOM killer 杀掉。报错通常是 gcc: internal compiler error: Killed出现这个先别怀疑编译器坏了去查 /var/log/syslog十有八九是内存不够。解决方法是降低 -j 并行度或者临时加 swap同时关掉网页浏览器、IDE 这些吃内存的程序。第三类编译速度异常慢。我遇到过 Linux 服务器上的文件审计服务在后台监听整个源码目录导致每次读写文件都要过一道实时扫描make 的速度慢到令人发指。从 Windows 上玩 ESP32 编译的经验也能看到类似问题安全软件的实时防护对小文件密集的编译过程伤害极大。解决方式是让编译目录加入扫描白名单或者干脆把编译放到隔离环境执行。Windows 上类似 Defender 实时保护拖慢 IDE 的说法也很常见本质是同一回事。另外强烈建议在编译环境中安装 ccache。第一次全量编译不吃亏关键是后续改一行源码重新编译时ccache 能命中大量缓存把增量编译从几分钟压缩到十几秒。这在调试自己的改动时幸福感极高。4.4 编译完成后的部署验证安装完成后先执行 greenplum_path.sh 或设置好 PATH然后验证版本SELECT version();如果是全新集群还需要运行 gpinitsystem 之类初始化命令把 segment 实例创建出来。这一步之后建议做一轮冒烟测试建库、建表、插入几行、跑一个带 join 的查询、最后 explain 看执行计划是否正常。千万别直接跨过验证步骤就上生产配置我有一次就是编译完直接配集群结果 segment 起不来排查了半天是个依赖库版本不对。如果集群规模大、需要批量部署建议把编译产物打包做成 rpm/deb 或者标准的 tar 包内网分发到各机器。避免每台机器都现场编译一遍既慢又容易出现环境差异。5. FAQ团队群里问得最多的十个问题最后把日常答疑里最高频的问题集中列一下每个问题都附上我的排查思路和结论。这些问题看着基础但背后都是真实翻车现场。5.1 性能类Q为什么 count(*) 这么慢因为 MPP 没有“元数据级 count”优化count 必须真实扫描所有 segment 上的数据再汇总到 coordinator。数据量越大越慢是正常的。优化思路是维护一张预聚合的计数表或者在允许误差的场景用采样估算。Q某个查询以前快今天突然变慢怎么定位按顺序排查先看统计信息是否过期跑一次 ANALYZE很多时候问题立刻消失再看执行计划是否变化重点是有没有出现新的 redistribute motion然后看资源组排队情况是不是被大查询堵住最后检查表膨胀。经验顺序是先统计信息、后执行计划、再资源、最后表结构。QIO 性能明显下降了怎么排查先用 iostat 和 vmstat 看是所有节点都慢还是个别节点慢。如果是某个节点慢检查磁盘是不是出了问题、数据是否倾斜、表是否膨胀。很多时候所谓“IO 性能下降”其实是数据分布不均导致某些节点过载不是磁盘本身的问题。Q大表 join 大表怎么调优第一优先保证两表分布键一致这样 join 完全在本地完成不产生重分布。第二通过过滤条件尽量下推减少参与 join 的数据量。第三检查执行计划里有没有不必要的 motion。如果小表足够小可以设置成复制表或者接受广播 join但要确认广播代价可控。5.2 编译部署类Q编译时内存不够怎么办先降低 -j 并行度比加 swap 管用。如果还不行再加 swap 或者找一台内存更大的编译机器。不要在编译时同时跑别的重型任务。Q可以直接下载编译好的安装包吗商业发行版一般有官方预编译包开源项目有时也有 CI 构建产物。能用就用省时间。但如果你要跑在特殊环境或者需要自定义编译参数就得自己编。就像某些 GIS 库会有已经编译好的 Windows 版本下载省心但受限于默认配置两回事。Q编译出来的二进制拷到另一台机器上报错 GLIBC 版本不对这是因为编译机的 glibc 比运行机新动态链接时在旧机器上找不到高版本符号。解决办法是在目标环境或与目标环境 glibc 版本一致的机器上编译不要相信“编译一次到处运行”那套那是 Java 才有的待遇。Q扩容之后数据分布不均怎么办检查扩容的第二个阶段有没有完成。有些 MPP 的扩容分两阶段第一阶段只把新节点接入集群第二阶段才会按新分布键重分布数据第二阶段没跑完之前新节点数据量很少旧节点依然超载。执行重分布任务并确认完成即可。5.3 使用与运维类Q小表 join 大表为什么还是慢看执行计划里小表是被广播还是被重分布。如果小表默认分布键和 join 键不一致它可能没有被广播而是老老实实重分布了一整张表这个代价可能比广播还大。直接把它建为复制表通常能解决。Q连接数被占满怎么办先看 pg_stat_activity 里是不是一堆 idle 连接把空闲连接清掉。然后查应用侧连接池配置确认池大小是否合理。最后才是调大 max_connections每次调大都要重新评估集群内存因为连接数放大效应在 MPP 里非常致命。QVACUUM 执行时提示表被锁或者卡住了典型原因是某些长事务一直持有快照不释放VACUUM 只能跳过那些被事务访问过的数据。先去 pg_stat_activity 找长时间运行的事务跟业务方确认后 kill 掉再重新 VACUUM。另外VACUUM 要在低峰期跑运行时也会占用 IO别把它和业务高峰叠在一起。写到最后想起一个很深的教训我第一次编译 MPP 时面对报错的第一反应是到处搜后来发现大部分问题就是依赖没装齐或者并行度太高内存撑不住。安静下来把 configure 的输出读一遍比搜索引擎高效得多。这个系列也一样很多答案不在文档里而在你自己的环境里。碰到问题别急着换工具、换产品先从分布、执行计划、资源、日志这几层按顺序查一遍大多数问题都会浮出水面。
返回列表