Skip to content

[ENG-41614] Discover and upload Iceberg tables alongside Hudi - #191

Merged
dharmendersheshma merged 4 commits into
mainfrom
eng-41614-lakeview-iceberg-discovery
May 22, 2026
Merged

[ENG-41614] Discover and upload Iceberg tables alongside Hudi#191
dharmendersheshma merged 4 commits into
mainfrom
eng-41614-lakeview-iceberg-discovery

Conversation

@dharmendersheshma

@dharmendersheshma dharmendersheshma commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Sub-PR 2 of 2 splitting #189. Layers the Iceberg detector + uploader on top of the TableFormatDetector SPI introduced in #190 (already merged). The control plane parses the snapshot summary from each uploaded metadata.json — cumulative total-records, total-files-size, total-data-files, per-snapshot deltas, and the snapshot-log[] history — gateway-controller side is a separate PR.

See docs/iceberg-support.md for the full design.

What changed

Discovery

  • IcebergTableFormatDetector: matches a directory if it contains a metadata/ sub-dir AND that sub-dir holds a *.metadata.json pointer (so Spark checkpoint dirs / doc folders named metadata/ don't false-positive). Registered alongside HudiTableFormatDetector in the TableDiscoveryService detector map.
  • Database gains an optional tableHints map keyed on the #tableId segment of basePaths; entries carry a metadataLocationHint that the control plane supplies when it knows the current metadata.json URI (e.g. AWS Glue metadata_location). Hint-less tables fall back to plain discovery.
  • Table model gains tableFormat (defaults to HUDI for backward compat) and metadataLocationHint.

Upload

  • IcebergMetadataUploaderService as sibling of TableMetadataUploaderService. Hudi's active/archived timeline distinction, hoodie.properties bootstrap, and LSM manifest plumbing don't apply, so a separate service is clearer than a branching megaclass. Reuses OnehouseApiClient, PresignedUrlFileUploader, AsyncStorageClient, StorageUtils.
  • Pointer-file selection (preference order):
    1. metadataLocationHint from parser YAML (catalog-driven, e.g. Glue metadata_location)
    2. metadata/version-hint.text (Hadoop catalog) — integer content names the current v{N}.metadata.json unambiguously
    3. Numeric-aware filename comparison — leading integer (after optional v prefix) is the version number; works for both v{N}.metadata.json (Hadoop) and 00000-<uuid>.metadata.json (Hive/Glue/Spark)
  • TableDiscoveryAndUploadJob.dispatchUpload partitions discovered tables by format and runs the two uploaders concurrently; both runOnce and processTables route through it.

Wire

  • TableFormat (added in [ENG-41614] Introduce TableFormatDetector SPI (Hudi-only) #190) now flows through to InitializeSingleTableMetricsCheckpointRequest.tableFormat (nullable; server treats absent as HUDI). TableType (COW/MOR) stays @NonNull to preserve the existing wire contract; Iceberg init passes COW as a meaningless placeholder since the server discriminates on tableFormat first.
  • OBJECT_STORAGE_URI_PATTERN accepts s3a:// alongside s3:// (Glue metadata_location scheme).

Hudi safety

Check Status
Database.tableFormat null → HUDI fallback ✅ (from #190)
Table.tableFormat @Builder.Default = HUDI
dispatchUpload null-tableFormat → HUDI bucket
TableMetadataUploaderService (Hudi path) untouched
OBJECT_STORAGE_URI_PATTERN widening — s3://, gs://, abfss://, Azure HTTPS all still match (verified with 13-case regex sanity check)
Hudi-only YAML: extra cost is one empty-set future per cycle ✅ negligible
Full ./gradlew build + ./gradlew test green across all modules

Deployment ordering ⚠️

This change spans three repos and must roll out in order:

  1. idlsTableFormat proto enum / TableMetricsCheckpoint field
  2. gateway-controllerIcebergCommitMetadataParser parses the uploaded metadata.json and emits to OpenSearch
  3. LakeView (this PR)

If LakeView ships before the server understands tableFormat, the server-side protobuf JSON parser drops the unknown enum value and persists TABLE_FORMAT_INVALID, routing Iceberg uploads through the Hudi back-compat fallback. Do not deploy this until the running control plane tolerates tableFormat=ICEBERG cleanly. See docs/iceberg-support.md.

Test plan

  • ./gradlew :lakeview:test green (full suite)
  • New TestIcebergMetadataUploaderService (~538 lines) covers all three pointer-resolution paths + upload + checkpoint flows
  • New TestIcebergTableFormatDetector covers match / no-match / false-positive (metadata/ without *.metadata.json) / Hudi-layout-rejection / trailing-slash CommonPrefix
  • Existing TableDiscoveryServiceTest extended with testDiscoverIcebergTableByDeclaredFormat
  • TableDiscoveryAndUploadJobTest covers dispatchUpload routing
  • StorageUtilsTest covers s3a:// for getPathFromUrl + getBucketNameFromUri
  • ./gradlew build green across all modules (lakeview, lakeview-glue, lakeview-sync-tool)
  • CI green
  • Manual: end-to-end against a real Glue iceberg table once control-plane parser lands

Related

Sub-PR 2 of 2 splitting #189. Layers the Iceberg detector + uploader on
top of the TableFormatDetector SPI introduced in #190.

Discovery
- IcebergTableFormatDetector: matches a directory if it contains a
  metadata/ sub-dir AND that sub-dir holds a *.metadata.json pointer
  (so Spark checkpoint dirs / doc folders named metadata/ don't false-
  positive). Registered alongside HudiTableFormatDetector in the
  TableDiscoveryService detector map.
- Database gains an optional tableHints map keyed on the per-tableId
  segment of basePaths; entries carry a metadataLocationHint that the
  control plane supplies when it knows the current metadata.json URI
  (e.g. AWS Glue's metadata_location). Hint-less tables fall back to
  plain discovery.
- Table model gains tableFormat (defaults to HUDI for backward compat)
  and metadataLocationHint.

Upload
- IcebergMetadataUploaderService sibling of TableMetadataUploaderService.
  Hudi's active/archived timeline distinction, hoodie.properties bootstrap,
  and LSM manifest plumbing don't apply to Iceberg, so a separate service
  is clearer than a branching megaclass. Reuses OnehouseApiClient,
  PresignedUrlFileUploader, AsyncStorageClient, StorageUtils.
- Pointer-file selection (in order of preference): metadataLocationHint
  -> metadata/version-hint.text (Hadoop catalog) -> numeric-aware
  filename comparison.
- TableDiscoveryAndUploadJob.dispatchUpload partitions discovered tables
  by format and runs the two uploaders concurrently; both runOnce and
  processTables route through it. Hudi-only configs see one extra empty
  future per cycle.

Wire
- New TableFormat alias on Database / Table flows through to
  InitializeSingleTableMetricsCheckpointRequest.tableFormat (nullable;
  server treats absent as HUDI).
- OBJECT_STORAGE_URI_PATTERN accepts s3a:// alongside s3:// (Glue
  metadata_location scheme).

Deploy ordering
This change spans three repos and MUST roll out in order:
  1. idls (TableFormat proto enum + TableMetricsCheckpoint field)
  2. gateway-controller (IcebergCommitMetadataParser)
  3. LakeView (this PR)
Out-of-order LakeView ship: server's proto-JSON parser drops the unknown
enum value and routes Iceberg uploads through the Hudi back-compat path.
@nimahajan

Copy link
Copy Markdown

* these tests) to the Iceberg uploader. Default the mock to a successful no-op so the dispatch
* combine doesn't NPE on the (empty Iceberg set) branch.
*/
lenient()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using linient

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

The @beforeeach used lenient() to stub the iceberg uploader because tests
that exercise only the discovery-failure branch never reach dispatchUpload,
and strict-stubbing rejects unused stubs. Moving the stub into the four
tests that actually invoke dispatchUpload (continuous-mode success path,
runOnce, runOnceWithRetry, plus the existing dispatch-routing test which
already had its own stubs) lets us remove the suppression.
@sonarqubecloud

Copy link
Copy Markdown

@dharmendersheshma
dharmendersheshma merged commit 37c749f into main May 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants