
# Spark Native算子优化
#### 什么是Native引擎？
Spark Native引擎是Apache Spark的一个核心组件，用于提高Spark SQL计算性能而设计。通过使用向量化的C++加速库（Velox），实现对Spark算子性能加速。开启Spark Native引擎特性，可以提升Spark SQL的作业性能，减少CPU和内存的消耗。
#### 工作原理
Native引擎在Spark的执行层与原生代码之间建立了一座"桥梁"。整个执行流程如下：
1. **查询规划**：Spark的Catalyst优化器仍然负责生成查询计划，保证现有优化逻辑的完整性。
2. **计划转换**：当执行计划生成后，Native引擎会校验其是否可以被原生引擎执行。如果可以，它将Spark计划转换为子计划（Substrait Plan），这是一种跨语言的查询计划表示形式。
3. **原生执行** ：转换后的子计划通过JNI（Java Native Interface）被下发给C++运行时执行。该运行时利用**列式批处理** 和**向量化计算**（如SIMD指令）来处理数据。
4. **混合模式与回退**：如果查询中包含引擎不支持的算子或表达式，系统会透明地回退到原生的Spark JVM执行，确保查询能够正确完成。
这种架构带来了多方面的性能提升：
- **消除JVM开销**：避免了垃圾回收（GC）暂停和对象内存占用。
- **利用现代CPU特性**：列式数据布局和批量处理大幅提高了CPU缓存命中率和内存带宽利用率，并支持SIMD指令加速。
- **高效内存管理**：原生引擎通常实现更精细、可预测的内存管理策略，减少内存抖动。
- **透明加速**：现有基于Spark SQL的代码无需任何修改即可获得性能提升。
 
#### 约束限制
- 不支持的功能会透明回退：遇到不支持的算子或表达式时，查询会回退到原生Spark执行，保证结果正确但无法获得加速。
- 用户自定义函数（UDF）：使用UDF、UDAF或UDTF的表达式目前无法加速，会回退执行。
- RDD/DataSet API：目前主要针对Spark SQL和DataFrame API进行加速，直接使用RDD或DataSet API的作业无法获益。
- 流处理：对Structured Streaming的支持正在完善中，当前对复杂的流式聚合（有状态流）支持有限。
- 短查询：对于执行时间极短（如几秒内）的查询，因任务调度和计划生成的开销占主导，性能提升可能不明显。
 
#### 开启Spark Native算子优化
- 当前默认开启Spark Native加速。
- 可通过explain查看执行计划，有"\^"前缀的算子表示已经实现Native加速；对于不支持的算子，退化到原生Spark算子处理。
 
#### 关闭Spark Native算子优化
支持作业级别关闭Spark Native特性。当出现兼容性、类冲突或执行器启动错误等情况时，可以通过在SQL作业的参数设置中配置spark.gluten.enabled=false来实现作业级别关闭Spark Native。
#### 支持ANSI语法
Apache Spark 4.0 在SQL标准合规性方面迈出了重要一步，将ANSI SQL模式默认启用。这一改变使Spark的行为更贴近主流SQL标准，提升了跨平台查询的可移植性和数据处理的严谨性。当前Spark Native引擎默认已支持ANSI，对于以下几种场景，Spark Native的处理行为如下：
- **执行算术运算**：Spark Native会对无效操作抛出运行时异常。
- **执行数据类型转换**：Spark Native遵循严格的转换规则，对无效转换抛出运行时异常。
- **向表中插入数据**：Spark Native严格校验类型，对数值溢出抛出运行时异常。
- **处理隐式转换**：Spark Native会阻止不允许的隐式转换。
- **回退到Spark**：对于不支持的功能，Spark Native会回退到Spark执行。
开启ANSI后，下列场景与不开启ANSI的场景存在差异：
- **Cast类型转换**
  ANSI 模式（spark.sql.ansi.enabled=true）下使用CAST语法进行显式类型转换时，对于标准中定义的非法转换模式（例如从字符串转换为整数）会抛出运行时异常。
  Spark Native(Velox)目前支持以下类型转换组合。对于不支持的转换组合，将回退到Spark执行转换。Spark Native中当前Fall Back的转换组合将在后续版本中得到支持。Spark Native FallBack的转换组合参考如下表格：
  
  表1Spark Native FallBack的转换组合 
  | SRC/TGT      | TinyInt | Smallint | Int | Bigint | Float | Double | Decimal | String | Date | Timestamp | TimestampNTZ | Interval | Boolean | Binary | Array | Map | Struct |
  |:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|
  | Tinyint      | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | N    | OK        | N            | FB       | OK      | N      | N     | N   | N      |
  | Smallint     | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | N    | OK        | N            | FB       | OK      | N      | N     | N   | N      |
  | Int          | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | N    | OK        | N            | FB       | OK      | N      | N     | N   | N      |
  | Bigint       | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | N    | OK        | N            | FB       | OK      | N      | N     | N   | N      |
  | Float        | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | N    | OK        | N            | N        | OK      | N      | N     | N   | N      |
  | Double       | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | N    | OK        | N            | N        | OK      | N      | N     | N   | N      |
  | Decimal      | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | N    | FB        | N            | FB       | OK      | N      | N     | N   | N      |
  | String       | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | OK   | OK        | FB           | FB       | OK      | OK     | N     | N   | N      |
  | Date         | N       | N        | N   | N      | N     | N      | N       | OK     | OK   | OK        | FB           | N        | N       | N      | N     | N   | N      |
  | Timestamp    | OK      | OK       | OK  | OK     | OK    | OK     | FB      | OK     | OK   | OK        | FB           | N        | N       | N      | N     | N   | N      |
  | TimestampNTZ | N       | N        | N   | N      | N     | N      | N       | FB     | FB   | FB        | FB           | N        | N       | N      | N     | N   | N      |
  | Interval     | FB      | FB       | FB  | FB     | N     | N      | FB      | FB     | N    | N         | N            | FB       | N       | N      | N     | N   | N      |
  | Boolean      | OK      | OK       | OK  | OK     | OK    | OK     | OK      | OK     | N    | N         | N            | N        | OK      | N      | N     | N   | N      |
  | Binary       | N       | N        | N   | N      | N     | N      | N       | OK     | N    | N         | N            | N        | N       | OK     | N     | N   | N      |
  | Array        | N       | N        | N   | N      | N     | N      | N       | OK     | N    | N         | N            | N        | N       | N      | OK    | N   | N      |
  | Map          | N       | N        | N   | N      | N     | N      | N       | FB     | N    | N         | N            | N        | N       | N      | N     | OK  | N      |
  | Struct       | N       | N        | N   | N      | N     | N      | N       | FB     | N    | N         | N            | N        | N       | N      | N     | N   | OK     |
     
  在数据类型转换的过程中，有三种不同的状态：
  - **OK**表示该转换在Velox中是支持的。
  
  - **N** 表示在Spark中该类型转换是无效的。
  
  - **FB** 表示在Velox中无法处理时会回退到Spark进行处理。
    ![](https://support.huaweicloud.com/usermanual-aidatalake/public_sys-resources/caution_3.0-zh-cn.png)
    TimestampNTZ数据类型在Velox中是不被支持的，因此在涉及该数据类型的转换时，需要回退到Spark进行处理。
    
  
  
  表2数据类型组合关系 
  | Source\\Target | Numeric | String | Date | Timestamp | Timestamp_NTZ | Interval | Boolean | Binary | Array | Map | Struct |
  |:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|
  | Numeric        | Y       | Y      | N    | Y         | N             | Y        | Y       | N      | N     | N   | N      |
  | String         | Y       | Y      | Y    | Y         | Y             | Y        | Y       | Y      | N     | N   | N      |
  | Date           | N       | Y      | Y    | Y         | Y             | N        | N       | N      | N     | N   | N      |
  | Timestamp      | Y       | Y      | Y    | Y         | Y             | N        | N       | N      | N     | N   | N      |
  | Timestamp_NTZ  | N       | Y      | Y    | Y         | Y             | N        | N       | N      | N     | N   | N      |
  | Interval       | Y       | Y      | N    | N         | N             | Y        | N       | N      | N     | N   | N      |
  | Boolean        | Y       | Y      | N    | N         | N             | N        | Y       | N      | N     | N   | N      |
  | Binary         | N       | Y      | N    | N         | N             | N        | N       | Y      | N     | N   | N      |
  | Array          | N       | Y      | N    | N         | N             | N        | N       | N      | Y     | N   | N      |
  | Map            | N       | Y      | N    | N         | N             | N        | N       | N      | N     | Y   | N      |
  | Struct         | N       | Y      | N    | N         | N             | N        | N       | N      | N     | N   | Y      |
     
  Spark的CAST 表达式中源数据类型与目标数据类型的有效组合如下表所示。"Y" 表示该组合在语法上有效且无限制，"N" 表示该组合无效。
  
  ![](https://support.huaweicloud.com/usermanual-aidatalake/public_sys-resources/note_3.0-zh-cn.png)
  上表中，所有采用新语法的CAST操作均以红色Y标记：
  - 数值转数值：如果值超出目标数据类型的范围，则抛出溢出异常。
  
  - 字符串转（数值/日期/时间戳/无时区时间戳/时间间隔/布尔值）：如果值无法解析为目标数据类型，则抛出运行时异常。
  
  - 时间戳转数值：如果自纪元起的秒数超出目标数据类型的范围，则抛出溢出异常。
  
  - 数值转时间戳：如果数值乘以1,000,000（每秒微秒数）的结果超出Long类型的范围，则抛出溢出异常。
  
  - 数组转数组：如果任一元素的转换出现问题，则抛出异常。
  
  - 映射转映射：如果任一键或值的转换出现问题，则抛出异常。
  
  - 结构体转结构体：如果任一结构体字段的转换出现问题，则抛出异常。
  
  - 数值转字符串：将小数值转换为字符串时始终使用普通字符串表示法，而非在需要指数时采用科学计数法。
  
  - 时间间隔转数值：如果日时间间隔的微秒数或年月间隔的月数超出目标数据类型的范围，则抛出溢出异常。
  
  - 数值转时间间隔：如果数值乘以目标间隔的末尾单位后，对于年月间隔超出Int类型范围，或对于日时间间隔超出Long类型范围，则抛出溢出异常。
    

- **插入数据**
  当spark.sql.storeAssignmentPolicy设置为ANSI（默认值）时，Spark SQL在表插入操作中遵循ANSI存储赋值规则。表插入操作中源数据类型与目标数据类型的有效组合如下表所示。
  ![](https://support.huaweicloud.com/usermanual-aidatalake/zh-cn_image_0000002632901906.png "点击放大")
  存储赋值规则由Spark处理，因此Spark Native中无需做任何修改。
  ![](https://support.huaweicloud.com/usermanual-aidatalake/public_sys-resources/note_3.0-zh-cn.png)
  - \* 表示Spark 不支持将间隔（Interval）类型作为表的列类型。
  
  - \*\* 表示对于数组（Array）/映射（Map）/结构体（Struct）类型，其数据类型检查规则会递归地应用于它们的组成元素。
    

- **存在差异的函数**
  某些SQL函数的行为在ANSI模式（spark.sql.ansi.enabled=true）下可能会有所不同。
  
  | 函数                | 描述                                                | Spark Native(Velox)状态 |
  |:---|:---|:---|
  | size              | 对于NULL输入，该函数返回NULL                                | 支持                    |
  | element_at        | 如果使用无效索引，该函数会抛出ArrayIndexOutOfBoundsException异常   | 支持                    |
  | elt               | 如果使用无效索引，该函数会抛出ArrayIndexOutOfBoundsException异常   | 回退Spark               |
  | parse_url         | 如果输入字符串不是有效的URL，该函数会抛出 IllegalArgumentException异常 | 回退Spark               |
  | to_date           | 如果输入字符串无法解析或格式字符串无效，该函数应抛出异常                      | 支持                    |
  | to_timestamp      | 如果输入字符串无法解析或格式字符串无效，该函数应抛出异常                      | 支持                    |
  | unix_timestamp    | 如果输入字符串无法解析或格式字符串无效，该函数应抛出异常                      | 支持                    |
  | to_unix_timestamp | 如果输入字符串无法解析或格式字符串无效，该函数应抛出异常                      | 支持                    |
  | make_date         | 如果生成的日期无效，该函数应抛出异常                                | 支持                    |
  | make_timestamp    | 如果生成的时间戳无效，该函数应抛出异常                               | 支持                    |
  | make_interval     | 如果生成的时间间隔无效，该函数应抛出异常                              | 回退Spark               |
  | next_day          | 如果输入不是有效的星期几，该函数会抛出IllegalArgumentException异常     | 支持                    |
     
  

- **SQL 运算符**
  某些SQL运算符的行为在ANSI模式（spark.sql.ansi.enabled=true）下可能会有所不同。
  
  
  | 运算符                | 描述                                                | Spark Native(Velox)状态 |
  |:---|:---|:---|
  | array_col\[index\] | 如果使用无效索引，该运算符会抛出ArrayIndexOutOfBoundsException异常。 | 回退Spark               |
     
  

- **ANSI 模式下的实用函数**
  当ANSI模式开启时，对于无效操作会抛出异常。以下SQL函数可用于抑制此类异常：
  
  | 函数                     | 描述                                                | Spark Native(Velox)状态 |
  |:---|:---|:---|
  | try_cast               | 与CAST功能相同，区别在于运行时错误时返回NULL结果而不是抛出异常。              | 支持                    |
  | try_add                | 与加法运算符+功能相同，区别在于整型值溢出时返回NULL结果而不是抛出异常。            | 支持                    |
  | try_subtract           | 与减法运算符-功能相同，区别在于整型值溢出时返回NULL结果而不是抛出异常。            | 支持                    |
  | try_multiply           | 与乘法运算符\*功能相同，区别在于整型值溢出时返回NULL结果而不是抛出异常。           | 支持                    |
  | try_divide             | 与除法运算符/功能相同，区别在于除零时返回NULL结果而不是抛出异常。               | 支持                    |
  | try_mod                | 与取模运算符%功能相同，区别在于除零时返回NULL结果而不是抛出异常。               | 支持                    |
  | try_sum                | 与sum函数功能相同，区别在于整型/十进制/时间间隔值溢出时返回NULL结果而不是抛出异常。    | 回退Spark               |
  | try_avg                | 与avg函数功能相同，区别在于十进制/时间间隔值溢出时返回NULL结果而不是抛出异常。       | 回退Spark               |
  | try_element_at         | 与element_at函数功能相同，区别在于数组索引越界时返回NULL结果而不是抛出异常。     | 支持                    |
  | try_to_timestamp       | 与to_timestamp函数功能相同，区别在于字符串解析错误时返回NULL结果而不是抛出异常。  | 支持                    |
  | try_parse_url          | 与parse_url函数功能相同，区别在于URL解析错误时返回NULL结果而不是抛出异常。     | 回退Spark               |
  | try_make_timestamp     | 与make_timestamp函数功能相同，区别在于出错时返回NULL结果而不是抛出异常。     | 支持                    |
  | try_make_timestamp_ltz | 与make_timestamp_ltz函数功能相同，区别在于出错时返回NULL结果而不是抛出异常。 | 支持                    |
  | try_make_timestamp_ntz | 与make_timestamp_ntz函数功能相同，区别在于出错时返回NULL结果而不是抛出异常。 | 回退Spark               |
  | try_make_interval      | 与make_interval函数功能相同，区别在于无效时间间隔时返回NULL结果而不是抛出异常。  | 回退Spark               |
     
  
 
#### Remote Shuffle Service支持
对于Shuffle密集型的作业，Native引擎可以接入远程Shuffle服务（Remote Shuffle Service），将Shuffle数据从本地磁盘卸载到外部集群，提升稳定性和性能。目前Spark Native集成Store作为远程Shuffle服务解决方案。
