Schema Evolution(模式演进)允许用户轻松更改 Hudi 表的当前模式,以适应随时间变化的数据。 从 0.11.0 版本开始,支持 Spark SQL(spark3.1.x 和 spark3.2.1)对 Schema 演进的 DDL 支持并且标志为实验性的。

场景

  • 可以添加、删除、修改和移动列(包括嵌套列)
  • 分区列不能演进
  • 不能对 Array 类型的嵌套列进行添加、删除或操作

SparkSQL模式演进以及语法描述

使用模式演进之前,请先设置spark.sql.extensions,对于spark 3.2.x,需要设置spark.sql.catalog.spark_catalog

  1. # Spark SQL for spark 3.1.x
  2. spark-sql --packages org.apache.hudi:hudi-spark3.1.2-bundle_2.12:0.11.1,org.apache.spark:spark-avro_2.12:3.1.2 \
  3. --conf 'spark.serializer=org.apache.spark.serializer.KryoSerializer' \
  4. --conf 'spark.sql.extensions=org.apache.spark.sql.hudi.HoodieSparkSessionExtension'
  5. # Spark SQL for spark 3.2.1
  6. spark-sql --packages org.apache.hudi:hudi-spark3-bundle_2.12:0.11.1,org.apache.spark:spark-avro_2.12:3.2.1 \
  7. --conf 'spark.serializer=org.apache.spark.serializer.KryoSerializer' \
  8. --conf 'spark.sql.extensions=org.apache.spark.sql.hudi.HoodieSparkSessionExtension' \
  9. --conf 'spark.sql.catalog.spark_catalog=org.apache.spark.sql.hudi.catalog.HoodieCatalog'

启动spark app后,请执行set schema.on.read.enable=true开启模式演进

当前模式演进开启后不能关闭

添加列

语法

  1. -- add columns
  2. ALTER TABLE Table name ADD COLUMNS(col_spec[, col_spec ...])

参数描述

参数 描述
tableName 表名
col_spec 列定义,由五个字段组成,col_name, col_type, nullable, comment, col_position

col_name : 新列名,强制必须存在,如果在嵌套类型中添加子列,请指定子列的全路径

示例

  • 在嵌套类型users struct<name: string, age int>中添加子列col1,设置字段为users.col1
  • 在嵌套map类型member map<string, struct<n: string, a: int>>中添加子列col1, 设置字段为member.value.col1

col_type : 新列的类型

nullable : 新列是否可为null,可为空,当前Hudi中并未使用

comment : 新列的注释,可为空

col_position : 列添加的位置,值可为FIRST或者AFTER 某字段

  • 如果设置为FIRST,那么新加的列在表的第一列
  • 如果设置为AFTER 某字段,将在某字段后添加新列
  • 如果设置为空,只有当新的子列被添加到嵌套列时,才能使用 FIRST。 不要在顶级列中使用 FIRST。 AFTER 的使用没有限制。

示例

  1. alter table h0 add columns(ext0 string);
  2. alter table h0 add columns(new_col int not null comment 'add new column' after col1);
  3. alter table complex_table add columns(col_struct.col_name string comment 'add new column to a struct col' after col_from_col_struct);

修改列

语法

  1. -- alter table ... alter column
  2. ALTER TABLE Table name ALTER [COLUMN] col_old_name TYPE column_type [COMMENT] col_comment[FIRST|AFTER] column_name

参数描述

参数 描述
tableName 表名
col_old_name 待修改的列名
column_type 新的列类型
col_comment 列comment
column_name 列名,放置目标列的新位置。 例如,AFTER column_name 表示目标列放在 column_name 之后

示例

  1. --- Changing the column type
  2. ALTER TABLE table1 ALTER COLUMN a.b.c TYPE bigint
  3. --- Altering other attributes
  4. ALTER TABLE table1 ALTER COLUMN a.b.c COMMENT 'new comment'
  5. ALTER TABLE table1 ALTER COLUMN a.b.c FIRST
  6. ALTER TABLE table1 ALTER COLUMN a.b.c AFTER x
  7. ALTER TABLE table1 ALTER COLUMN a.b.c DROP NOT NULL

列类型变更矩阵表

源列类型\目标列类型 long float double string decimal date int
int Y Y Y Y Y N Y
long Y N Y Y Y N N
float N Y Y Y Y N N
double N N Y Y Y N N
decimal N N N Y Y N N
string N N N Y Y Y N
date N N N Y N Y N

删除列

语法

  1. -- alter table ... drop columns
  2. ALTER TABLE tableName DROP COLUMN|COLUMNS cols

示例

  1. ALTER TABLE table1 DROP COLUMN a.b.c
  2. ALTER TABLE table1 DROP COLUMNS a.b.c, x, y

修改列名

语法

  1. -- alter table ... rename column
  2. ALTER TABLE tableName RENAME COLUMN old_columnName TO new_columnName

示例

  1. ALTER TABLE table1 RENAME COLUMN a.b.c TO x

修改表属性

语法

  1. -- alter table ... set|unset
  2. ALTER TABLE Table name SET|UNSET tblproperties

示例

  1. ALTER TABLE table SET TBLPROPERTIES ('table_property' = 'property_value')
  2. ALTER TABLE table UNSET TBLPROPERTIES [IF EXISTS] ('comment', 'key')

修改表名

语法

  1. -- alter table ... rename
  2. ALTER TABLE tableName RENAME TO newTableName

示例

  1. ALTER TABLE table1 RENAME TO table2

0.11.0之前的模式演进

模式演进是数据管理的一个非常重要的方面。 Hudi 支持开箱即用的常见模式演进场景,例如添加可为空的字段或提升字段的数据类型。 此外,演进后的模式可以跨引擎查询,例如 Presto、Hive 和 Spark SQL。 下表总结了与不同 Hudi 表类型兼容的Schema变更类型。

Schema变更 COW MOR 说明
在最后的根级别添加一个新的可为空列 Yes Yes Yes意味着具有演进模式的写入成功并且写入之后的读取成功读取整个数据集
向内部结构添加一个新的可为空列(最后) Yes Yes
添加具有默认值的新复杂类型字段(map和array) Yes Yes
添加新的可为空列并更改字段的顺序 No No 如果使用演进模式的写入仅更新了一些基本文件而不是全部,则写入成功但读取失败。 目前Hudi 不维护模式注册表,其中包含跨基础文件的更改历史记录。 然而如果 upsert 触及所有基本文件,则读取将成功
添加自定义可为空的 Hudi 元列,例如 _hoodie_meta_col Yes Yes
将根级别字段的数据类型从 int 提升为 long Yes Yes 对于其他类型,Hudi 支持与Avro相同 Avro schema resolution
.
将嵌套字段的数据类型从 int 提升为 long Yes Yes
对于复杂类型(map或array的值),将数据类型从 int 提升为 long Yes Yes
在最后的根级别添加一个新的不可为空的列 No No 对于Spark数据源的MOR表,写入成功但读取失败。 作为一种解决方法,您可以使该字段为空
向内部结构添加一个新的不可为空的列(最后) No No
将嵌套字段的数据类型从 long 更改为 int No No
将复杂类型的数据类型从 long 更改为 int(映射或数组的值) No No

让我们通过一个示例来演示 Hudi 中的模式演进支持。 在下面的示例中,我们将添加一个新的字符串字段并将字段的数据类型从 int 更改为 long。

  1. Welcome to
  2. ____ __
  3. / __/__ ___ _____/ /__
  4. _\ \/ _ \/ _ `/ __/ '_/
  5. /___/ .__/\_,_/_/ /_/\_\ version 3.1.2
  6. /_/
  7. Using Scala version 2.12.10 (OpenJDK 64-Bit Server VM, Java 1.8.0_292)
  8. Type in expressions to have them evaluated.
  9. Type :help for more information.
  10. scala> import org.apache.hudi.QuickstartUtils._
  11. import org.apache.hudi.QuickstartUtils._
  12. scala> import scala.collection.JavaConversions._
  13. import scala.collection.JavaConversions._
  14. scala> import org.apache.spark.sql.SaveMode._
  15. import org.apache.spark.sql.SaveMode._
  16. scala> import org.apache.hudi.DataSourceReadOptions._
  17. import org.apache.hudi.DataSourceReadOptions._
  18. scala> import org.apache.hudi.DataSourceWriteOptions._
  19. import org.apache.hudi.DataSourceWriteOptions._
  20. scala> import org.apache.hudi.config.HoodieWriteConfig._
  21. import org.apache.hudi.config.HoodieWriteConfig._
  22. scala> import org.apache.spark.sql.types._
  23. import org.apache.spark.sql.types._
  24. scala> import org.apache.spark.sql.Row
  25. import org.apache.spark.sql.Row
  26. scala> val tableName = "hudi_trips_cow"
  27. tableName: String = hudi_trips_cow
  28. scala> val basePath = "file:///tmp/hudi_trips_cow"
  29. basePath: String = file:///tmp/hudi_trips_cow
  30. scala> val schema = StructType( Array(
  31. | StructField("rowId", StringType,true),
  32. | StructField("partitionId", StringType,true),
  33. | StructField("preComb", LongType,true),
  34. | StructField("name", StringType,true),
  35. | StructField("versionId", StringType,true),
  36. | StructField("intToLong", IntegerType,true)
  37. | ))
  38. schema: org.apache.spark.sql.types.StructType = StructType(StructField(rowId,StringType,true), StructField(partitionId,StringType,true), StructField(preComb,LongType,true), StructField(name,StringType,true), StructField(versionId,StringType,true), StructField(intToLong,IntegerType,true))
  39. scala> val data1 = Seq(Row("row_1", "part_0", 0L, "bob", "v_0", 0),
  40. | Row("row_2", "part_0", 0L, "john", "v_0", 0),
  41. | Row("row_3", "part_0", 0L, "tom", "v_0", 0))
  42. data1: Seq[org.apache.spark.sql.Row] = List([row_1,part_0,0,bob,v_0,0], [row_2,part_0,0,john,v_0,0], [row_3,part_0,0,tom,v_0,0])
  43. scala> var dfFromData1 = spark.createDataFrame(data1, schema)
  44. scala> dfFromData1.write.format("hudi").
  45. | options(getQuickstartWriteConfigs).
  46. | option(PRECOMBINE_FIELD_OPT_KEY.key, "preComb").
  47. | option(RECORDKEY_FIELD_OPT_KEY.key, "rowId").
  48. | option(PARTITIONPATH_FIELD_OPT_KEY.key, "partitionId").
  49. | option("hoodie.index.type","SIMPLE").
  50. | option(TABLE_NAME.key, tableName).
  51. | mode(Overwrite).
  52. | save(basePath)
  53. scala> var tripsSnapshotDF1 = spark.read.format("hudi").load(basePath + "/*/*")
  54. tripsSnapshotDF1: org.apache.spark.sql.DataFrame = [_hoodie_commit_time: string, _hoodie_commit_seqno: string ... 9 more fields]
  55. scala> tripsSnapshotDF1.createOrReplaceTempView("hudi_trips_snapshot")
  56. scala> spark.sql("desc hudi_trips_snapshot").show()
  57. +--------------------+---------+-------+
  58. | col_name|data_type|comment|
  59. +--------------------+---------+-------+
  60. | _hoodie_commit_time| string| null|
  61. |_hoodie_commit_seqno| string| null|
  62. | _hoodie_record_key| string| null|
  63. |_hoodie_partition...| string| null|
  64. | _hoodie_file_name| string| null|
  65. | rowId| string| null|
  66. | partitionId| string| null|
  67. | preComb| bigint| null|
  68. | name| string| null|
  69. | versionId| string| null|
  70. | intToLong| int| null|
  71. +--------------------+---------+-------+
  72. scala> spark.sql("select rowId, partitionId, preComb, name, versionId, intToLong from hudi_trips_snapshot").show()
  73. +-----+-----------+-------+----+---------+---------+
  74. |rowId|partitionId|preComb|name|versionId|intToLong|
  75. +-----+-----------+-------+----+---------+---------+
  76. |row_3| part_0| 0| tom| v_0| 0|
  77. |row_2| part_0| 0|john| v_0| 0|
  78. |row_1| part_0| 0| bob| v_0| 0|
  79. +-----+-----------+-------+----+---------+---------+
  80. // In the new schema, we are going to add a String field and
  81. // change the datatype `intToLong` field from int to long.
  82. scala> val newSchema = StructType( Array(
  83. | StructField("rowId", StringType,true),
  84. | StructField("partitionId", StringType,true),
  85. | StructField("preComb", LongType,true),
  86. | StructField("name", StringType,true),
  87. | StructField("versionId", StringType,true),
  88. | StructField("intToLong", LongType,true),
  89. | StructField("newField", StringType,true)
  90. | ))
  91. newSchema: org.apache.spark.sql.types.StructType = StructType(StructField(rowId,StringType,true), StructField(partitionId,StringType,true), StructField(preComb,LongType,true), StructField(name,StringType,true), StructField(versionId,StringType,true), StructField(intToLong,LongType,true), StructField(newField,StringType,true))
  92. scala> val data2 = Seq(Row("row_2", "part_0", 5L, "john", "v_3", 3L, "newField_1"),
  93. | Row("row_5", "part_0", 5L, "maroon", "v_2", 2L, "newField_1"),
  94. | Row("row_9", "part_0", 5L, "michael", "v_2", 2L, "newField_1"))
  95. data2: Seq[org.apache.spark.sql.Row] = List([row_2,part_0,5,john,v_3,3,newField_1], [row_5,part_0,5,maroon,v_2,2,newField_1], [row_9,part_0,5,michael,v_2,2,newField_1])
  96. scala> var dfFromData2 = spark.createDataFrame(data2, newSchema)
  97. scala> dfFromData2.write.format("hudi").
  98. | options(getQuickstartWriteConfigs).
  99. | option(PRECOMBINE_FIELD_OPT_KEY.key, "preComb").
  100. | option(RECORDKEY_FIELD_OPT_KEY.key, "rowId").
  101. | option(PARTITIONPATH_FIELD_OPT_KEY.key, "partitionId").
  102. | option("hoodie.index.type","SIMPLE").
  103. | option(TABLE_NAME.key, tableName).
  104. | mode(Append).
  105. | save(basePath)
  106. scala> var tripsSnapshotDF2 = spark.read.format("hudi").load(basePath + "/*/*")
  107. tripsSnapshotDF2: org.apache.spark.sql.DataFrame = [_hoodie_commit_time: string, _hoodie_commit_seqno: string ... 10 more fields]
  108. scala> tripsSnapshotDF2.createOrReplaceTempView("hudi_trips_snapshot")
  109. scala> spark.sql("desc hudi_trips_snapshot").show()
  110. +--------------------+---------+-------+
  111. | col_name|data_type|comment|
  112. +--------------------+---------+-------+
  113. | _hoodie_commit_time| string| null|
  114. |_hoodie_commit_seqno| string| null|
  115. | _hoodie_record_key| string| null|
  116. |_hoodie_partition...| string| null|
  117. | _hoodie_file_name| string| null|
  118. | rowId| string| null|
  119. | partitionId| string| null|
  120. | preComb| bigint| null|
  121. | name| string| null|
  122. | versionId| string| null|
  123. | intToLong| bigint| null|
  124. | newField| string| null|
  125. +--------------------+---------+-------+
  126. scala> spark.sql("select rowId, partitionId, preComb, name, versionId, intToLong, newField from hudi_trips_snapshot").show()
  127. +-----+-----------+-------+-------+---------+---------+----------+
  128. |rowId|partitionId|preComb| name|versionId|intToLong| newField|
  129. +-----+-----------+-------+-------+---------+---------+----------+
  130. |row_3| part_0| 0| tom| v_0| 0| null|
  131. |row_2| part_0| 5| john| v_3| 3|newField_1|
  132. |row_1| part_0| 0| bob| v_0| 0| null|
  133. |row_5| part_0| 5| maroon| v_2| 2|newField_1|
  134. |row_9| part_0| 5|michael| v_2| 2|newField_1|
  135. +-----+-----------+-------+-------+---------+---------+----------+

详解 Apache Hudi Schema Evolution(模式演进)的更多相关文章

  1. 详解Apache Hudi如何配置各种类型分区

    1. 引入 Apache Hudi支持多种分区方式数据集,如多级分区.单分区.时间日期分区.无分区数据集等,用户可根据实际需求选择合适的分区方式,下面来详细了解Hudi如何配置何种类型分区. 2. 分 ...

  2. LVS原理详解(3种工作模式及8种调度算法)

    2017年1月12日, 星期四 LVS原理详解(3种工作模式及8种调度算法)   LVS原理详解及部署之二:LVS原理详解(3种工作方式8种调度算法) 作者:woshiliwentong  发布日期: ...

  3. 图文详解AO打印(标准模式)

    一.概述   AO打印是英文Active-Online Print的简称,也称主动在线打印.打印前支持AO通讯协议的AO打印机(购买地址>>)首先通过普通网络与C-Lodop服务保持在线链 ...

  4. Java8初体验(二)Stream语法详解---符合人的思维模式,数据源--》stream-->干什么事(具体怎么做,就交给Stream)--》聚合

    Function.identity()是什么? // 将Stream转换成容器或Map Stream<String> stream = Stream.of("I", & ...

  5. 大数据入门第八天——MapReduce详解(四)本地模式运行与join实例

    一.本地模式调试MR程序 1.准备 参考之前随笔的windows开发说明处:http://www.cnblogs.com/jiangbei/p/8366238.html 2.流程 最重要的是设置Loc ...

  6. 详解Apache服务与高级配置,(主配置文件每行都有描述)

    HTTP服务---> http://httpd.apache.org/(官方网站) httpd  service :纯粹的web服务器,同时开源(不是GPL). 特性:1.在进程特性上通常是事先 ...

  7. 详解ThinkPHP支持的URL模式有四种普通模式、PATHINFO、REWRITE和兼容模式

    URL模式     URL_MODEL设置 普通模式    0 PATHINFO模式     1 REWRITE模式     2 兼容模式     3 如果你整个应用下面的模块都是采用统一的URL模式 ...

  8. 详解apache的allow和deny

    今天看了一篇关于apache allow,deny的文章收获匪浅,防止被删,我直接摘过来了,原文地址!!! !http://www.cnblogs.com/top5/archive/2009/09/2 ...

  9. android中的LaunchMode详解----四种加载模式

    Activity有四种加载模式: standard singleTop singleTask singleInstance 配置加载模式的位置在AndroidManifest.xml文件中activi ...

随机推荐

  1. 操作系统实现-boot.asm实现

    博客网址:www.shicoder.top 微信:18223081347 欢迎加群聊天 :452380935 这一次我们进入操作系统实现的真实编码, 这一次主要是完善对boot.asm文件的全部实现, ...

  2. docker 灵活的构建 php 环境

    地址: https://github.com/ydtg1993/server           使用docker搭建灵活的线上php环境 有时候你可能不太需要一些别人已经集成了的包或者镜像      ...

  3. Linux下切换root用户提示Authentication failure错误的解决方法(亲测有效)

    第一种情况可能是root密码输入错误造成的,再仔细检查一遍是否输入错误 第二种是刚安装完,没有设置root用户密码导致的,我的就是最小化安装,就会出现这种小问题 解决办法:sudo passwd 然后 ...

  4. mybatis 查询返回的类型中字段类型为 List<xx>

    基本类型数组 mapper.xml <resultMap id="xxDtoResultMap" type="com.xx.xxDto"> < ...

  5. 构建AR视频空间大数据平台(物联网及工业互联网、视频、AI场景识别)

    目       录 1.      应用背景... 2 2.      系统框架... 2 3.      AI场景识别算法和硬件... 3 4.      AR视频空间管理系统... 5 5.    ...

  6. 好客租房21-react组件的两种创建方式(函数组件)

    1使用函数创建组件 函数组件:使用js的函数或者箭头函数创建的组件 约定1:函数组件名称必须以 开头 约定2:函数组件必须有返回值 表示该组件的结构 如果返回值为null 表示不渲染任何内容 2.1使 ...

  7. 好客租房23-react组件的两种创建方式(抽离为独立js)

    2.3抽离为单独组件 组件作为一个单独的个体,一般把每个组件放在单独的js中文件中 1创建hello.js 2在hello.js中导入React 3创建组件(函数或者类) hello.js子组件 // ...

  8. 使用虚拟机在3台centos7系统安装docker和k8s集群

    一.安装docker 环境:准备3台centos7系统,都安装上docker环境,具体安装步骤和流程如下 参考: https://docs.docker.com/install/linux/docke ...

  9. Java 对象实现 Serializable 的原因

    java.io.Serializable 是 Java 中的一种标记接口(marker interface).标记接口是一种特殊的接口,java.io.Serializable 接口没有任何方法,也没 ...

  10. 什么是Netty编解码,Netty编解码器有哪些?Protostuff怎么使用?

    哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,昨天下雨没怎么上街上 ...