apache / apache/hudi

[FEATURE] Support Sub-directory Bucketing for Metadata Table Partitions to Overcome File Count Limits

Open
#18,178 1 comment 0 reactions 0 assignees View on GitHub
type:feature
Dominant language
Java
Stars
6.2k
Forks
2.5k
Avg merge
2d 8h
Merged PRs (30d)
111

Description

### Summary

Large-scale Hudi datasets with millions of records require many file groups (shards) in the Metadata Table (MDT), particularly for the Record Index partition. When all these file groups reside in a single directory, filesystems can hit per-directory file count limits. This proposal introduces a bucketing strategy that organizes MDT file groups into sub-directories (buckets), enabling Hudi to scale to larger datasets and migrate to cloud storage systems that do not support file append operations.

---

### Problem Statement

Currently, large datasets can have many Record Index shards (e.g., ~15,000+) which consist of HFiles (base files) and log files. All of these are located in a single partition directory (e.g., `record_index/`), which can exceed the file count limit that a single directory can handle on certain filesystems.

**Current structure:**
```
.hoodie/metadata/
└── record_index/
├── .hoodie_partition_metadata
├── record-index-0000_xxx.hfile
├── record-index-0000_xxx.log
├── record-index-0001_xxx.hfile
├── ...
└── record-index-14999_xxx.hfile # ~15,000+ files in one directory
```

**Issues with the current approach:**
1. **Directory file count limits**: Some filesystems impose limits on the number of files per directory
2. **Cloud storage compatibility**: Log append operations are used to minimize file creation, but append is not supported on cloud storage (S3, GCS, Azure Blob), blocking cloud migration for large datasets
3. **Filesystem performance degradation**: Directory listing and file operations become slower with many files in a single directory

---

### Proposed Solution

Introduce an intermediate bucketing layer within MDT partitions. File groups are distributed across numbered sub-directories (buckets), with a configurable number of file groups per bucket.

**New bucketed structure:**
```
.hoodie/metadata/
└── record_index/
├── 0000/
│ ├── .hoodie_partition_metadata
│ ├── record-index-0000_xxx.hfile
│ ├── record-index-0001_xxx.hfile
│ └── ... # Up to 1000 file groups
├── 0001/
│ ├── .hoodie_partition_metadata
│ ├── record-index-1000_xxx.hfile
│ ├── record-index-1001_xxx.hfile
│ └── ...
└── 0014/
└── ...
```

---

### Design Details

#### Configuration

| Config Key | Default | Description |
|------------|---------|-------------|
| `hoodie.metadata.file.group.bucketing.enable` | `false` | Enable bucketing for MDT partitions. Only applicable when MDT or a new partition is initialized. |
| `hoodie.metadata.file.group.bucket.size` | `1000` | Number of file groups (shards) per bucket |

A table-level property `hoodie.metadata.partitions.bucketing.enable` is persisted in `hoodie.properties` to track whether bucketing is enabled for the MDT.

#### Backward Compatibility

- **Reader-side auto-detection**: The reader code automatically detects whether bucketing is enabled by checking for the presence of `.hoodie_partition_metadata` at the partition level vs bucket level
- **No reader-side config required**: Existing readers continue to work without configuration changes
- **Write-once**: Bucketing mode is set at MDT initialization; cannot be changed after MDT is created

#### Key Implementation Changes

1. **File Group Initialization** (`HoodieBackedTableMetadataWriter.java`):
- When bucketing is enabled, file groups are created in bucket sub-directories
- Bucket index is calculated as: `fileGroupIndex / bucketSize`

2. **Partition Path Resolution** (`HoodieTableMetadataUtil.java`):
- `getPartitionLatestFileSlices()` iterates over bucket directories when bucketing is detected
- File slices are collected from all buckets and sorted by file ID

3. **Record Key to Partition Mapping**:
- When writing records with bucketing enabled, the partition path in `HoodieKey` is updated to include the bucket path

---

### Testing Plan

1. **Unit Tests**: All existing MDT tests pass with bucketing enabled
2. **Integration Testing**:
- New tables with bucketing enabled from scratch
- Read/write operations on bucketed MDT

---

### Risks and Considerations

#### Immutability of Bucketing Setting
Once MDT is initialized with or without bucketing, the setting cannot be changed via config. Changing requires:
- Deleting the MDT
- Re-initializing with the desired bucketing setting

---

### Rollout Plan

1. **Phase 1**: Feature flag disabled by default (`hoodie.metadata.file.group.bucketing.enable=false`)
2. **Phase 2**: Enable for new tables hitting file count limits
3. **Phase 3**: Consider making bucketing the default for new MDT initializations

---

### Related

- **Affects**: Record Index, Files partition, Column Stats, and other MDT partitions

---

### Acceptance Criteria

- [ ] MDT file groups can be distributed across buckets when config is enabled
- [ ] Reader code auto-detects bucketed vs non-bucketed format
- [ ] All existing MDT unit tests pass
- [ ] New unit tests for bucketing scenarios
- [ ] Documentation for configuration and migration

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with HoodieBackedTableMetadataWriter.java and HoodieTableMetadataUtil.java, focusing on file-group initialization and getPartitionLatestFileSlices(). Review the existing MDT unit tests and add coverage for new bucketed tables and reader auto-detection. Done means enabled configurations distribute file groups into buckets, readers handle both layouts, and all existing tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
data-engineering
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.