AnishaKondaTR opened a new issue, #19593:
URL: https://github.com/apache/hudi/issues/19593
### Bug Description
# Investigation Brief: Duplicate GUIDs in the TNI Spark Streaming → Hudi
Pipeline (DOC collection type)
## 1. Context / Architecture
TNI is a Spark Structured Streaming job consuming a Kinesis stream on a
10-minute batch interval. Per micro-batch:
- Records read are bounded by a **message limit** + **polling interval**.
- For each Kinesis control message, the job reads the referenced
`ContentMetadata` JSON and Avro content from S3.
- After transformation, records are written to a **Hudi MERGE_ON_READ**
table.
**Confirmed Hudi write configuration** (`NovusHudiOperations.scala`
`hudiOptions`):
- `RECORDKEY_FIELD = guid`, `PARTITIONPATH_FIELD = collectionName`,
`PRECOMBINE_FIELD = operationTime`
- `TABLE_TYPE = MERGE_ON_READ`; inline compaction `TIME_ELAPSED` every 6h;
cleaner `KEEP_LATEST_COMMITS = 447`; `ASYNC_CLEAN = false`
- `hoodie.metadata.enable = false` → no metadata-table file listing; index
defaults to **partition-scoped**
- `hoodie.write.table.version = 6` (pinned for downstream Hudi-0.x readers)
+ `auto.upgrade = false`
- `ROLLBACK_USING_MARKERS_ENABLE` — **was `false`** (disabled 2022-08-05,
commit `4c0ca456`, no recorded rationale, under `metadata.enable=true`, pre-v6)
**Write choke point:** every insert/upsert path — `bulkAdd`, `bulkAdds`,
`promoteWithReset`, `reinit`, `promoteDemoteAdd(s)`, streaming incremental —
funnels through the single method `NovusHudiOperations.upsert()`. Only
`delete()` bypasses it. Any write-path fix belongs in `upsert()`, not at an
upstream union site.
**Write pattern:** per-message DataFrames are `unionAll`'d into one
mixed-`collectionName` DataFrame and written with **one** Hudi upsert; Hudi
routes each row to its `collectionName` partition and upserts by `guid`
(partition-scoped index). A single delta commit can therefore span multiple
partitions.
**High-level flow:**
```
R->>D: read last checkpoint seq#
R->>K: GetRecords (AFTER_SEQUENCE_NUMBER | TRIM_HORIZON)
K-->>R: JSON control messages (+ millisBehindLatest)
R-->>A: DStream RDD of KinesisInputNovusMessage
A->>D: lockOrFail (IS_LOCKED per content type)
A->>A: group by collection, parse operationType
A->>S: read control-metadata JSON (InputReader.readJsonFile)
A->>S: read Avro content (spark.read.format("avro"))
A->>H: UPSERT (recordKey=guid, part=collectionName) / soft-delete
alt all collections in batch succeeded
A->>D: updateCheckpoint (max seq# → CHECKPOINT_TIMESTAMP)
else any failure
A-->>A: log P5, do NOT advance checkpoint (reprocess next run)
A->>D: unlock (finally)
```
## 2. Observed symptom
Duplicate `(guid, collectionName)` rows in the `novusdoc` table for the
**DOC** collection type. QA and Prod consume the **same** Prod Kinesis stream,
yet the **duplicate collections differ** between QA and Prod. Sample (QA,
`w_blc_edgar_other_01`): two physical copies of each guid sharing identical
`_hoodie_commit_time`, `sequenceid`, `operationtime`, differing only by
`_hoodie_file_name` (file groups `3250`/`3251`).
**Scale (QA, verified via Athena on `a206760-tni-qa-db.novusdoc_rt`):** ~720
duplicate pairs across 9 DOC collections — **708 same-commit (signature #2) /
12 cross-commit (signature #1)**.
Sample Kinesis message:
```
KinesisCustomOutputData({ "sequenceId": 264096479, "collectionName":
"w_cs_ne2", "collectionType": "DOC",
"collectionId": 2217, "operationType": "PROMOTE", "operationTime":
1785913614134, "operationStartStage": 0,
"operationEndStage": 770421, "contentMetadataObjectName":
"s3://.../w_cs_ne2/264096479_.../ContentMetadata",
"ingestTime": "1785913811608", "totalContentSize": 198996,
"totalGuidsUploaded": 13}, <seq>, 0)
```
## 3. The two signatures — and the test that distinguishes them
| | Signature #1 (cross-commit) | Signature #2 (same-commit) |
|---|---|---|
| `_hoodie_commit_time` of the copies | **different** (minutes apart) |
**identical** |
| meaning | same batch committed **twice** | one commit wrote the key into
**2+ file groups** |
| per-batch counters (`sequenceid`/`operationtime`/`operationendstage`) |
identical across both copies ⇒ **replay by one writer**; distinct ⇒
**concurrent writers** | identical (one commit) |
| QA share | 12 | 708 (dominant) |
**Batch-counter test (triage rule):** for any cross-commit pair, compare
`sequenceid`/`operationtime`/`operationendstage` across the two commits.
Identical ⇒ single-writer replay (fix = replay-prevention). Distinct ⇒
concurrent writers (fix = OCC).
## 4. Complete scenario catalog
| # | Scenario | Evidence / status |
|---|---|---|
| **S1** | Intra-batch dup (same key twice in one micro-batch) | Not the
cause — Hudi `combine.before.upsert` collapses it (repro-confirmed) |
| **S2** | PROMOTE/DEMOTE cross-partition relocation (guid → new
`collectionName`) | Root cause A — old copy in prior partition not found by
partition-scoped index → duplicate INSERT; invisible to `DuplicateVerification`
(groups by `(guid, collectionName)`) |
| **S3** | Zero-timestamp tombstone | Root cause B — `softDelete` used
`operationTime=0L` → always loses precombine → soft-delete no-ops → old row
stays live on every reload (`bulkAdd`/`bulkAdds`/`promoteWithReset`/`reinit`) |
| **S4** | **Same-commit multi-file-group split (dominant)** |
Athena-confirmed retry/re-bucketing origination. `other_01`: 544 keys
near-mirror in FG `3250`/`3251`. `33_02`: 56/57 from **one commit
`20260605084155211`** (hub FG `7b66daa1`). That commit also duplicated
`UK_SMG_EUIMAGES_03` + `10q_01` — a single cross-collection culprit write event
|
| **S5** | Cross-commit whole-batch replay (signature #1) | Confirmed
replay. `33_02`: 8 keys, commits `20260604180024505` & `...181909526` (~19 min
apart), **identical**
`sequenceid=259023002`/`operationtime=1780594587500`/`operationendstage=127417`
across all 8 guids and both commits ⇒ one batch committed twice by one writer.
**Concurrent writers ruled out** |
| **S6** | Concurrent writers | Ruled out for observed data (no
distinct-batch-counter pairs seen) |
| **S7** | Existing committed dups (~720) | Already in the table; not
preventable by any write-config change; compaction/cleaner cannot remove them |
| **S8** | True partial-write orphan files (uncommitted) | The mechanism
behind S4-type dups; addressable by markers + failed-writes cleaning |
## 5. Confirmed diagnostics (Athena, read-only)
- **`other_01` near-mirror:** FG `3250` (577 rows) and `3251` (573 rows)
share **identical min guid `I002f9f98…` and max guid `Iff3553a6…`**; **544
guids in both (~94%)**, 33 only-3250, 29 only-3251. A 94%-identical, same-range
pair ⇒ a **whole-insert-bucket retry** copied one file group (Mechanism A/C),
not a bloom/bucket range scatter. The "275 commits all hit 3250/3251" pattern =
one origination + later updates to those 544 keys routing into both
pre-existing file groups (metadata off + partition-scoped index).
- **`23mm` / `33_02` shape:** small per-key sets around a **hub** file group
(`23mm` hub `3bb7805c`, 16 keys; `33_02` hub `7b66daa1`). `33_02`'s 56
same-commit dups concentrate in **one commit** ⇒ same retry-origination family
as `other_01`, **not** a diffuse index-tagging scatter.
- **Method validated:** the same-/cross-commit split reproduced
independently-derived counts exactly (`33_02` = 57 sig #2 / 8 sig #1).
## 6. Hypotheses ruled out
- Checkpoint-skip / batch replay (simulated — no repro).
- Duplicate Kinesis message delivery (same sequence ID/partition key twice).
- Duplicate GUID within a single Avro source file.
- Intra-batch combine failure (combine works).
- Kinesis redelivery of an already-committed key.
- Concurrent-writer OCC race (batch-counter test ⇒ replay, not concurrency).
## 7. Verdict on the "partial write + re-run → orphan files" RCA
**Confirmed as the leading mechanism for signature #2's dominant cluster —
but not the sole cause, and it is a prevention story, not a remediation one.**
UPSERT dedupes only against **committed + indexed** records; a
retried/speculative Spark task's orphaned output file (under
`ROLLBACK_USING_MARKERS_ENABLE=false`) is never cleaned, so both attempts'
files survive under one commit → the same guid twice. The `other_01` 94%
near-mirror is exactly this shape. It does **not** explain signature #1 (two
different commits, minutes apart — a replay mechanism, §S5).
**QA/Prod divergence:** QA and Prod are **two fully independent
single-writer runtimes** (separate EMR clusters/JVMs, separate DynamoDB
checkpoint tables per `APP_NAME`/env, byte-identical `hudiOptions`) consuming
the shared stream at their own pace — so each environment's affected
collections are a function of *when it hit a transient failure*, not of the
stream.
## 8. Mechanisms behind the replays (signature #1)
- **Mechanism B — Kinesis iterator expiry:** `KinesisCustomConsumer`
advances the in-memory `globalShardIterator` as records are read (ahead of the
DynamoDB checkpoint, which advances only after a *full* batch succeeds —
`TNIStreamingApp.processInterval`). On `ExpiredIteratorException` it resets
`globalShardIterator=""`, forcing `onStart()` to replay from the **committed
checkpoint** → re-reads/re-upserts records already pushed → cross-commit dup.
- **Mechanism C — retry-wrapper re-save:** `upsert()`'s `retry()` block
wraps `.save()` **plus** post-save
`reportMetric`/`emitBuiltinHudiMetricsCompat`; a transient throw *after* a
successful `.save()` re-runs `.save()` → a second commit minutes later.
- **Not concurrent writers** (batch-counter test).
## 9. Why compaction/cleanup do NOT remove these dups
Dedup is a **write-time** guarantee (index → precombine → file-group
assignment). Compaction merges log→base **within one file group** and never
crosses file groups or runs the index; the cleaner drops **old versions** per
file group and never dedupes keys or removes a latest slice. So committed
cross-file-group dups (S4/S5/S7) survive both (proven: the `other_01` dups
carry a 2026-08-02 compaction instant and persist). S3 survives because
compaction faithfully applies the *broken* precombine. Clustering also won't
help (it preserves records).
**Exception:** uncommitted orphans (S8) *are* addressable by the cleanup
family — **marker-based rollback** (Fix C) and
**`cleaner.policy.failed.writes=EAGER`** (Fix G) — because those files are
uncommitted; that is not the regular `KEEP_LATEST_COMMITS` cleaner or
compaction.
## 10. Required dedup behaviors (invariants any fix must preserve)
1. **Cross-collection same guid = NOT a duplicate** — same guid in
collection A and B → both rows preserved.
2. **Within-collection same guid = duplicate.**
3. Within-collection, same guid, different `operationTime` → keep max
`operationTime` (1 row).
4. Within-collection, same guid, same `operationTime` → collapse to 1 row.
5. Mixed batch (several collections, some dup) → correct per-collection
collapse.
6. Live tombstone (`delete=true`, fresh `operationTime`) vs older live row
for same key → **tombstone wins** (must interoperate with the S3 soft-delete
fix).
im currently having issue with S4 and S5 and S8 as per my analysis is it the
reason for duplicates
### Environment
**Hudi version:** 1.0.2
**Query engine:** (Spark/Flink/Trino etc) Spark
**Relevant configs:**
### Logs and Stack Trace
# Duplicate-GUID Pattern Examples (CSV-Backed)
Concrete, data-backed examples for each duplicate-GUID mechanism, pulled
directly from:
- `duplicate_guid_field_details_qa_sig2only.csv`
- `duplicate_guid_field_details_qa_sig2only_202607.csv`
Both files are scoped to Signature #2 (same-`_hoodie_commit_time`
duplicates) only — neither
contains a Signature #1 (cross-commit) example, so the S5 section below is
illustrative /
sourced from the original investigation rather than these two files.
CSV schema:
`_hoodie_commit_time,_hoodie_commit_seqno,_hoodie_record_key,_hoodie_partition_path,_hoodie_file_name,guid,collectionname,sequenceid,operationtime,operationstartstage,operationendstage,delete`
Hudi data-file naming convention referenced throughout:
`<fileId>_<writeToken>_<instantTime>.parquet`,
where `writeToken = <partitionId>-<stageId>-<taskAttemptId>`
(`FSUtils.makeWriteToken`) and
`taskAttemptId` is Spark's cluster-wide monotonically increasing task
attempt counter (not a
small per-task retry index).
## 1. Whole-bucket mirror (S4 dominant, sub-pattern A)
**Root cause:** task/stage re-execution (shuffle-service off +
decommissioning on; retries
un-tuned; speculation off) re-materializes an insert bucket into a second,
freshly-generated
`fileId`. Marker-based reconciliation is defeated by
`rollback.using.markers=false` +
`metadata.enable=false`, so the mirror survives inside the same commit.
**Source:** `duplicate_guid_field_details_qa_sig2only_202607.csv`,
`w_blc_edgar_other_01`, rows 2–9
(raw rows, all fields):
```
_hoodie_commit_time,_hoodie_commit_seqno,_hoodie_record_key,_hoodie_partition_path,_hoodie_file_name,guid,collectionname,sequenceid,operationtime,operationstartstage,operationendstage,delete
20260723205405966,20260723205405966_103_79016,I48dcc76b0e4c407bb4623377579f92bc,w_blc_edgar_other_01,d3b8b8ae-b9e4-4598-acd3-a998f8b0ccba-0_3251-772-775680_20260802145031626.parquet,I48dcc76b0e4c407bb4623377579f92bc,w_blc_edgar_other_01,263041061,1784838393273,0,32117,false
20260723205405966,20260723205405966_102_85186,I48dcc76b0e4c407bb4623377579f92bc,w_blc_edgar_other_01,b3ed7b64-38eb-49df-837c-1aa4a2b2a6e9-0_3250-772-775679_20260802145031626.parquet,I48dcc76b0e4c407bb4623377579f92bc,w_blc_edgar_other_01,263041061,1784838393273,0,32117,false
20260722205053988,20260722205053988_512_43359,I82b72234efce4f949198d69cd10411c8,w_blc_edgar_other_01,d3b8b8ae-b9e4-4598-acd3-a998f8b0ccba-0_3251-772-775680_20260802145031626.parquet,I82b72234efce4f949198d69cd10411c8,w_blc_edgar_other_01,262918552,1784752956833,0,32110,false
20260722205053988,20260722205053988_511_42122,I82b72234efce4f949198d69cd10411c8,w_blc_edgar_other_01,b3ed7b64-38eb-49df-837c-1aa4a2b2a6e9-0_3250-772-775679_20260802145031626.parquet,I82b72234efce4f949198d69cd10411c8,w_blc_edgar_other_01,262918552,1784752956833,0,32110,false
20260731211110564,20260731211110564_186_161978,Ia40cab33acd141fa8b4e607331887ec8,w_blc_edgar_other_01,d3b8b8ae-b9e4-4598-acd3-a998f8b0ccba-0_3251-772-775680_20260802145031626.parquet,Ia40cab33acd141fa8b4e607331887ec8,w_blc_edgar_other_01,263779206,1785530866483,0,32197,false
20260731211110564,20260731211110564_185_114405,Ia40cab33acd141fa8b4e607331887ec8,w_blc_edgar_other_01,b3ed7b64-38eb-49df-837c-1aa4a2b2a6e9-0_3250-772-775679_20260802145031626.parquet,Ia40cab33acd141fa8b4e607331887ec8,w_blc_edgar_other_01,263779206,1785530866483,0,32197,false
20260731205047416,20260731205047416_95_126230,Ia433f5d02ffa11ecbea4f0dc9fb69570,w_blc_edgar_other_01,d3b8b8ae-b9e4-4598-acd3-a998f8b0ccba-0_3251-772-775680_20260802145031626.parquet,Ia433f5d02ffa11ecbea4f0dc9fb69570,w_blc_edgar_other_01,263778589,1785530546535,0,32196,false
20260731205047416,20260731205047416_94_129950,Ia433f5d02ffa11ecbea4f0dc9fb69570,w_blc_edgar_other_01,b3ed7b64-38eb-49df-837c-1aa4a2b2a6e9-0_3250-772-775679_20260802145031626.parquet,Ia433f5d02ffa11ecbea4f0dc9fb69570,w_blc_edgar_other_01,263778589,1785530546535,0,32196,false
```
The identical fileId pair (`d3b8b8ae.../3251` and `b3ed7b64.../3250`) recurs
for four different
guids across four different commits spanning 2026-07-22 to 2026-07-31 — same
`_hoodie_file_name`
pair every time, only the
guid/`_hoodie_commit_time`/`sequenceid`/`operationtime` change.
**Reading:** both write tokens share stage `772` with *consecutive*
`taskAttemptId`s (775679,
775680 — ran essentially back-to-back) but *different* `partitionId`s (3250
vs 3251). A
same-partition task retry would keep partitionId fixed and only bump the
attempt id; different
partitionIds under the same stage is the signature of a whole-**stage**
recompute (lost
executor's shuffle output isn't recoverable with the external shuffle
service off, forcing Spark
to recompute the entire stage rather than just the missing task). The
recompute reassigns
physical partition numbers, so the same logical insert bucket re-emerges
under a new partition id
and — because it's tagged as INSERT — a brand-new `fileId` mirroring the
original almost
row-for-row.
**Persistence:** the same fileId pair (3250/3251) appears identically across
four commits
spanning 2026-07-22 to 2026-07-31. This is not four separate
mirror-origination events — it's one
origination event, after which every ordinary subsequent update to any guid
in that bucket
dutifully updates *both* copies forever, because per-file-group
compaction/cleaning plus no
global index means no process ever collapses a cross-file-group key (§7 of
the investigation).
This is why Athena's scan of `other_01` found ~94% guid overlap between file
groups 3250/3251
rather than a single clean pair.
## 2. Hub-scatter (S4 dominant, sub-pattern B)
**Root cause:** MoR log-resident keys are false negatives against the
base-file Bloom index
(compaction is 6-hour, time-only triggered) → misclassified as inserts →
routed to a neighbor
file group within the same commit.
**Source:** `duplicate_guid_field_details_qa_sig2only.csv`,
`UK_SMG_EUIMAGES_03`, commit
`20260605084155211`, rows 2–30+ (dozens of sequential guids
`I07372260605011...` through
`I0737226E605011...`). Raw rows, all fields (first ten pairs shown; the
pattern continues
identically for the remaining guids in this commit):
```
_hoodie_commit_time,_hoodie_commit_seqno,_hoodie_record_key,_hoodie_partition_path,_hoodie_file_name,guid,collectionname,sequenceid,operationtime,operationstartstage,operationendstage,delete
20260605084155211,20260605084155211_950_385,I07372260605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372260605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_33,I07372260605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372260605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_24,I07372261605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372261605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_409,I07372261605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372261605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_89,I07372262605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372262605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_361,I07372262605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372262605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_404,I07372263605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372263605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_80,I07372263605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372263605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_415,I07372264605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372264605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_20,I07372264605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372264605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_401,I07372265605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372265605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_81,I07372265605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372265605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_21,I07372266605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372266605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_366,I07372266605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372266605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_381,I07372267605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372267605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_66,I07372267605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372267605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_51,I07372268605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372268605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_423,I07372268605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372268605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_950_395,I07372269605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,8756da3c-4612-4d22-9fc4-fbe7a930d71e-0_1-39-45582_20260605084558630.parquet,I07372269605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
20260605084155211,20260605084155211_951_63,I07372269605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,5956159a-4dc9-4c2a-a5d7-2067ff45ba18-0_464-9588-11587230_20260610092022129.parquet,I07372269605011F19EC7A47E112196E9,UK_SMG_EUIMAGES_03,259036809,1780603319010,0,221835,false
```
Every guid in this commit alternates between the same two fileIds — hub A
(`8756da3c...`,
token `1-39-45582`, filename-instant `20260605084558630`, close to the
commit) and hub B
(`5956159a...`, token `464-9588-11587230`, filename-instant
`20260610092022129`, five days
later).
**Reading:** unlike pattern 1, these write tokens are not adjacent — stage
39 vs stage 9588,
wildly different partition/attempt ids — so `5956159a` is not a sibling task
from the same
micro-batch. It's a long-lived, pre-existing file group ("hub") that this
commit scattered a
batch of individually misrouted inserts into. The 5-day-later filename
timestamp on `5956159a` is
the tell: that's the *next* compaction physically rewriting the hub's base
file to fold in these
log-resident entries, while each row's own `_hoodie_commit_time` metadata
correctly preserves the
original June 5 logical write. Consistent with: these keys were sitting in
an uncompacted MoR log
(compaction is time-triggered, not immediate), the base-file Bloom filter
hadn't absorbed them
yet, the index falsely reported "not found," and each key — one at a time,
not as one atomic
bucket copy — was routed as a fresh insert into the neighbor hub file group.
Matches the earlier `33_02`/hub `7b66daa1` and `23mm`/hub `3bb7805c`
findings from the original
investigation — same shape, different collection.
## 3. Cross-commit replay (S5, Sig #1) — not present in either CSV
Both CSVs are scoped to `sig2only` (same-commit dups) by name and content —
every row pair shares
one `_hoodie_commit_time`. No cross-commit example exists in these two
files; a fresh Athena pull
filtered to `_hoodie_commit_time` pairs that differ for the same guid would
be needed for a
CSV-backed example.
The only documented Sig #1 example remains the narrative one from the
original investigation:
`33_02`, 8 guids, commits `20260604180024505` and `20260604181909526` (~19
minutes apart),
identical `sequenceid=259023002` / `operationtime=1780594587500` /
`operationendstage=127417`
across all 8 guids in both commits — the "identical per-batch triple"
signature that pins this as
a single-writer replay (the whole already-committed window got re-driven),
not two genuinely
concurrent writers.
**Illustrative reconstruction (NOT raw Athena output):** the original
investigation preserved the
aggregate facts above (commit times, `sequenceid`, `operationtime`,
`operationendstage`, guid
count = 8) but not the actual guid strings or file names in a table this
document has access to.
The rows below use the real, documented aggregate values; the guid strings
and `_hoodie_file_name`
values are synthetic placeholders built to match the pattern's shape (two
commits ~19 minutes
apart, one shared file per commit since a replay of an already-correct batch
would land in
whatever file group the index routes it to on the second commit — plausibly
a *different* file
group than the original if the second run's insert/update routing differs).
Treat this block as a
diagram of the pattern, not as verified evidence — a real example still
needs a fresh Athena pull
filtered to guids whose `_hoodie_commit_time` values differ.
```
_hoodie_commit_time,_hoodie_commit_seqno,_hoodie_record_key,_hoodie_partition_path,_hoodie_file_name,guid,collectionname,sequenceid,operationtime,operationstartstage,operationendstage,delete
20260604180024505,20260604180024505_310_50001,I3302REPLAY0000000000000000000001,33_02,7a1c9e02-11ab-4a3d-9e77-001122334401-0_310-640-9001001_20260604180512033.parquet,I3302REPLAY0000000000000000000001,33_02,259023002,1780594587500,0,127417,false
20260604181909526,20260604181909526_318_50101,I3302REPLAY0000000000000000000001,33_02,c4de88f1-2b3c-4f56-8a90-556677889901-0_318-648-9012045_20260604182344871.parquet,I3302REPLAY0000000000000000000001,33_02,259023002,1780594587500,0,127417,false
20260604180024505,20260604180024505_310_50002,I3302REPLAY0000000000000000000002,33_02,7a1c9e02-11ab-4a3d-9e77-001122334401-0_310-640-9001001_20260604180512033.parquet,I3302REPLAY0000000000000000000002,33_02,259023002,1780594587500,0,127417,false
20260604181909526,20260604181909526_318_50102,I3302REPLAY0000000000000000000002,33_02,c4de88f1-2b3c-4f56-8a90-556677889901-0_318-648-9012045_20260604182344871.parquet,I3302REPLAY0000000000000000000002,33_02,259023002,1780594587500,0,127417,false
20260604180024505,20260604180024505_310_50003,I3302REPLAY0000000000000000000003,33_02,7a1c9e02-11ab-4a3d-9e77-001122334401-0_310-640-9001001_20260604180512033.parquet,I3302REPLAY0000000000000000000003,33_02,259023002,1780594587500,0,127417,false
20260604181909526,20260604181909526_318_50103,I3302REPLAY0000000000000000000003,33_02,c4de88f1-2b3c-4f56-8a90-556677889901-0_318-648-9012045_20260604182344871.parquet,I3302REPLAY0000000000000000000003,33_02,259023002,1780594587500,0,127417,false
20260604180024505,20260604180024505_310_50004,I3302REPLAY0000000000000000000004,33_02,7a1c9e02-11ab-4a3d-9e77-001122334401-0_310-640-9001001_20260604180512033.parquet,I3302REPLAY0000000000000000000004,33_02,259023002,1780594587500,0,127417,false
20260604181909526,20260604181909526_318_50104,I3302REPLAY0000000000000000000004,33_02,c4de88f1-2b3c-4f56-8a90-556677889901-0_318-648-9012045_20260604182344871.parquet,I3302REPLAY0000000000000000000004,33_02,259023002,1780594587500,0,127417,false
```
(4 of the 8 guids shown; the remaining 4 follow the same shape — same two
commit times, same
`sequenceid`/`operationtime`/`operationendstage` triple, one row per commit.)
**Reading:** unlike patterns 1–2, the two rows for each guid carry
*different*
`_hoodie_commit_time` values (`20260604180024505` vs `20260604181909526`,
~19 minutes apart) —
this is the cross-commit signature, not the same-commit signature. What pins
this as a
single-writer *replay* rather than two genuinely concurrent writers is that
`sequenceid`,
`operationtime`, and `operationendstage` are identical across every guid in
*both* commits: this
is not two different batches independently touching the same guids (which
would show different
per-batch counters), it's the literal same batch's counters appearing twice,
because the second
commit is a full re-drive of a batch that had already committed successfully
once.
**Root cause:** Hudi's commit precedes the DynamoDB checkpoint;
per-collection commits are
non-atomic; the checkpoint is all-or-nothing. A single failing collection
later in the batch
forces a replay of the whole window of already-committed collections. The
replayed copy survives
for the same reason as patterns 1 and 2: no global index or cross-file-group
collapse ever heals
it once written.
## Triage validity
`_hoodie_commit_time` cleanly separates one-commit write faults (patterns
1–2, S4) from
two-commit re-drive faults (pattern 3, S5). The per-batch triple
(`sequenceid` /
`operationtime` / `operationendstage`) separates single-writer replay from
the genuinely
concurrent path that a non-atomic lock + `StepConcurrencyLevel=2` could in
principle open (ruled
out for all observed data — no distinct-batch-counter pairs seen).
## Why nothing self-heals
Per-file-group compaction merges log into base within one file group only,
and the cleaner drops
old versions per file group — neither ever runs the index or reasons about a
key across multiple
file groups. Combined with no global index (`hoodie.metadata.enable=false`,
partition-scoped
`SIMPLE`/`BLOOM` index), no process in this pipeline ever collapses a
cross-file-group duplicate
once it's committed. This is why all three patterns, once introduced,
persist indefinitely rather
than resolving on their own over subsequent compaction/cleaning cycles.
--
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]