Skip to content

[836] Introduce xtable-spark-runtime for in-job metadata sync#837

Closed
vinishjail97 wants to merge 1 commit into
apache:mainfrom
vinishjail97:836-spark-runtime
Closed

[836] Introduce xtable-spark-runtime for in-job metadata sync#837
vinishjail97 wants to merge 1 commit into
apache:mainfrom
vinishjail97:836-spark-runtime

Conversation

@vinishjail97

Copy link
Copy Markdown
Contributor

What is the purpose of the pull request

Implements the first slice of #836 (RFC-3): a new xtable-spark-runtime module that runs incremental XTable metadata sync inside an existing Spark job, activated by config only — no separate RunSync process, no user code change.

A driver-side QueryExecutionListener runs ConversionController.sync(...) after a successful write to a configured source table:

spark-submit --packages org.apache.xtable:xtable-spark-runtime_2.12:<ver> \
  --conf spark.sql.queryExecutionListeners=org.apache.xtable.spark.XTableSyncListener \
  --conf spark.xtable.tables=orders \
  --conf spark.xtable.orders.basePath=/warehouse/db/orders \
  --conf spark.xtable.orders.sourceFormat=HUDI \
  --conf spark.xtable.orders.targets=ICEBERG,DELTA

Design details are in rfc/rfc-3/rfc-3.md.

Brief change log

  • New module xtable-spark-runtime_${scala.binary.version} (Spark/Hadoop provided), registered in the parent pom.xml.
  • XTableSparkConfig — parse spark.xtable.* into TableSyncSpec (path- and name-based table selection).
  • XTableSyncService — build ConversionConfig(INCREMENTAL) and run ConversionController.sync(...).
  • PlanTargetResolver — best-effort extraction of the written path from the analyzed plan (isolated Spark-internal dependence).
  • XTableSyncListenerstateless, synchronous: on a recognized write matching a configured table, run the sync inline on the callback. Single-flight falls out for free (single-threaded LiveListenerBus); per-table failures are caught as Throwable so a listener never destabilizes the bus.
  • RFC-3.

Design notes

  • Stateless: the sync watermark already lives in the target's TableSyncMetadata, and INCREMENTAL auto-falls back to a full snapshot when unsafe, so there is nothing to track client-side.
  • Synchronous: QueryExecutionListener callbacks are delivered asynchronously by Spark off SparkListenerSQLExecutionEnd, so a listener can't block write(); running inline is the simplest correct choice and completes before JVM exit via the bus drain on SparkContext.stop(). Async / "when to trigger" is intentionally left to the user's DAG as an opt-in follow-up.

Non-goals (follow-ups)

  • Thin shaded/relocated bundle (curated allowlist, org.apache.xtable.shaded.*).
  • StreamingQueryListener variant, CALL xtable.sync(...) procedure, async execution opt-in.

Verify this pull request

This change added tests and can be verified as follows:

  • Unit: TestXTableSparkConfig, TestPlanTargetResolver, TestXTableSyncListener (path matching).
  • Integration: ITXTableSyncListener — embedded local[*]; a real Hudi datasource write triggers the listener and syncs to Delta + Iceberg in the same JVM, asserting the id sets match across all three formats.
SPARK_LOCAL_IP=127.0.0.1 mvn -pl xtable-spark-runtime -am verify

Closes #836 (first slice).

🤖 Generated with Claude Code

Add a new xtable-spark-runtime module and RFC-3. A driver-side
QueryExecutionListener runs an incremental ConversionController.sync
after a successful write to a configured source table, activated via
spark.xtable.* config only (path- and name-based table selection).

The listener is stateless and synchronous: the sync watermark already
lives in the target's TableSyncMetadata, and callbacks are delivered on
Spark's single-threaded LiveListenerBus, so single-flight falls out for
free and the sync runs inline on the callback. Per-table failures are
caught (as Throwable) so a listener never destabilizes the bus.

- XTableSparkConfig: parse spark.xtable.* into TableSyncSpec
- XTableSyncService: build ConversionConfig(INCREMENTAL) + run sync
- PlanTargetResolver: best-effort write-path extraction from the plan
- XTableSyncListener: trigger sync on a recognized write matching a
  configured table
- Unit tests + ITXTableSyncListener (embedded local[*], Hudi -> Delta/Iceberg)

Spark/Hadoop are provided; thin shaded bundle, streaming listener, and
CALL xtable.sync(...) are follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vinishjail97

Copy link
Copy Markdown
Contributor Author

Splitting the PR into packaging and listener for easier validation.

@vinishjail97

Copy link
Copy Markdown
Contributor Author

Split into two stacked PRs for easier review, per feedback:

Closing this combined PR in favor of those.

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.

Introduce xtable-spark-runtime: thin, relocated Spark bundle for in-job metadata sync

1 participant