【发布时间】:2018-12-07 06:48:54
【问题描述】:
我有一个查询,我在 thrift 中运行了很长时间。我在一个有 500k 行的表的单个分区上运行它。
查询如下所示:
select col0 from <table> where partition=<partition> and <col1>=<val>
我做到了col1 != val,所以查询返回 0 行。
此查询大约需要 30 秒(如果我使用 select *,则需要一分钟)。
当我运行完全相同的查询但使用 select count(col0) 时,需要 2 秒。
什么可能导致使用 select col 而不是 select count(col) 的查询需要很长时间?
这里解释了查询
explain select col0 from table where `partition` = partition and col=val;
*项目 [col0#607]
+- *Filter (isnotnull(col1#607) && (col1#607 = aaaa))
+- *FileScan parquet
table[col1#607,partition#611]
批处理:真,
格式:镶木地板,
位置:PrunedInMemoryFileIndex[...,
分区数:23,
分区过滤器:[isnotnull(partition#611),
(cast(partition#611 as int) = partition_name)],
PushedFilters:[IsNotNull(col1),
EqualTo(col1,aaaa)],
读取架构:结构
explain select count(col0) from table where `partition` = partition and col=val;
*HashAggregate(keys=[], functions=[count(col0#625)])
+- 交换 SinglePartition
+- *HashAggregate(keys=[], functions=[partial_count(col0#625)])
+- *项目 [col0#625]
+- *Filter (isnotnull(col1#625) && (col1#625 = aaaa))
+- *FileScan parquet
table[col1#625,partition#629] 批处理:真,
格式:镶木地板,
位置:PrunedInMemoryFileIndex[...,
分区数:23,
分区过滤器:[isnotnull(partition#629),
(cast(partition#629 as int) = partition_name)],
PushedFilters:[IsNotNull(col1),
EqualTo(col1,aaaa)],
读取架构:结构
据我所知,过程完全相同,只是count查询的步骤更多。那么它为什么要快 15 倍呢?
编辑:
我在日志中发现了这个有趣的块:
有计数:
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 0.0(TID 8092,ip-123456,执行程序 36,分区 0,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 1.0(TID 8093,ip-123456,执行程序 35,分区 1,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 2.0(TID 8094,ip-123456,执行程序 36,分区 2,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 3.0(TID 8095,ip-123456,执行程序 35,分区 3,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 4.0(TID 8096,ip-123456,执行程序 36,分区 4,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 5.0(TID 8097,ip-123456,执行程序 35,分区 5,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 6.0(TID 8098,ip-123456,执行程序 36,分区 6,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 7.0(TID 8099,ip-123456,执行程序 35,分区 7,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager:在阶段 2509.0 启动任务 8.0(TID 8100,ip-123456,执行程序 36,分区 8,RACK_LOCAL,5521 字节)
18/06/28 11:42:55 INFO TaskSetManager: 在阶段 2509.0 启动任务 9.0 (TID 8101, ip-123456, executor 35, partition 9, RACK_LOCAL, 5521 bytes)
- 没有:*
18/06/28 11:45:32 INFO TaskSetManager:启动任务 0.0 在阶段 2512.0(TID 8136,ip-10-117-49-97.eu-west-1.compute .internal, executor 37, partition 1, RACK_LOCAL, 5532 bytes)
18/06/28 11:45:32 INFO BlockManagerInfo:在内存中添加了广播_2352_piece0 ip-10-117-49-97.eu-west-1.compute.internal:40489(大小:12.6 KB,免费:11.6 GB)
28 年 6 月 18 日 11:45:32 信息 TaskSetManager:在 ip-10-117-49-97.eu-west-1.compute.internal(执行程序 37)上的 667 毫秒内完成阶段 2512.0(TID 8136)中的任务 0.0 (1/1)
28 年 6 月 18 日 11:45:32 信息 YarnScheduler:从池中删除了任务已全部完成的 TaskSet 2512.0
28 年 6 月 18 日 11:45:32 信息 DAGScheduler:ResultStage 2512(OperationManager.java:220 处的 getNextRowSet)在 0.668 秒内完成
28 年 6 月 18 日 11:45:32 信息 DAGScheduler:作业 2293 完成:OperationManager.java:220 处的 getNextRowSet,耗时 0.671740 秒
28 年 6 月 18 日 11:45:32 信息 SparkContext:开始工作:OperationManager.java:220 处的 getNextRowSet
28 年 6 月 18 日 11:45:32 信息 DAGScheduler:得到作业 2294 (getNextRowSet at OperationManager.java:220),具有 1 个输出分区
28 年 6 月 18 日 11:45:32 信息 DAGScheduler:最后阶段:ResultStage 2513(OperationManager.java:220 处的 getNextRowSet)
18/06/28 11:45:32 INFO DAGScheduler:最后阶段的父母:List()
28 年 6 月 18 日 11:45:32 信息 DAGScheduler:失踪父母:List()
28 年 6 月 18 日 11:45:32 信息 DAGScheduler:提交 ResultStage 2513(MapPartitionsRDD[312] 在 AccessController.java:0 处运行),它没有缺少父
18/06/28 11:45:32 INFO MemoryStore:块 broadcast_2353 存储为内存中的值(估计大小 66.6 KB,可用 12.1 GB)
18/06/28 11:45:32 INFO MemoryStore:块 broadcast_2353_piece0 以字节形式存储在内存中(估计大小 12.6 KB,空闲 12.1 GB)
28 年 6 月 18 日 11:45:32 信息 BlockManagerInfo:在 10.117.48.68:41493 的内存中添加了 broadcast_2353_piece0(大小:12.6 KB,免费:12.1 GB)
28 年 6 月 18 日 11:45:32 信息 SparkContext:从 DAGScheduler.scala:1047 的广播创建广播 2353
18/06/28 11:45:32 INFO DAGScheduler:从 ResultStage 2513 提交 1 个缺失的任务(MapPartitionsRDD[312] 在 AccessController.java:0 运行时)(前 15 个任务用于分区
Vector(2)) 18/06/28 11:45:32 INFO YarnScheduler:添加任务集 2513.0 和 1 个任务
28 年 6 月 18 日 11:45:32 信息 TaskSetManager:开始任务 0.0 在阶段 2513.0(TID 8137,ip-10-117-49-97.eu-west-1.compute.internal,执行器 37,分区 2,RACK_LOCAL,5532 字节)
18/06/28 11:45:33 INFO BlockManagerInfo:在内存中添加了广播_2353_piece0 ip-10-117-49-97.eu-west-1.compute.internal:40489(大小:12.6 KB,免费:11.6 GB)
18/06/28 11:45:38 INFO TaskSetManager:在 ip-10-117-49-97.eu-west-1.compute.internal(执行程序 37)上的 5238 毫秒内完成阶段 2513.0(TID 8137)中的任务 0.0 (1/1)
18/06/28 11:45:38 INFO YarnScheduler:从池中删除了任务已全部完成的 TaskSet 2513.0
18/06/28 11:45:38 INFO DAGScheduler: ResultStage 2513 (getNextRowSet at OperationManager.java:220) 在 5.238 秒内完成
28 年 6 月 18 日 11:45:38 信息 DAGScheduler:作业 2294 已完成:OperationManager.java:220 处的 getNextRowSet,耗时 5.242084 秒
28 年 6 月 18 日 11:45:38 信息 SparkContext:开始工作:OperationManager.java:220 处的 getNextRowSet
28 年 6 月 18 日 11:45:38 信息 DAGScheduler:得到作业 2295 (getNextRowSet at OperationManager.java:220),具有 1 个输出分区
28 年 6 月 18 日 11:45:38 信息 DAGScheduler:最后阶段:ResultStage 2514(OperationManager.java:220 处的 getNextRowSet)
18/06/28 11:45:38 INFO DAGScheduler:最后阶段的父母:List()
28 年 6 月 18 日 11:45:38 信息 DAGScheduler:失踪父母:List()
28 年 6 月 18 日 11:45:38 信息 DAGScheduler:提交 ResultStage 2514(MapPartitionsRDD[312] 在 AccessController.java:0 处运行),其中没有缺少父项
18/06/28 11:45:38 INFO MemoryStore:块 broadcast_2354 存储为内存中的值(估计大小 66.6 KB,可用 12.1 GB)
18/06/28 11:45:38 INFO MemoryStore:块 broadcast_2354_piece0 以字节形式存储在内存中(估计大小 12.6 KB,空闲 12.1 GB)
28 年 6 月 18 日 11:45:38 信息 BlockManagerInfo:在 10.117.48.68:41493 的内存中添加了 broadcast_2354_piece0(大小:12.6 KB,免费:12.1 GB)
28 年 6 月 18 日 11:45:38 信息 SparkContext:从 DAGScheduler.scala:1047 的广播创建广播 2354
18/06/28 11:45:38 INFO DAGScheduler:从 ResultStage 2514 提交 1 个缺失的任务(MapPartitionsRDD[312] 在 AccessController.java:0 运行)(前 15 个任务用于分区 Vector(3))
(即它重复这个块,看起来它按顺序运行任务而不是像计数情况那样并行)
我也尝试过“order by”,它实际上使查询运行速度提高了 2 倍更快
使用 spark 而不是 thrift 对相同数据运行相同的查询要快得多。
我在 aws emr-5.11.1 上运行 thrift
Hive 2.3.2
火花 2.2.1
节俭 0.11.0
【问题讨论】:
-
Thrift 不运行查询。它是 Facebook 使用的 RPC 协议,Hive (开发 @Facebook) 也将其用于 Metastore API 和 JDBC 有线协议。 Hive 运行查询。
-
Spark 也将 Thrift 用于其 JDBC 有线协议(并使用相同的 JDBC 驱动程序)。 Impala 也是如此。
-
Hive 是一个 batch 查询引擎。它必须生成一个 YARN 作业,分配容器,启动 Mapper 和 Reducer 阶段(中间有很多 I/O),等待作业完成。 Spark 也是一个批处理查询引擎,但它预先分配容器(甚至不使用容器)并在内存中运行其查询。对于任何对数据处理和大数据感兴趣的人来说,所有这些都是常识。
-
为了更快的 Hive 执行:安装 TEZ 批处理框架并使用而不是 MapReduce => 查询的运行速度将提高 2-6 倍。或者安装 LLAP 服务(由 HortonWorks 开发,在 EMR 中可能不可用)及其持久容器和内存缓存网格,它可能会炸毁 Spark。
标签: apache-spark hive