The Div macro wraps content in a div tag with optional class and styles. This allows us to use macros such as the Style macro below.

Proposers

Approvers

Status

Current state


Current State




(tick)


Discussion thread: here

JIRA: here

Released: <Hudi Version>

Abstract

For tables or partitions, where the majority of records change every cycle, it is inefficient to do upsert or merge.  We want to provide hive like 'insert overwrite' API to ignore all the existing data and create a commit with just new data provided. These  API can also be used for certain operational tasks to fix a specific corrupted partition. We can do 'insert overwrite'  on that partition with records from the source. This can be much faster than restore and replay for some data sources. 

Implementation


Hoodie supports multiple write operations such as insert, upsert, bulk_insert on the target table.  At a high level, we like to add two new operations:

  1. insert overwrite: overwrite partitions touched.   Example: Say a table has 3 total partitions (p0, p1, p2). Client performs insert overwrite with 10 records. Lets say all 10 new records belong to p2.  Then overwrite is only performed on p2.  All previous records in p0, p1 will continue to exist as before.
  2. insert overwrite_table: overwrite all partitions. For the above example, p0 and p1 will have 0 records after the write operation completes successfully. p2 will only have new records

Below, we discuss some high level implementation choices. Most of the discussion is centered on one partition. It is relatively easy to extend this to multiple partitions.

Also, note that my focus is primarily on COW tables. I tried giving examples for MOR tables too. But I don't fully grasp complexity and edge cases in MOR tables, so please highlight any mistakes/bad assumptions.

Implementation Approach #1: Reuse existing file groups 

We reuse existing file groups in partition and distribute new records among them. We create new file groups if number of new records is much larger than existing file groups can support.  If number of new records is much smaller, then there are two high level strategies to distribute records.

  1. Minimize number of file groups required after insert overwrite. Fill the first file group until it meets the size criteria specified in the config. Move to second file group and so on. Some file groups will be empty after distribution.
  2. Round robin records across all existing file groups. None of the file groups will be empty (unless total new records size < number of file groups)

I am inclined towards #1 to minimize IO in subsequent writes. 


Example for Copy On Write:

files in partition before

insert overwrite with similar number of records

insert overwrite with much larger number of records

insert overwrite with 1 record

Partition contains  file1-t0.parquet, file2-t0.parquet

Partition will add file1-t1.parquet, file2-t1.parquet

Partition will add file1-t1.parquet, file2-t1.parquet

file3-t1.parquet

.

.

.fileN-t1.parquet

Partition will add 

file1-t1.parquet,

file2-t1.parquet 

One of the parquet files above will be empty. Other will have one record

Example for Merge On Read:

Note that for MOR, we schedule compaction inline before doing ‘insert overwrite’. We don’t have to wait for compaction to complete.

So assume initial state of partition has commit at t0. ‘Insert overwrite’ is done at t2. We schedule compaction at t1 before starting ‘insert overwrite’ at t2.

files in partition before

insert overwrite with similar number of records

insert overwrite with much larger number of records

insert overwrite with 1 record

Partition contains  file1-t0.parquet, file2-t0.parquet

.file1-t00.log

Partition will add file1-t2.parquet, file2-t2.parquet


Previous files continue to exist. After compaction runs, they will change to:

file1-t1.parquet

file2-t0.parquet

Partition will add file1-t2.parquet, file2-t2.parquet

file3-t2.parquet

.

.

.fileN-t2.parquet

Previous files continue to exist. After compaction runs, they will change to:

file1-t1.parquet

file2-t0.parquet

Partition will add 

file1-t2.parquet,

file2-t2.parquet 

One of the parquet files above will be empty. Other will have one record.

Previous files continue to exist. After compaction runs, they will change to:

file1-t1.parquet

file2-t0.parquet


Advantages:

Disadvantages:

Implementation Approach #2: Create new set of file groups in same partition

Existing file groups are marked for 'deletion'. New file groups are created based on number of new records


Example for Copy On Write:

files in partition before

insert overwrite with similar number of records

insert overwrite with much larger number of records

insert overwrite with 1 record

Partition contains  file1-t0.parquet, file2-t0.parquet

Partition will add file3-t1.parquet, file4-t1.parquet


file1, file2 marked invalid in metadata after  t1

Partition will add file3-t1.parquet, file4-t1.parquet

file5-t1.parquet

.

.

.fileN-t1.parquet

file1, file2 marked invalid in metadata after  t1

Partition will add file3-t1.parquet




file1, file2 marked invalid in metadata after  t1

Example for Merge On Read:


files in partition before

insert overwrite with similar number of records

insert overwrite with much larger number of records

insert overwrite table with 1 record

Partition contains  file1-t0.parquet, file2-t0.parquet

.file1-t00.log

file3-t1.parquet

file4-t1.parquet


file1, file2 marked invalid in metadata after  t1

Partition will add file3-t1.parquet, file4-t1.parquet

.

.

.fileN-t1.parquet

file1, file2 marked invalid in metadata after  t1

Partition will add file3-t1.parquet


file1, file2 marked invalid in metadata after  t1

Advantages:


Disadvantages:

Implementation Approach #3: version partition paths

Add an extra level of versioning on the partition itself.


Example for Copy On Write:

files in partition before

insert overwrite with similar number of records

insert overwrite with much larger number of records

insert overwrite with 1 record

Partition contains  file1-t0.parquet, file2-t0.parquet

Partition contains  file1-t0.parquet, file2-t0.parquet

A new directory ‘partition-t1’ is created. 

partition-t1 will add file3-t1.parquet, file4-t1.parquet

Partition contains  file1-t0.parquet, file2-t0.parquet

A new directory ‘partition-t1’ is created. 

partition-t1 will add file3-t1.parquet, file4-t1.parquet

file5-t1.parquet

.

.

.fileN-t1.parquet

Partition contains  file1-t0.parquet, file2-t0.parquet

A new directory ‘partition-t1’ is created. 

partition-t1 will add file3-t1.parquet

Example for Merge On Read:

files in partition before

insert overwrite with similar number of records

insert overwrite with much larger number of records

insert overwrite table with 1 record

Partition contains  file1-t0.parquet, file2-t0.parquet

.file1-t00.log

Partition contains  file1-t0.parquet, file2-t0.parquet

.file1-t00.log

A new directory ‘partition-t1’ is created. 


partition-t1 will add file3-t1.parquet, file4-t1.parquet

Partition contains  file1-t0.parquet, file2-t0.parquet

.file1-t00.log

A new directory ‘partition-t1’ is created. 


partition-t1 will add file3-t1.parquet, file4-t1.parquet

.

.

.

fileN-t1.parquet

Partition contains  file1-t0.parquet, file2-t0.parquet

.file1-t00.log

A new directory ‘partition-t1’ is created. 


partition-t1 will add file3-t1.parquet, file4-t1.parquet

Advantages:


Disadvantages:


API

Regardless of implementation approach chosen, we need to add/change existing high level API. At the moment, we incline towards creating two new operations on spark dataframe instead of adding flags on existing operation because the actions taken are equivalent to deleting all pre-existing data. But open to feedback.

Similarly a new high level public method is added on HoodieWriteClient (We can discuss merging these two into one as well)

Other considerations


Recommendation

After weighing trade-offs, we are inclined towards going with Option 2.  Option 1 has lot of complexity especially with MOR tables. Option 3 would add a lot of complexity in general.  With Option2, we can take multiple steps.

  1. Before consolidated metadata launches, in the initial version, we keep list of file groups to be filtered out in active commit metadata. We change reader to query active commits and filter appropriate file groups. We also make changes to cleaner to archive active commits only after removing corresponding file groups from disk. (This approach can also be used for RFC-19)
  2.  After consolidated metadata launches, we can change metadata lookup to leverage that instead of reading from active commit files.

Rollout/Adoption Plan

Test Plan