Hudi的precombine.field释疑

从不同资料,可看到四个让人迷惑的 precombine.field 配置项:

  • precombine.field

  • write.precombine.field

  • hoodie.table.precombine.field

  • hoodie.datasource.write.precombine.field

它们是完全相同,还是有什么关系了?

  • hoodie.datasource.write.precombine.field

HoodieWriteConfig.java

public class HoodieWriteConfig extends HoodieConfig {
  public static final ConfigProperty<String> PRECOMBINE_FIELD_NAME = ConfigProperty
      .key("hoodie.datasource.write.precombine.field")
      .defaultValue("ts")
      .withDocumentation("Field used in preCombining before actual write. When two records have the same key value, "
          + "we will pick the one with the largest value for the precombine field, determined by Object.compareTo(..)");
}
  • hoodie.table.precombine.field

HoodieTableConfig.java

/**
 * Configurations on the Hoodie Table like type of ingestion,
 * storage formats, hive table name etc Configurations are loaded from hoodie.properties,
 * these properties are usually set during
 * initializing a path as hoodie base path and never changes during the lifetime of a hoodie table.
 *
 * @see HoodieTableMetaClient
 * @since 0.3.0
 */
public class HoodieTableConfig extends HoodieConfig {
  public static final ConfigProperty<String> PRECOMBINE_FIELD = ConfigProperty
      .key("hoodie.table.precombine.field")
      .noDefaultValue()
      .withDocumentation("Field used in preCombining before actual write. By default, when two records have the same key value, "
          + "the largest value for the precombine field determined by Object.compareTo(..), is picked.");
}
  • precombine.field

这个是 FlinkSQL 专用的,不能在 SparkSQL 等上使用,write.precombine.field 也是如此。

FlinkOptions.java

/**
 * Hoodie Flink config options.
 *
 * <p>It has the options for Hoodie table read and write. It also defines some utilities.
 */
@ConfigClassProperty(name = "Flink Options",
    groupName = ConfigGroups.Names.FLINK_SQL,
    description = "Flink jobs using the SQL can be configured through the options in WITH clause."
        + " The actual datasource level configs are listed below.")
public class FlinkOptions extends HoodieConfig {
  public static final String NO_PRE_COMBINE = "no_precombine";
  public static final ConfigOption<String> PRECOMBINE_FIELD = ConfigOptions
      .key("precombine.field")
      .stringType()
      .defaultValue("ts")
      // HoodieWriteConfig.PRECOMBINE_FIELD_NAME 为 hoodie.datasource.write.precombine.field
      .withFallbackKeys("write.precombine.field", HoodieWriteConfig.PRECOMBINE_FIELD_NAME.key())
      .withDescription("Field used in preCombining before actual write. When two records have the same\n"
          + "key value, we will pick the one with the largest value for the precombine field,\n"
          + "determined by Object.compareTo(..)");
}

从上面的 precombine.field 定义可以看到,precombine.field 同 write.precombine.field、hoodie.datasource.write.precombine.field 是一样的,最底层用的都是 hoodie.datasource.write.precombine.field 。

  • write.precombine.field

完全等同于 precombine.field

从上面还没看出 hoodie.table.precombine.field 同其它三个有和关系,实际上也是一样的,这从 HoodieTableFactory.java 的实现可以看到。

/**
 * Hoodie data source/sink factory.
 */
public class HoodieTableFactory implements DynamicTableSourceFactory, DynamicTableSinkFactory {
  /**
   * Supplement the table config options if not specified.
   */
  private void setupTableOptions(String basePath, Configuration conf) {
    StreamerUtil.getTableConfig(basePath, HadoopConfigurations.getHadoopConf(conf))
        .ifPresent(tableConfig -> {
          // HoodieTableConfig.RECORDKEY_FIELDS 为 hoodie.table.recordkey.fields
          // FlinkOptions.RECORD_KEY_FIELD 为 hoodie.datasource.write.recordkey.field
          if (tableConfig.contains(HoodieTableConfig.RECORDKEY_FIELDS)
              && !conf.contains(FlinkOptions.RECORD_KEY_FIELD)) {
            conf.setString(FlinkOptions.RECORD_KEY_FIELD, tableConfig.getString(HoodieTableConfig.RECORDKEY_FIELDS));
          }
          // HoodieTableConfig.PRECOMBINE_FIELD 为 hoodie.table.precombine.field
          // FlinkOptions.PRECOMBINE_FIELD 为 precombine.field 和 write.precombine.field、hoodie.datasource.write.precombine.field
          if (tableConfig.contains(HoodieTableConfig.PRECOMBINE_FIELD)
              && !conf.contains(FlinkOptions.PRECOMBINE_FIELD)) {
            conf.setString(FlinkOptions.PRECOMBINE_FIELD, tableConfig.getString(HoodieTableConfig.PRECOMBINE_FIELD));
          }
          if (tableConfig.contains(HoodieTableConfig.HIVE_STYLE_PARTITIONING_ENABLE)
              && !conf.contains(FlinkOptions.HIVE_STYLE_PARTITIONING)) {
            conf.setBoolean(FlinkOptions.HIVE_STYLE_PARTITIONING, tableConfig.getBoolean(HoodieTableConfig.HIVE_STYLE_PARTITIONING_ENABLE));
          }
        });
  }
}
  • 总结

precombine.field 和 write.precombine.field 仅限 FLinkSQL 使用。

HoodieConfig.java

/**
 * This class deals with {@link ConfigProperty} and provides get/set functionalities.
 */
public class HoodieConfig implements Serializable {
  public <T> String getString(ConfigProperty<T> configProperty) {
    Option<Object> rawValue = getRawValue(configProperty);
    return rawValue.map(Object::toString).orElse(null);
  }

 private <T> Option<Object> getRawValue(ConfigProperty<T> configProperty) {
    if (props.containsKey(configProperty.key())) {
      // 从 key 取到值
      return Option.ofNullable(props.get(configProperty.key()));
    }

    // 从 key 没有取到值,遍历所有的将废弃的 keys
    for (String alternative : configProperty.getAlternatives()) {
      if (props.containsKey(alternative)) {
        LOG.warn(String.format("The configuration key '%s' has been deprecated "
                + "and may be removed in the future. Please use the new key '%s' instead.",
            alternative, configProperty.key()));
        return Option.ofNullable(props.get(alternative));
      }
    }
    return Option.empty();
  }
}
### 配置冲突解决方案 当 Apache Hudi 的 `KeyGenerator` 从 `SimpleKeyGenerator` 变更为 `NonpartitionedKeyGenerator` 时,可能会遇到配置冲突问题。这是因为两种键生成器的工作机制不同,导致数据分区路径和记录键的映射逻辑发生变化。 #### 背景分析 - **SimpleKeyGenerator**: 使用记录中的字段作为分区路径的一部分,并将这些字段组合成唯一的 `Hoodie Key`[^2]。 - **NonpartitionedKeyGenerator**: 不依赖于任何分区字段,而是基于全局唯一标识符来构建 `Hoodie Key`,因此不会涉及具体的分区路径[^1]。 如果直接更改 `KeyGenerator` 类型而不调整现有数据结构,则可能导致以下问题: 1. 数据无法正确匹配到已有的文件组或分区内。 2. 新增数据可能覆盖旧有数据,或者丢失原有的更新关系。 --- #### 解决方案 以下是针对上述问题的具体解决办法: 1. **清理受影响的数据** 如果可以接受删除并重新导入整个数据集,则可以通过以下步骤完成转换: - 删除现有的 Hudi 表及其关联的底层存储目录。 ```bash hdfs dfs -rm -r /path/to/hudi/table ``` - 修改配置项以启用新的 `KeyGenerator` 并重建表。 ```properties hoodie.datasource.write.keygenerator.class=org.apache.hudi.keygen.NonpartitionedKeyGenerator ``` 2. **迁移已有数据** 若需保留原有数据而仅切换 `KeyGenerator`,则需要手动重构数据模型: - 创建一个新的临时表,使用目标类型的 `KeyGenerator` 进行初始化。 - 将原表中的数据读取出来并通过 ETL 流程重写至新表中。 - 更新下游应用指向新表的位置。 3. **动态适配策略** 对于某些复杂场景下不允许完全替换的情况,可考虑实现自定义的 `CustomKeyGenerator` 来兼容两者差异。例如,在代码层面判断输入源是否包含特定分区信息从而决定如何生成最终的 `Hoodie Key`。 4. **验证一致性** 完成变更操作之后务必进行全面测试以确认无误: - 检查是否有重复条目存在; - 确认时间戳标记功能正常运作; - 验证查询结果与预期相符。 --- ```python from pyspark.sql import SparkSession spark = SparkSession.builder.appName("hudi_key_generator_migration").getOrCreate() # 设置 NonpartitionedKeyGenerator 参数 write_options = { "hoodie.datasource.write.recordkey.field": "_row_key", "hoodie.datasource.write.partitionpath.field": "", "hoodie.datasource.write.precombine.field": "ts", "hoodie.datasource.write.table.name": "new_table_name", "hoodie.datasource.write.operation": "upsert", "hoodie.datasource.write.keygenerator.class": "org.apache.hudi.keygen.NonpartitionedKeyGenerator" } df = spark.read.format("hudi").load("/path/to/old_hudi_table") # 写入到新表 df.write.format("hudi") \ .options(**write_options) \ .mode("overwrite") \ .save("/path/to/new_hudi_table") ``` --- ### 注意事项 - 在实际部署前应充分评估业务影响范围以及潜在风险。 - 建议提前备份重要资料以防万一发生不可逆损坏。 - 当涉及到大规模分布式计算资源调配时,请参照官方文档推荐的最佳实践设置合理参数如并发数等[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值