Hive 分区和存储桶示例

⚡ 智能摘要

分区和桶是 Apache Hive 在磁盘上拆分表数据的两种方式:分区为每个键值创建一个目录,而桶将行哈希成固定数量的文件。

  • 🗂️ 分区即目录: 分区键的每个不同值都会在 HDFS 的表文件夹下创建一个子目录。
  • ✂️ 分区剪枝: 按分区键筛选的查询只会读取匹配的目录,而不是扫描整个表。
  • ⚙️ 需要动态模式: 从 SELECT 语句加载多个分区需要将 hive.exec.dynamic.partition.mode 设置为 nonstrict。
  • 🧮 存储桶是一个文件: CLUSTERED BY 对所选列进行哈希处理,并将每一行写入固定数量的文件之一。
  • 🔍 抽样和连接: 分桶表支持高效的 TABLESAMPLE 读取和映射端桶连接,而普通表则不支持。
  • 📐 按基数选择: 按低基数列(例如州或日期)进行分区,并将高基数列(例如用户标识符)放入存储桶中。

通过实例讲解 Hive 分区和存储桶

表、分区和桶是 Hive 数据建模的组成部分。表定义了数据模式,分区将表拆分为磁盘上的多个目录,而桶则将目录中的数据拆分为固定数量的文件。

什么是分区?

Hive 分区是一种将表组织成分区的方法,它根据分区键将表划分为不同的部分。从物理层面来说,每个分区都是 HDFS 中表文件夹下的一个独立子目录,这使得 Hive 可以跳过不需要的数据。

当表包含一个或多个分区键时,分区就显得尤为重要。分区键是决定数据在表中存储方式的基本元素。分区键本身并不存储在数据文件中,它的值编码在目录名称中,因此,基于该键的查询筛选可以在读取任何行之前丢弃整个目录。这称为分区修剪。

例如: -

“客户拥有一些电子商务数据,这些数据属于印度业务,其中涵盖了印度38个邦的业务。如果我们以邦/州列作为分区键,并对这些印度数据进行分区,我们将得到38个分区,这与印度的邦/州数量(38)相等。这样,每个邦/州的数据就可以在分区表中单独查看。”

样本 Code 分区代码片段

以下六条语句在 Hive shell 中按顺序执行。每条语句都是一个独立的步骤,后面的屏幕截图显示了在运行中的集群上执行相同序列的过程。

  1. 创建表 allstates
    create table allstates(state string, District string,Enrolments string)
    
    row format delimited
    
    fields terminated by ',';
    
  2. 将数据加载到创建的表 allstates 中
    Load data local inpath '/home/hduser/Desktop/AllStates.csv' into table allstates;
  3. 分区表的创建
    create table state_part(District string,Enrolments string) PARTITIONED BY(state string);
  4. 对于分区,我们必须设置此属性
    set hive.exec.dynamic.partition.mode=nonstrict
  5. 将数据加载到分区表中
    INSERT OVERWRITE TABLE state_part PARTITION(state)
    SELECT district,enrolments,state from  allstates;
  6. 基于状态作为分区键的分区表的实际处理和形成

HDFS 存储中将有 38 个分区输出,文件名即为状态名称。我们将在此步骤中进行检查。

以下屏幕截图显示了上述代码的执行情况。

在第一个屏幕中,步骤 1 运行于 蜂巢> 提示并创建 所有州 表格包含三列,字段分隔符为逗号。

使用 Hive shell 创建包含三个分隔符列的 allstates 表

下一个屏幕涵盖步骤 2 和 3:将 AllStates.csv 文件加载到 所有州以及分区表 州/省 创建时,状态被声明为分区键。

将 AllStates.csv 加载到 allstates 表中,并创建分区表 state_part。

接下来是步骤 4 和 5。动态分区模式设置为非严格模式,INSERT OVERWRITE 语句启动一个 MapReduce 作业,其日志显示每个状态值对应加载一个分区。

MapReduce 作业输出加载每个状态值对应的一个 Hive 分区

在 HDFS 中列出仓库目录证实了步骤 6 的结果——shell 报告有 38 个项目,其中一个 州/州= 按州划分目录。

HDFS 列表显示 Hive 仓库中有 38 个 state_part 分区目录。

从上面的代码中,我们做以下事情

  1. 创建名为 allstates 的表,包含三列,列名分别为 state、district 和 enrolments。
  2. 将数据加载到表 allstates 中
  3. 创建以状态为分区键的分区表
  4. 在此步骤中,将分区模式设置为非严格模式(此模式将激活动态分区模式)。
  5. 将数据加载到分区表 state_part 中
  6. 基于状态作为分区键的分区表的实际处理和形成
  7. HDFS 存储中将有 38 个分区输出,文件名作为状态名称。在此步骤中,我们将看到 HDFS 中的这 38 个分区输出。

Hive中的静态分区与动态分区

上面的示例使用了动态分区,但 Hive 支持两种加载方式,两者的区别决定了加载多少数据。ping ——以及每批货物所携带的风险。

静态分区 语句本身指定了分区值,因此必须在加载运行之前知道该值,并且一条语句只能填充一个分区。 动态分区 让 Hive 从 SELECT 列表的最后一列读取分区值,并在运行时创建目录,这就是为什么该示例只需要一次 INSERT 操作即可生成 38 个目录。

方面 静态分区 动态分区
分区值 在“分割条款”中手工提供 运行时从 SELECT 列读取
每个语句的分区数 一个 更多来自Google的
配置 在默认严格模式下运行 需要将 hive.exec.dynamic.partition.mode 设置为 nonstrict。
加载速度 速度更快,因为无需进行数值扫描。 速度较慢,因为该作业按键对行进行分组。
最适合 小的、已知的集合,例如每日负荷 大型或未知密钥集,例如 38 个州

动态负载也有上限。Hive 限制单个作业可以创建的分区数量——默认情况下,每个 mapper 或 reducer 最多 100 个分区,整个语句最多 1000 个分区——一旦超过上限,作业就会失败。因此,对于基数非常高的键,需要提高这些限制或采用不同的设计。

什么是 Bucket?

Hive 中的存储桶用于将 Hive 表中的数据隔离到多个文件或目录中。它们用于提高查询效率,并且与分区不同,存储桶的数量在创建表时就已固定,因此不会随数据增长而增加。

  • 这些分区中的数据可以进一步划分成多个桶。
  • 该分割操作是基于我们在表中选择的特定列的哈希值进行的。
  • 存储桶在后端使用某种哈希算法来读取每条记录并将其放入不同的存储桶中。
  • 一行最终落入哪个桶是由……决定的 hash_function(bucketing_column) mod num_buckets因此,相等的值总是保存在同一个文件中。
  • 在 Hive 0.x 和 1.x 版本中,必须启用分桶功能。 设置 hive.enforce.bucketing=true; 插入前

最后那个设置是当前集群上的历史设置: Apache Hive 手册 需要注意的是,从 Hive 2.x 开始就不需要这样做了,因为引擎现在会自动从表定义中获取 reducer 计数和 cluster-by 列。

步骤1) 如下所示创建Bucket。

下图显示了 CREATE TABLE 语句 样本桶其中,底部的 CLUSTERED BY 子句用于确定桶的数量。

Hive CREATE TABLE 语句将 samplebucket 聚类成四个桶

从上面的屏幕截图

  • 我们正在创建一个名为 samplebucket 的示例存储桶,其中包含 first_name、job_id、department、salary 和 country 等列名。
  • 我们在这里创建了4个桶。
  • 数据加载完成后,它会自动将数据放入 4 个桶中。
  • 国家/地区列是聚类列,因此每个国家/地区的每一行都会写入同一个存储桶文件。

步骤2) 将数据加载到表 samplebucket

假设 Hive 系统中已经创建了“employees”表,在此步骤中,我们将看到数据从 employees 表加载到 samplebucket 表中。

在开始将员工数据移入存储桶之前,请确保数据包含诸如 first_name、job_id、department、salary 和 country 之类的列名。

这里我们将员工表中的数据加载到 samplebucket 中——下面的屏幕显示了执行复制操作的 INSERT OVERWRITE 语句。

INSERT OVERWRITE 语句将员工行复制到 samplebucket 表中

步骤3) 显示步骤 1 中创建的 4 个桶

列出 HDFS 中的表目录显示了实际结果:四个编号的数据文件,而不是一个。

HDFS 列出了 samplebucket 目录下创建的四个存储桶文件。

从上面的截图中我们可以看到,员工表中的数据被转移到了步骤 1 中创建的 4 个桶中。

Hive分区与分桶:主要区别

这两个功能都能将表格分割成更小的部分,但它们在文件系统的不同层级上进行操作,并且解决的问题也不同。下表对它们进行了对比。

比较点 分区 桶装
单位已创建 每个键值对应一个目录 每个哈希桶一个文件
已声明 分区 按…聚类成 n 个桶
件数 随着不同值的数量增加而增长 在创建表时修复
存储在数据文件中的列 不——值存在于目录名称中。 是的——该列仍然是普通的列。
最佳列类型 低基数,例如州、年份或国家 高基数,例如 user_id 或 transaction_id
主要好处 分区修剪会跳过不需要的目录 甚至文件大小、低成本采样和地图边连接

两者并非竞争关系。常见的生产布局是将事实表按日期分区,然后根据连接键对每天的数据进行分组,这样查询只需将数据精简到一个目录中,然后逐个连接该目录中的数据即可。

何时使用分区、分桶或两者都用

选择哪种方式首先要考虑列的基数以及读取表的查询的结构。

  • 隔墙 当查询几乎总是基于同一个低基数列进行筛选,并且不同值的数量保持在几百而不是几百万时,就会出现问题。
  • 当有用列的不同值过多而无法作为目录时,或者当表在该列上进行重复连接或采样时,
  • 同时使用 对于大型事实表:按日期分区,然后在每个分区内按连接键进行分组。

需要警惕的故障模式是小文件问题。基于基数非常高的列(例如时间戳或客户标识符)进行分区,会生成数千个小目录,每个目录包含的文件远小于 HDFS 块大小。这会导致 NameNode 内存占用增加,每次扫描速度变慢,而分区的初衷正是为了避免这种情况。分桶可以避免这个问题,因为文件数量受到表定义的限制。

分桶机制本身也存在一个问题:只有当所有写入操作都遵循分桶布局时,布局才能正确。创建时声明的桶计数是元数据,因此,如果作业在写入表时没有正确进行分桶,则可能会留下与声明布局不匹配的文件,导致后续的采样或桶连接操作读取错误的行。

常见问题

在 Hive shell 中运行 SHOW PARTITIONS table_name 命令。它会读取元数据存储,并为每个分区目录打印一行,这比列出 HDFS 中的仓库路径更快,而且还能确认元数据存储是否同步。

ALTER TABLE table_name ADD PARTITION (state='Goa') 会添加一个新的分区,而 ALTER TABLE table_name DROP PARTITION (state='Goa') 会删除该分区。ping 受管分区会删除其数据,而删除操作则会删除分区数据。ping 外部操作只会清除元数据存储条目。

Hive 默认将每个节点的动态分区数限制为 100 个,每个语句的动态分区数限制为 1000 个。如果某个键的分区数超过此限制,则会导致作业终止。您可以提高 `hive.exec.max.dynamic.partitions.pernode` 和 `hive.exec.max.dynamic.partitions` 的值,或者选择一个更粗略的键。

不。在 Hive 0.x 和 1.x 版本中,需要强制 reducer 数量与 bucket 数量匹配。Hive-12331 在 Hive 2.0 中移除了这一机制,现在引擎会从表中获取 reducer 数量和 cluster-by 列。

TABLESAMPLE(BUCKET x OUT OF y ON column) 只读取匹配的桶文件,而不是扫描所有文件。由于行在写入时已按同一列进行哈希处理,因此该采样可重复,并且比随机行过滤器成本低得多。

将表拆分成数千个小文件会浪费 NameNode 内存,并且每个文件都会启动一个任务,从而降低扫描速度。这种情况通常发生在分区键基数非常高的情况下。使用更粗的键、分桶或文件压缩可以解决这个问题。

是的。Cloudera Workload XM 等工具中的查询日志分析和机器学习模型会根据过滤频率、倾斜度和基数对列进行排序,然后提出布局建议。但由于该建议无法查看计划的工作负载,因此仍需审核。

Copilot 可以根据注释快速生成 PARTITIONED BY 和 CLUSTERED BY 语句,而智能助手可以生成完整的加载脚本。务必将生成的桶计数和键与实际基数进行核对,因为模型仅根据名称进行猜测。

总结一下这篇文章: