• 自适应两阶段Hash Distinct速记


    本文所述技术,适用于 Hash Distinct 下压处理,Hash GroupBy 下压处理

    基础技术

    create table tbl1 (c1 int, c2 int) partition by hash(c2) partitions 3;
    select /*+ parallel(3) use_hash_distinct */ distinct c1 from tbl1
    
    • 1
    • 2

    下面讲解分布式场景下如何实现高效的 distinct 计算。

    最简单的计划是各个机器上做初步的去重,然后将数据收集到中心节点做最终的去重

    OceanBase(admin@test)>explain select /*+ parallel(3) use_hash_distinct */ distinct c1 from tbl1\G
    Query Plan: 
    ==========================================================
    |ID|OPERATOR                     |NAME    |EST. ROWS|COST|
    ----------------------------------------------------------
    |0 |HASH DISTINCT                |        |1        |46  |
    |1 | PX COORDINATOR              |        |1        |46  |
    |2 |  EXCHANGE OUT DISTR         |:EX10001|1        |46  |
    |3 |   HASH DISTINCT             |        |1        |46  |
    |4 |    PX BLOCK ITERATOR        |        |1        |46  |
    |5 |     TABLE SCAN              |tbl1    |1        |46  |
    ==========================================================
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    考虑到中心节点可能会收到大量初步去重后的数据,导致中心节点压力过大,引入两阶段 Hash Distinct 算法。

    首先,各个机器上做独立的去重,然后对去重后的结果做 HASH 重分区,分发到多台机器上做第二阶段的去重。各个机器得到的去重结果就是最终结果,汇聚到中心节点直接输出即可。

    OceanBase(admin@test)>explain select /*+ parallel(3) use_hash_distinct */ distinct c1 from tbl1\G
    Query Plan: 
    ==========================================================
    |ID|OPERATOR                     |NAME    |EST. ROWS|COST|
    ----------------------------------------------------------
    |0 |PX COORDINATOR               |        |1        |46  |
    |1 | EXCHANGE OUT DISTR          |:EX10001|1        |46  |
    |2 |  HASH DISTINCT              |        |1        |46  |
    |3 |   EXCHANGE IN DISTR         |        |1        |46  |
    |4 |    EXCHANGE OUT DISTR (HASH)|:EX10000|1        |46  |
    |5 |     HASH DISTINCT           |        |1        |46  |
    |6 |      PX BLOCK ITERATOR      |        |1        |46  |
    |7 |       TABLE SCAN            |tbl1    |1        |46  |
    ==========================================================
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    自适应技术

    上面的两种方式足以应对大部分场景。为了进一步优化一种场景场景,引入自适应技术。

    考虑场景:优化器已经生成了两阶段 Hash Distinct 算法,但各个节点上的 c1 分布非常离散,使得第一阶段 Hash Distinct 基本都是在做无用功。如果 c1 的值非常离散,理想情况下 5 号 Hash Distinct 算子应该什么都不做,直接 bypass 数据,让它去 2 号 Hash Distinct 算子里计算最终结果。

    更复杂的场景:优化器已经生成了两阶段 Hash Distinct 算法,但各个节点上的 c1 的分区再数据流上呈现出时而离散,时而紧凑的特征。这时 5 号算子应该根据数据流的特征,有时做 distinct 计算,有时直接 bypass 数据。

    对于上面的场景,都可以用自适应的技术来解决。

    1. 针对数据流中的数据建立 hash 表,并统计建立 hash 表过程中的命中率
    2. 当 hash 表的大小达到 L1 个字节时判定:
      • 如果历史命中率大于 95%,那么继续扩大 hash 表
      • 如果历史命中率小于 95%,那么废弃 hash 表
    3. 当 hash 表的大小达到 L2 个字节时判定:
      • 如果历史命中率大于 95%,那么继续扩大 hash 表
      • 如果历史命中率小于 95%,那么废弃 hash 表
    4. 当 hash 表的大小达到 L3 个字节时判定:
      • 如果历史命中率大于 95%,那么新来的行要么命中 hash 表,要么直接 bypass 到 2 号Hash Distinct 算子去
      • 如果历史命中率小于 95%,那么废弃 hash 表

    讨论

    • L1、L2、L3 的选取,可以基于 CPU 的 L1 缓存大小、L2缓存大小、L3缓存大小来决定。
      95% 这个值只是举例子,在实际的系统里可以通过测试得到。
    • 可以不是 3 层,可以是 2 层,比如 L2、L3 (因为 L1 Cache 实在太小了)
    • 很多时候,决策的最优方向是尽量快速启动 Bypass,而不是提高命中率(反常识吧?)

    其它改进

    对于高频值,在每次淘汰 Hash Table Cache 时可予以保留。详见参考文献一节。

    参考文献

    《Adaptive and Big Data Scale Parallel Execution in Oracle》 第2节 GroupBy

  • 相关阅读:
    数据脱敏的风险量化评估介绍
    QML代码生成
    Pod 健康检查和服务可用性检查
    47_ue4进阶末日生存游戏开发[基础游戏循环]
    HTML网页设计制作——响应式网页影视动漫资讯bootstrap网页(9页)
    Python中协程异步IO(asyncio)理解与入门
    深扒,Java性能调优手册:编程+多线程+JVM+设计模式+数据库+实战
    随便写一下
    第08篇:巧用Spring之Data Binding数据绑定
    05.QString字符串处理及中文乱码问题处理
  • 原文地址:https://blog.csdn.net/maray/article/details/126837719