未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用范围与待维护事项
本文面向具备SQL、分布式执行计划及集群运维基础的读者,不是可直接运行的部署脚本。需按实际Greenplum/PXF版本确认默认分布、压缩、资源管理、外部服务配置和镜像条件。SQL含省略号、重复建表及缺失status字段;联邦查询按user_id连接可能重复计数,应先明确粒度。压缩5-10倍、性能10-50倍及切换1-3分钟均为原稿未核实示例,不作选型或SLA依据;恢复命令须经备份、演练和维护窗口审批。
warning 关联材料边界
以下原引用目标缺失或无法唯一确认,已取消跳转并保留提及:10-Greenplum性能调优实战;11-Greenplum数仓建模实践;Apache Hive;Apache Spark。相关素材/延伸说明暂不可用,不能据此声称已经提供配套材料。
Greenplum架构原理与核心特性深度解析
abstract 学习目标
- 深入理解MPP架构的分布式查询执行原理,而不只是知道"多节点并行"
- 掌握分布键的选择策略——这是Greenplum最重要的设计决策
- 理解行存/列存/AO表的底层差异与适用场景
- 建立Greenplum与Hive/Spark SQL的场景定位清晰认知
- 了解PXF外部表的数据联邦查询能力
quote 核心洞察
Greenplum的本质是:将一个大查询切割成若干并行子任务,分发到N个节点同时执行,再汇总结果。这听起来简单,但背后的核心挑战是:数据分布决定了并行的效率。如果数据分布不均(倾斜),所有节点都要等最慢的那个——并行变成了串行。理解这一点,你就理解了分布键选择为什么是Greenplum最关键的设计决策。
MPP架构:分布式查询执行的内核
什么是Shared Nothing架构
Greenplum采用Shared Nothing MPP架构:每个Segment节点拥有独立的CPU、内存和磁盘,节点之间不共享存储,通过高速网络(Interconnect)交换数据。
这与以下架构形成对比:
- Shared Everything(传统SMP):所有CPU共享内存和磁盘,扩展上限受限
- Shared Disk(Oracle RAC):多节点共享存储,存储层成为瓶颈
- Shared Nothing(Greenplum/Redshift等):具备横向扩展能力;仅在负载可充分并行、数据均衡且网络等资源不构成瓶颈时,增加节点才可能接近线性提升性能
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
graph TD
classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
Client["客户端"] --> Master["Master节点<br/>查询解析+执行计划<br/>汇总结果"]
Master <-->|"Interconnect<br/>UDP/TCP"| S1["Segment 0<br/>数据分片0<br/>独立CPU/内存/磁盘"]
Master <-->|"Interconnect"| S2["Segment 1<br/>数据分片1<br/>独立CPU/内存/磁盘"]
Master <-->|"Interconnect"| S3["Segment 2<br/>数据分片2<br/>独立CPU/内存/磁盘"]
Master <-->|"Interconnect"| S4["Segment N<br/>数据分片N<br/>独立CPU/内存/磁盘"]
S1 <-->|"数据重分布<br/>Motion"| S2
S2 <-->|"数据重分布<br/>Motion"| S3
S3 <-->|"数据重分布<br/>Motion"| S4
class Client,Master,S1,S2,S3,S4 default
一条SQL的完整执行流程
以一条聚合查询为例,展示Greenplum如何分布式执行:
SELECT city, SUM(amount) AS total
FROM orders
WHERE status = 1
GROUP BY city
ORDER BY total DESC
LIMIT 10;
执行流程:
- Master接收SQL:解析器生成查询树,由所选优化器(如ORCA)基于统计信息和代价估算生成分布式执行计划,不保证全局最优
- 生成Slice(切片):执行计划按Motion边界划分为多个Slice;各Slice依执行计划在协调节点或相应Segment执行,并非都在所有Segment上执行
- Segment并行执行:每个Segment在本地数据上执行过滤(WHERE status=1)和本地聚合(GROUP BY city的本地SUM)
- Motion(数据重分布):需要汇总跨Segment的city数据时,通过Redistribute Motion将相同city的数据发送到同一个Segment
- 最终聚合:各Segment对接收到的数据完成最终SUM聚合
- Gather Motion:所有Segment的结果汇聚到Master,Master做最终排序和LIMIT
note Motion的代价
Motion(数据网络传输)是Greenplum最主要的性能代价。好的分布键设计可以让关联操作在本地完成(Collocated Join),避免大量数据跨网络传输。这就是为什么分布键的选择如此关键。
Motion的三种类型
| Motion类型 | 触发场景 | 代价 |
|---|---|---|
| Redistribute Motion | 按某列的哈希值重新分布数据 | 中等,数据量决定代价 |
| Broadcast Motion | 将一个表的全量数据广播到所有Segment | 高,仅适合小表 |
| Gather Motion | 将所有Segment结果汇聚到Master | 低,结果集通常小 |
数据分布机制:分布键的选择
三种数据分布策略
-- 1. 哈希分布(此处显式指定;默认策略受版本、配置和建表方式影响)
CREATE TABLE orders (
order_id BIGINT,
user_id INT,
amount DECIMAL(10,2)
) DISTRIBUTED BY (user_id); -- user_id的哈希值决定存储在哪个Segment
-- 2. 随机分布(Round Robin)
CREATE TABLE temp_result (
col1 INT, col2 TEXT
) DISTRIBUTED RANDOMLY; -- 数据均匀轮询分配,无法做Collocated Join
-- 3. 复制分布(每个Segment都有完整副本,6.x新增)
CREATE TABLE dim_calendar (
date_key DATE, year INT, month INT
) DISTRIBUTED REPLICATED; -- 适合小维度表
分布键选择:最重要的设计决策
核心原则:兼顾数据均衡与主要JOIN模式选择分布键;两表分布策略、键类型及连接条件兼容时,可使相关数据落在同一Segment,减少连接重分布,但不保证整个查询没有Motion。
原则1:选择高基数列
-- 错误示例:分布键选择性太低
CREATE TABLE orders (...) DISTRIBUTED BY (status);
-- status只有0/1/2,大量行落在同一个Segment,严重倾斜
-- 正确:高基数列
CREATE TABLE orders (...) DISTRIBUTED BY (order_id); -- 或user_id
原则2:与最频繁的JOIN对齐
-- orders表和order_items表经常JOIN
-- 让两表按相同分布键分布,JOIN可在本地完成(Collocated Join)
CREATE TABLE orders (
order_id BIGINT, user_id INT, amount DECIMAL
) DISTRIBUTED BY (order_id);
CREATE TABLE order_items (
item_id BIGINT, order_id BIGINT, product_id INT, quantity INT
) DISTRIBUTED BY (order_id); -- 与orders表相同分布键
-- 此JOIN可在本地完成(Collocated Join);最终结果汇总仍可能需要Gather Motion
SELECT o.order_id, SUM(i.quantity)
FROM orders o
JOIN order_items i ON o.order_id = i.order_id
GROUP BY o.order_id;
原则3:避免NULL值多的列
NULL值在哈希分布中通常映射到同一个Segment,造成倾斜。
原则4:复合分布键的使用
-- 当单列无法均匀分布时,使用复合分布键
CREATE TABLE user_behavior (
user_id INT, event_date DATE, event_type VARCHAR(50)
) DISTRIBUTED BY (user_id, event_date);
-- 注意:复合分布键的JOIN条件需要两列都相同才能Collocated
验证数据分布是否均匀
-- 检查各Segment的数据分布(倾斜检测)
SELECT gp_segment_id, COUNT(*) AS row_count
FROM orders
GROUP BY gp_segment_id
ORDER BY row_count DESC;
-- 原稿未核实的教学参考:各Segment行数相差不超过10%
-- 原稿未核实的教学参考:最大值/最小值 > 2;非通用判定标准
-- 无行的Segment不会出现在上述结果中,需结合Segment清单补零检查
-- 也可以用内置函数检测存储倾斜
SELECT * FROM gp_toolkit.gp_skew_coefficients
WHERE skcrelname = 'orders';
-- 视图系数字段为skccoeff;原稿的 > 1.5 为未核实教学阈值,不能直接作为显著倾斜的通用判据
存储格式:行存/列存/AO表
堆表(Heap Table)
默认存储格式,与PostgreSQL相同,支持UPDATE/DELETE,适合写入频繁的表。
CREATE TABLE hot_data (...); -- 默认堆表
AO表(Append-Optimized Table)
针对批量写入和分析查询优化,仍支持MVCC可见性语义,但实现不同于堆表;通过辅助可见性映射记录删除状态,数据写入及日志机制也不同于堆表,具体开销取决于版本和工作负载。
-- 行存AO表:适合宽表(列多但每次查询需要大部分列)
CREATE TABLE fact_orders (...) WITH (appendoptimized=true, compresstype=zstd, compresslevel=1);
-- 列存AO表:适合分析查询(通常只访问少数几列)
CREATE TABLE fact_orders_col (...) WITH (appendoptimized=true, orientation=column,
compresstype=zstd, compresslevel=1);
行存 vs 列存的深度对比
行存储(每行数据物理连续存储):
物理存储:[row1_col1|row1_col2|...|row1_colN] [row2_col1|row2_col2|...|row2_colN]
适合查询:SELECT * FROM t WHERE id = 1 (取整行,一次I/O)
不适合: SELECT AVG(amount) FROM t (只需amount列,却读了全部列)
列存储(同一列的数据物理连续存储):
物理存储:[col1_row1|col1_row2|...|col1_rowN] [col2_row1|col2_row2|...|col2_rowN]
适合查询:SELECT AVG(amount), SUM(quantity) FROM t (只读两列,I/O大幅减少)
另一优势:同列数据类型相同,通常有利于压缩,可降低存储成本和I/O;5-10倍为原稿未核实示例,实际压缩比取决于数据分布和压缩算法
不适合: 频繁UPDATE/DELETE操作(AO通常通过删除可见性标记及更新时追加新数据实现,可能增加回收与维护开销)
| 场景 | 推荐格式 |
|---|---|
| 宽表 + 需要查询大部分列 | 行存AO |
| 分析查询 + 只访问少数列 + 大数据量 | 列存AO(主流选择) |
| 有频繁UPDATE/DELETE | 堆表 |
| 维度表(小表,频繁访问) | 堆表 或 REPLICATED |
-- 原稿未核实的教学性能示例(百亿行表,只查2列;未提供实测环境)
-- 列存:扫描数据量 ≈ 全表 × (2/总列数) × 压缩率
-- 行存:扫描全表数据
-- 原稿示例称宽表(50+列)场景下性能差距可达10-50倍;该数字未经核实,不作为性能保证
Greenplum vs Hive/Spark SQL:场景定位
核心差异
| 对比维度 | Greenplum | Hive | Spark SQL |
|---|---|---|---|
| 计算模型 | MPP(进程级并行) | MR/Tez(DAG批处理) | DAG内存计算 |
| 数据位置 | 数据存在本地磁盘 | 数据在HDFS | 数据在HDFS/对象存储 |
| 查询延迟 | 秒级(OLAP首选) | 分钟级 | 秒到分钟级 |
| 并发查询 | 支持(资源队列控制) | 较差 | 中等 |
| 数据规模 | 十TB到百TB | PB级 | PB级 |
| 事务支持 | 支持事务,分布式约束需按版本确认 | 原生支持ACID事务表,受版本、表类型和配置限制,并非必须使用Hudi/Iceberg | Spark SQL本身不提供通用数据库事务;表级事务能力取决于数据源、表格式及集成方式 |
| 适用场景 | 交互式查询、BI报表 | 离线批量计算、ETL | 机器学习、实时+批量统一 |
Greenplum最擅长的场景
- 复杂SQL分析查询:适合多表JOIN + GROUP BY + 窗口函数;能否秒级返回取决于数据量、执行计划、硬件和并发负载
- 高并发BI报表:多个用户同时查询,通过资源队列隔离
- 联邦分析查询:通过PXF连接Hadoop,在同一SQL中查询Greenplum本地数据和HDFS外部数据;这不等同于具备HTAP事务与分析混合负载能力
Greenplum不擅长的场景
- 流数据处理(选Flink/Kafka Streams)
- 机器学习特征工程(选Spark)
- 超大规模(PB级)非结构化数据处理(选Hive/Spark)
- 频繁的点更新(选MySQL/PostgreSQL)
PXF:数据联邦查询
PXF(Platform Extension Framework)是Greenplum的数据联邦查询框架,允许通过外部表访问Hadoop、S3、数据库等外部数据源。
-- 创建PXF外部表,以HdfsTextSimple和CSV格式读取HDFS上的文本文件
CREATE EXTERNAL TABLE ext_hive_orders (
order_id BIGINT,
user_id INT,
amount DECIMAL(10,2),
dt DATE
)
LOCATION ('pxf://hadoop-namenode/data/orders/?PROFILE=HdfsTextSimple')
FORMAT 'CSV';
-- 或者读取S3上的Parquet文件
CREATE EXTERNAL TABLE ext_s3_logs (
event_time TIMESTAMP,
user_id INT,
action VARCHAR(50)
)
LOCATION ('pxf://my-bucket/logs/?PROFILE=s3:parquet&SERVER=s3')
FORMAT 'CUSTOM' (FORMATTER='pxfwritable_import');
-- 联邦查询:Greenplum本地数据 + S3外部数据
SELECT l.user_id, COUNT(l.event_time) AS event_count, SUM(o.amount) AS total_amount
FROM ext_s3_logs l
JOIN orders o ON l.user_id = o.user_id
WHERE l.action = 'purchase'
AND o.status = 1
GROUP BY l.user_id;
note PXF的性能特点
PXF查询的性能瓶颈通常在外部数据源的读取速度和网络带宽,而非Greenplum本身。大量数据从外部拉入Greenplum进行计算时,Predicate Pushdown(谓词下推)至关重要——让过滤条件尽可能在外部数据源侧执行,减少传输数据量。
高可用:Primary/Mirror机制
在已配置Segment镜像且镜像健康的集群中,每个Primary Segment有对应的Mirror(镜像);应通过部署策略使对应Primary和Mirror位于不同物理机。Primary故障后,集群检测故障并在条件允许时将Mirror提升为新Primary,更新相关路由。原稿所称1-3分钟为未核实的切换时间示例,实际耗时取决于检测配置、故障类型和集群状态,不构成可用性承诺。
-- 查看当前Segment状态(是否有Primary/Mirror故障)
SELECT * FROM gp_segment_configuration
ORDER BY content;
-- status: 'u' = up(正常), 'd' = down(故障)
-- role: 'p' = primary, 'm' = mirror
-- preferred_role: 最初配置的角色
warning 恢复被降级的Mirror
生产环境中Primary/Mirror状态不一致会持续积累,需要定期执行gprecoverseg恢复Mirror,并在合适时机执行gprecoverseg -r将Primary/Mirror恢复到preferred状态,避免长期运行在不平衡状态。
关联知识
- 02-Greenplum架构原理 - MPP基础架构入门
- 07-表设计与分区 - 分区表与分布键的组合设计
- 10-Greenplum性能调优实战(原引用“10-Greenplum性能调优实战”未能确定有效目标,暂不可跳转) - 基于架构理解的调优实践
- 11-Greenplum数仓建模实践(原引用“11-Greenplum数仓建模实践”未能确定有效目标,暂不可跳转) - 数仓场景下的架构应用
- Apache Hive(原引用“Apache Hive”未能确定有效目标,暂不可跳转) - Hive与Greenplum的场景对比
- Apache Spark(原引用“Apache Spark”未能确定有效目标,暂不可跳转) - Spark与Greenplum的协作模式