nsivabalan commented on PR #19299:
URL: https://github.com/apache/hudi/pull/19299#issuecomment-5027930035

   First off — this is a genuinely useful addition for Hudi. The multi-source 
drift problem is one of the most common footguns I see in production Streamer 
deployments, and consolidating it into a single-commit, single-checkpoint 
source is the right architectural direction. Thanks for taking this on and for 
the level of detail in the write-up — the framing of the drift/atomicity 
problem is accurate, the scoping is disciplined, and the Test Plan is thorough 
enough that we can actually judge the design against how it'll be verified.
   
   A few asks below before this can move to implementation. `InputBatch` is 
left for a separate follow-up comment.
   
   ### 1. Tighten the "atomic across sources" wording
   
   The Abstract and the Motivating cases imply a globally consistent 
point-in-time snapshot across the source tables. The design does not deliver 
that — each per-source read snapshots its own timeline as the driver iterates 
sequentially, and a source can advance between the moment one source's read 
starts and the next source's read starts.
   
   The guarantee is: **the batch commits every source's checkpoint together, or 
none of them**. That's real and worth having. But it's a weaker guarantee than 
"distributed snapshot," and users doing tight cross-source foreign-key joins 
need the distinction to be explicit. Please spell it out, roughly:
   
   > Atomicity here means "the batch commits all source checkpoints together, 
or none." It does **not** guarantee a globally consistent snapshot across 
sources: each source is read against its own timeline as the driver iterates, 
and a source can advance between the moment the previous source's read starts 
and this source's read starts. Users who need cross-source point-in-time 
consistency at the row level must layer their own snapshot mechanism (e.g. 
as-of-time reads at a coordinated timestamp) on top.
   
   ### 2. `InputBatch#getBatch()` fail-loud is a behavior change on a public 
interface — deferring detailed discussion
   
   Marking this as a known concern; will follow up in a separate comment with a 
design proposal. Short version: `InputBatch` is documented public API used by 
every source, transformer, and third-party integration. Making `getBatch()` 
throw when it would previously have returned a value is a behavior change worth 
thinking about carefully, and there may be alternatives (separate type, 
generic-parameter reuse) that avoid the fail-loud contract entirely by making 
misuse a compile-time error rather than a runtime one. Detailed comment to 
follow.
   
   ### 3. Source identifier design — switch to indices, add optional label, 
fingerprint the checkpoint
   
   The current scheme has three parallel identifiers in flight for the same 
source: the dotted-name-in-`tables`-list, the 
underscore-mangled-in-property-key form, and the positional-index-in-SQL form. 
This is a maintenance and operator-confusion tax, and the mangling has a real 
silent-collision failure mode: a user declaring `tables=db.orders,db_orders` 
gets two sources whose property keys both resolve to 
`...source.hudi.db_orders.*`, one silently overwrites the other.
   
   Proposal: replace the alias-with-mangling scheme with **zero-based indices** 
as the durable identifier everywhere.
   
   ```
   hoodie.streamer.source.multi.hudi.count=2
   
   hoodie.streamer.source.multi.hudi.0.path=/hudi/orders
   hoodie.streamer.source.multi.hudi.0.num_instants=5
   hoodie.streamer.source.multi.hudi.0.missing_checkpoint_strategy=READ_LATEST
   
   hoodie.streamer.source.multi.hudi.1.path=/hudi/users
   hoodie.streamer.source.multi.hudi.1.num_instants=3
   
   # Optional non-durable human label — used only in logs / metrics / error 
messages
   hoodie.streamer.source.multi.hudi.0.label=orders
   hoodie.streamer.source.multi.hudi.1.label=users
   ```
   
   Checkpoint format: `<index>=<pathHash>:<instantTime>,…` — e.g. 
`0=abc123:20250606182826197,1=def456:20250606182830145`. The path hash guards 
against silent misconfiguration when a source's path is reordered or replaced 
at an existing index — a fingerprint mismatch fails the job at startup with a 
clear diagnostic pointing at the affected index, rather than silently resuming 
from an unrelated table's checkpoint.
   
   SQL bindings stay as `<SRC_0>`, `<SRC_1>`, … (with `<SRC>` still aliasing 
`<SRC_0>` for single-source SQL file compatibility).
   
   Benefits:
   - No property-key collision possible by construction.
   - Legacy single-timestamp checkpoints migrate cleanly: `<timestamp>` → 
`0=<computed-hash>:<timestamp>` on next commit.
   - Reorder/replace is caught at startup rather than silently corrupting the 
resume point.
   - Labels are decoupled from durability — operators can rename freely for log 
clarity without touching the checkpoint.
   
   ### 4. Source-list evolution semantics need to be specified
   
   The RFC covers migration from a legacy single-source checkpoint but does not 
address the ongoing case where a running pipeline's source list changes. Please 
specify the intended semantics for each of the following operational events, or 
explicitly declare which are supported vs. out-of-scope for v1:
   
   - **Adding a new source** to an existing multi-source pipeline. Does the new 
source apply `missing_checkpoint_strategy`? Is that the right default, given 
`READ_LATEST` implies a silent history skip?
   - **Removing a source** from an existing multi-source pipeline. Is the 
persisted checkpoint entry for the removed source ignored, warned about, or 
cleaned up?
   - **Reordering the source list**, given the `<SRC_0>`/`<SRC_1>` SQL bindings 
are positional — reordering silently rebinds the SQL against different sources. 
How is this prevented, or is it accepted as operator responsibility?
   - **Replacing an existing source's path** with a different Hudi table at the 
same position — would resume from the previous source's checkpoint, which is 
meaningless for the new source (silent data loss or silent misuse).
   
   These are distinct operational events, each with a different failure mode; 
the RFC should own the answer to each.
   
   ### 5. All-sources-failed behavior under `skip_on_schema_failure=true`
   
   `skip_on_schema_failure=true` is a well-scoped opt-in for tolerating a 
single source's schema-resolution failure without stalling the pipeline. Please 
clarify the behavior when *every* configured source has 
`skip_on_schema_failure=true` and every source fails schema resolution in the 
same batch.
   
   Suggested semantics: the merged transformer output is empty, and the batch 
**does not commit** by default (respecting Streamer's existing "commit on no-op 
batch" configuration) — no checkpoint advance, no timeline noise, the 
`<table>.schema_resolution_failure` metric fires N times, and the operator's 
alerting on that metric is the surface for detection. Users who have explicitly 
opted into committing empty batches (e.g. for downstream freshness heartbeats) 
get an empty commit at the previous composite checkpoint. Either way, no data 
is silently written and no cursor silently advances.
   
   Also worth documenting the `<table>.schema_resolution_failure` metric as a 
**hard-alert** signal, not a soft one — a persistent non-zero value indicates a 
source is silently no-op'ing.
   
   ### 6. Multi-statement SQL + temp-view namespace ownership
   
   Two clarifications on the SQL transformer:
   
   - **Cleanup lifetime.** User temp views tracked and cleaned up "in a 
`finally` block" — please specify whether this is per-batch or 
per-Streamer-process. A driver-restart mid-batch will leak them either way; 
that's OK if it's stated.
   - **Namespace collision.** `HOODIE_SRC_TMP_TABLE_<uuid>` is unique per 
invocation. But user-created views (`CREATE OR REPLACE TEMPORARY VIEW my_dim`) 
are not UUID-mangled and can collide with views created by an operator's own 
SQL outside the transformer. Recommend documenting that the transformer takes 
exclusive ownership of the Spark session's temp-view namespace during 
`apply()`, or that view names inside the user's SQL should follow a documented 
naming convention to avoid collision with anything the operator manages outside 
Streamer.
   
   ### 7. Confirm the schema-provider story for the merged output
   
   Reviewing the current single-source `HoodieIncrSource` flow, 
`SchemaProvider` is used for two things: (a) as a fallback source of the 
target-write schema when the transformed dataset is empty, and (b) as a way for 
users to pin the target-write schema of the downstream table via 
`hoodie.streamer.schemaprovider.class`. In practice, most `HoodieIncrSource` 
deployments do not configure a `SchemaProvider` at all — Hudi tables 
self-describe, so the schema flows through implicitly from the `Dataset<Row>` 
returned by the source.
   
   For `HoodieIncrMultiSource`, please confirm in the RFC that:
   
   - The top-level `hoodie.streamer.schemaprovider.class` continues to work 
unchanged for the merged output. When configured with a non-null target schema, 
it wins for the write (matching Sub-branch A of `getDeducedSchemaProvider`).
   - Registry-backed providers (`SchemaRegistryProvider` and variants) are not 
applicable to the multi-source read path, since Hudi tables self-describe. This 
should not surprise users given they aren't applicable to single-source 
`HoodieIncrSource` either, but a one-line note prevents someone from wiring one 
up expecting per-source behavior.
   
   ### 8. Scope statement: v1 is Hudi-to-Hudi only
   
   The RFC introduces general-looking API surface (`InputBatch#getBatches()`, 
`MultiDatasetTransformer`) but the only source implementation is Hudi-specific 
— the checkpoint encoding (`table=instantTime` strings), the per-source config 
keys (`hoodie.streamer.source.multi.hudi.*`), and the sequential-read-on-driver 
design all assume Hudi tables. Please add an explicit scope statement:
   
   > **v1 scope: Hudi-to-Hudi only.** `HoodieIncrMultiSource` reads exclusively 
from Hudi tables. Extending multi-source semantics to Kafka, S3 events, GCS 
events, JSON/DFS, JDBC, or other non-Hudi sources is out of scope for this RFC 
and requires its own design work — specifically, a checkpoint encoding suitable 
for each source type (Kafka per-partition offsets, S3 event-queue positions, 
JDBC watermark columns) that does not fit the `table=instantTime` string format 
used by `MultiTableCheckpointManager`. The `MultiDatasetTransformer` and 
`InputBatch#getBatches()` API changes are structured so that a future 
multi-source implementation for another source type could reuse them, but the 
implementation and validation for those is future work.
   
   ### 9. Schema-provider scope for v1
   
   Please add an explicit paragraph on per-source schema providers, roughly:
   
   > **Schema providers — v1 scope.** `HoodieIncrMultiSource` deliberately does 
not accept per-source `SchemaProvider` configuration in v1. Every source is a 
Hudi table with a well-defined Avro schema in its own metadata; the read path 
derives each source dataset's shape from that. Unlike single-source Streamer 
inputs, there is no "source cannot describe itself" case to solve. Per-source 
shape normalization — renames, casts, null-fills, dropping/pinning columns 
during upstream evolution — is expressed in the transformer SQL against 
`<SRC_0>` / `<SRC_1>` / …. `SchemaProvider` targets Avro conformance, not 
arbitrary row-shape coercion, so SQL is the right layer.
   >
   > The merged output continues to honor the existing top-level 
`hoodie.streamer.schemaprovider.class`. When present with a non-null target 
schema it wins for the write, exactly as in single-source Streamer today; when 
absent, the writer schema is deduced from the merged transformer output 
reconciled against the latest target-table schema.
   >
   > **Startup behavior:** per-source schema-provider keys (e.g. 
`hoodie.streamer.source.hudi.{table}.schemaprovider.class`) are rejected at 
startup with a clear error so operators pattern-matching from single-source 
Streamer configuration are not surprised by a silent-ignore.
   >
   > **Future work:** if a real user need for per-source schema providers 
surfaces (e.g. registry-backed evolution across source *and* target under one 
Streamer job), a follow-up RFC can add it; the v1 API changes do not preclude 
it.
   
   ### 10. Empty-batch guard rewording
   
   The RFC's current phrasing "empty-batch guard skips the transformer when the 
source produced no data" doesn't quite match how `HoodieIncrMultiSource` 
behaves. The source always emits datasets (empty-but-schema-shaped for tables 
with no new data), so `getBatches()` is never actually empty — the list 
contains N entries, some of which may individually be empty. Please reword:
   
   > **Empty-batch guard.** The transformer is invoked whenever 
`HoodieIncrMultiSource` returns any non-empty dataset across its N configured 
sources. When *every* dataset in `getBatches()` is empty (all sources caught up 
in this batch), the transformer is skipped and the batch either commits with no 
data or no-ops entirely, depending on Streamer's "commit on no-op batch" 
configuration.
   
   This also disambiguates from the "all sources failed schema resolution" case 
in #5 — which routes through the schema-failure metric path, not the 
empty-batch path.
   
   ### 11. Confirm sanitization at write time
   
   The RFC states that `SanitizationUtils` is bypassed at the source read 
(since Hudi table rows are already schema-conformant), but "sanitization still 
applies at write time to the merged transformer output when the user has 
enabled it." Please confirm this is actually the case in the implementation — 
specifically, that field-name sanitization on the merged output flows through 
whatever the top-level Streamer write path applies. If sanitization at write 
time was only ever driven by the `RowSource` codepath being taken at read time, 
this bypass silently disables it. A one-line integration test that runs the 
merged output through a write with sanitization enabled would close this out.
   
   ---
   
   ## Overall
   
   Solid, well-scoped design for a real pain point in multi-source ingestion. 
Once the identifier design tightens (#3), the source-list evolution semantics 
get spelled out (#4), and the scope statements land (#8, #9), the RFC is close 
to `+1`. The `InputBatch` conversation is separate and I'll follow up on that.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to