Skip to content

[fix](be) Fail the scan when a deletion vector file is missing - #69578

Merged
morningman merged 1 commit into
apache:masterfrom
morningman:b2m-t14i-paimon-dv-missing-fails
Oct 26, 2026
Merged

morningman merged 1 commit into
apache:masterfrom
morningman:b2m-t14i-paimon-dv-missing-fails

Conversation

@morningman

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #69571, #68142

Problem Summary:

In short. When a Paimon split's deletion vector file is missing, the native readers report NOT_FOUND, and both file scanners take that for a missing data file and skip the whole range. The query succeeds and silently drops every surviving row of the data file. This PR reports a missing deletion vector as an error, so the query fails instead. A missing data file is still skipped as before.

Background. A Paimon split for a table with deletion vectors names a data file and the deletion vector that marks its deleted rows. The native readers open the deletion vector before they read a row: V1 in PaimonParquetReader / PaimonOrcReader::_init_deletion_vector, V2 in TableReader::_parse_delete_predicates, both through DeletionVectorReader. FileScanner and FileScannerV2 skip a range whose reader reports NOT_FOUND while ignore_not_found_file_in_external_table is on (the default). That setting exists for file listings that are cached by the FE and may name a file that has since been removed. #68142 already exempted Iceberg ranges, whose files all belong to a committed snapshot.

The problem, and what it cost. A deletion vector is never a stale listing: it belongs to the snapshot that planned its data file. It can still be missing, for example when Paimon expires the snapshot that wrote it while the data file stays live under a newer one, or when it is looked up under a partition name that is never found (#69571). The data file is still there, but the scanners skip the whole range, so its surviving rows are dropped together with the deleted ones. The BE logs Skip missing file with the path of the data file, which exists. Paimon's own reader, the JNI reader and the Rust reader fail on the same split instead.

How this PR fixes it. DeletionVectorReader, which opens every deletion vector for V1 Paimon, V1 Iceberg and the V2 table reader, now reports a missing file as INTERNAL_ERROR with the path of the deletion vector, both when it opens the file and when it reads it. The scanners only skip NOT_FOUND, so the query fails. A missing data file still reports NOT_FOUND and is skipped as before, with or without a deletion vector. For Iceberg, which already failed on any missing file (#68142), only the error code of a missing deletion vector changes.

One boundary: if the data file is gone as well, the result depends on which file the reader reaches first. FileScannerV2 parses the deletion vector while it prepares the split, before it opens the data file, so such a split now fails; in the local run below V1 Parquet failed too, while V1 ORC, which opens the data file first, still skipped it.

Results. A local cluster with a file:// Paimon catalog, two append tables (Parquet and ORC, deletion-vectors.enabled = true), five rows each, the first row deleted through a deletion vector the way a Spark DELETE persists it:

Files on storage Reader Before After
data file and deletion vector V1 / V2, Parquet / ORC 2,3,4,5 2,3,4,5
data file only (deletion vector removed) V1 / V2, Parquet / ORC empty result, query succeeds; Skip missing file: path=<data file> in be.INFO [INTERNAL_ERROR]Deletion vector file is missing: path .../index/index-...-0, failed to get file size ...: No such file or directory
deletion vector only (data file removed) V1 / V2, Parquet / ORC empty result empty result (unchanged)
neither V2, V1 Parquet empty result the deletion vector error above
neither V1 ORC empty result empty result (unchanged)

Release note

A Paimon query now fails when a deletion vector file of its snapshot is missing, instead of silently skipping the data file it applies to.

Check List (For Author)

  • Test
    • Regression test
    • Unit Test
      • be/test/format/table/paimon_reader_test.cpp (new): the V1 Parquet and ORC readers report INTERNAL_ERROR with the path for a missing deletion vector file. These restore the V1 deletion vector tests of [fix](be) Release deletion vector rows on read errors #65351 that went away with paimon_cpp_reader_test.cpp in [chore](paimon) remove paimon-cpp dependency #67385.
      • be/test/format_v2/table/paimon_reader_test.cpp: the V2 reader fails the split for a missing deletion vector file, and a missing data file with an existing deletion vector still reports NOT_FOUND.
      • With the change reverted, the three missing-deletion-vector tests fail and the data-file test still passes. The Iceberg helper test for a missing deletion vector, PaimonReaderTest, the V2 Iceberg deletion vector tests and FileScanRangeUtilsTest pass (51 tests).
    • Manual test (add detailed scripts or steps below)
      • The cluster run in the table above, before and after replacing doris_be.
    • No need to test or manual test.
  • Behavior changed:
    • No.
    • Yes. A Paimon query whose deletion vector file is missing fails instead of returning a result without the rows of that data file. A missing data file is still skipped.
  • Does this need documentation?
    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69571

Problem Summary: When a Paimon split's deletion vector file cannot be
found, both native readers report NOT_FOUND when they open it (V1
PaimonParquetReader / PaimonOrcReader::_init_deletion_vector, V2
TableReader::_parse_delete_predicates). FileScanner and FileScannerV2
treat NOT_FOUND as a data file that a stale listing still names, and
with ignore_not_found_file_in_external_table (default true) they skip
the whole range. The data file is still there, so its surviving rows
are dropped together with the deleted ones, and the query returns a
successful but incomplete result. Paimon's own reader, the JNI reader
and the Rust reader fail instead. A deletion vector goes missing when
Paimon expires the snapshot that wrote it while the data file stays
live under a newer one, or when it is looked up under a partition name
that is never found (apache#69571).

A deletion vector belongs to the snapshot that planned its data file,
so it is never a stale listing. DeletionVectorReader, which opens every
deletion vector for V1 Paimon, V1 Iceberg and the V2 table reader, now
reports a missing file as INTERNAL_ERROR naming its path, so the query
fails. A missing data file still reports NOT_FOUND and is skipped as
before. Iceberg ranges already fail on any missing file (apache#68142); only
the error code of a missing Iceberg deletion vector changes.

### Release note

A Paimon query now fails when a deletion vector file of the snapshot is
missing, instead of silently skipping the data file it applies to.

### Check List (For Author)

- Test: Unit Test
    - be/test/format/table/paimon_reader_test.cpp: V1 Parquet and ORC
      readers report INTERNAL_ERROR with the path for a missing
      deletion vector file
    - be/test/format_v2/table/paimon_reader_test.cpp: the V2 reader
      fails the split for a missing deletion vector file, and a missing
      data file with an existing deletion vector still reports NOT_FOUND
- Behavior changed: Yes. A missing Paimon deletion vector file fails the
  query instead of skipping its data file.
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

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.

No actionable regression found in the three changed files at d278820. Static review supports the fix: a missing deletion vector becomes a query error at both open and read boundaries, preventing the scanners from silently discarding the surviving rows of its data file. There are no existing inline findings to carry forward and no additional user-provided focus points.

Execution paths checked: V1 FileScanner -> GenericReader after-init hook -> Paimon ORC/Parquet DV loader -> DeletionVectorReader; V2 FileScannerV2 -> native Paimon child -> TableReader split preparation -> the same DV reader. The shared Iceberg helper was also traced. Both scanner versions skip NOT_FOUND only; the converted INTERNAL_ERROR is propagated.

Critical checkpoint conclusions:

  1. Goal and tests: both DV I/O boundaries are covered by the conversion. Four added unit cases assert V1 ORC/Parquet missing-DV errors, V2 missing-DV failure, and V2 missing-data NOT_FOUND after a valid DV. These tests were inspected, not executed.
  2. Scope and reuse: one private helper at the shared DV layer is a focused change and avoids duplicating connector policy.
  3. Concurrency and locking: no thread, lock, atomic, or shared state is added. Existing per-key cache factory locking and scanner task context are unchanged; the adapter adds no lock-order dependency.
  4. Lifecycle and static initialization: Status owns its message; formatting consumes it before destruction. Failed factories return their status before DV pointer use. Existing reader/context cleanup and cache ownership remain intact, with no new static initializer or ownership cycle.
  5. Configuration: no new configuration. The existing ignore-not-found option still governs missing data-file statuses.
  6. Compatibility: no public interface, storage format, or wire protocol change. The DV error-code change is intentional. Iceberg already fails on missing snapshot files, and its existing helper test asserts failure/path rather than NOT_FOUND. No new glibc API or build/link dependency is introduced.
  7. Thrift/protobuf ids: not applicable; no shared definitions changed.
  8. Parallel paths: the shared adapter covers native V1 ORC/Parquet, V2 table readers, and Iceberg. JNI/Rust dispatch is unchanged. Both eager and deferred I/O errors reach the adapter.
  9. Conditions and comments: the exact NOT_FOUND branch has a concrete F/D1 explanation. Other statuses, stop/EOF behavior, and short-read errors retain their existing treatment. ORC's text-based missing-file conversion runs before the Paimon after-init hook and cannot reclassify this DV error.
  10. Test coverage: the new test file is included by recursive CMake collection. Existing DV parsing, Iceberg missing-file, and scanner status-gate tests supplement the new cases. Local open-time errors are covered; deferred read errors, remote/cache paths, full scanner option on/off integration, and V2 ORC remain test gaps.
  11. Test-result artifacts: no golden outputs changed. The new assertions agree with the inspected control flow; runtime pass/fail was not independently verified.
  12. Observability: the new diagnostic identifies the deletion-vector path and preserves the underlying message. Existing timers/counters remain; no added per-row logging or new metric is needed for this classification change.
  13. Transactions and persistence: no commit, version visibility, delete bitmap, EditLog, replay, or failover protocol is modified. Returning the read error protects snapshot result correctness.
  14. Data writes and atomicity: production changes only read-error classification; no write or recovery protocol changes. Existing resource cleanup still applies on failure.
  15. FE/BE variables: none added or changed; no additional forwarding path is required.
  16. Performance and memory: success adds a status check outside row loops. Formatting occurs only on missing files; there is no new file-content copy, allocation proportional to data, or remote I/O.
  17. Additional recheck: cache misses recover internally before final errors reach the adapter; decoded/byte-cache hits can use available DV contents. Existing partition pruning and trusted metadata COUNT paths need no DV bytes. If both data and DV are absent, the first attempted open determines skip versus failure, as disclosed in the PR; no new silent-data-loss path was found.

Limits: this was a static-only review. No builds, tests, cluster runs, source changes, or final ELF ABI audits were performed. The test gaps above are validation limits, with no substantiated unresolved code defect. All changed files and both initial risk questions were checked and disposed of with evidence.

Review converged in 2 rounds: both independent reviewers returned NO_NEW_VALUABLE_FINDINGS on the unchanged final payload. New inline findings: 0. Existing confirmed P0/P1 findings: 0.

Review converged after 2 round(s).

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28205 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit d278820aeb3af75c1125ff57ed869f288b396c2f, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17589	5159	5058	5058
q2	2110	310	257	257
q3	10257	1374	817	817
q4	4677	466	335	335
q5	7425	794	536	536
q6	181	183	151	151
q7	742	799	592	592
q8	9252	1470	1042	1042
q9	5460	4204	4141	4141
q10	6825	1298	1019	1019
q11	417	245	230	230
q12	632	405	277	277
q13	18128	3490	2979	2979
q14	270	268	243	243
q15	q16	727	710	672	672
q17	1180	760	582	582
q18	6946	6176	6094	6094
q19	1106	950	597	597
q20	403	336	223	223
q21	4825	2107	2282	2107
q22	344	292	253	253
Total cold run time: 99496 ms
Total hot run time: 28205 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5611	5597	5627	5597
q2	649	569	540	540
q3	5224	5390	4975	4975
q4	1989	2148	1536	1536
q5	5328	5227	5502	5227
q6	252	194	145	145
q7	2314	2037	1878	1878
q8	3023	2698	2678	2678
q9	8116	8163	8107	8107
q10	3738	3662	3277	3277
q11	612	446	589	446
q12	702	715	508	508
q13	3512	3926	3175	3175
q14	349	348	308	308
q15	q16	696	712	641	641
q17	8405	7481	6953	6953
q18	12954	12072	12938	12072
q19	899	768	782	768
q20	2147	2158	1914	1914
q21	5605	4602	4672	4602
q22	493	438	402	402
Total cold run time: 72618 ms
Total hot run time: 65749 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 150135 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit d278820aeb3af75c1125ff57ed869f288b396c2f, data reload: false

query5	4308	590	445	445
query6	420	207	200	200
query7	4804	430	220	220
query8	326	171	152	152
query9	8757	3062	3073	3062
query10	453	309	257	257
query11	6026	3866	3559	3559
query12	150	105	92	92
query13	1249	444	307	307
query14	6128	3919	3626	3626
query14_1	3404	3456	3382	3382
query15	210	206	183	183
query16	951	415	411	411
query17	837	662	540	540
query18	2422	423	313	313
query19	199	163	134	134
query20	84	85	81	81
query21	209	129	106	106
query22	12902	13022	12735	12735
query23	13485	12800	12414	12414
query23_1	12456	12526	12602	12526
query24	7365	806	454	454
query24_1	465	456	479	456
query25	511	394	328	328
query26	1288	253	131	131
query27	2768	432	266	266
query28	4620	1906	1896	1896
query29	1548	560	427	427
query30	295	215	180	180
query31	1002	930	799	799
query32	138	90	88	88
query33	501	293	230	230
query34	874	854	492	492
query35	721	757	636	636
query36	852	816	711	711
query37	124	93	83	83
query38	1788	1703	1645	1645
query39	786	785	753	753
query39_1	733	728	737	728
query40	211	112	91	91
query41	65	62	62	62
query42	83	80	80	80
query43	344	363	302	302
query44	1302	670	669	669
query45	173	165	161	161
query46	820	961	554	554
query47	2954	2960	2692	2692
query48	290	298	219	219
query49	575	408	304	304
query50	696	276	205	205
query51	10531	10365	10473	10365
query52	80	110	69	69
query53	195	211	148	148
query54	239	190	181	181
query55	65	63	67	63
query56	226	231	207	207
query57	1643	1605	1541	1541
query58	276	260	280	260
query59	2141	2143	2004	2004
query60	291	238	222	222
query61	147	142	141	141
query62	399	327	282	282
query63	185	158	160	158
query64	2760	949	768	768
query65	3309	3260	3246	3246
query66	1763	410	315	315
query67	20173	20099	19902	19902
query68	3027	987	610	610
query69	385	289	251	251
query70	831	776	775	775
query71	283	224	205	205
query72	2825	2772	2372	2372
query73	515	563	292	292
query74	4594	4519	4363	4363
query75	2305	2284	1937	1937
query76	2319	1018	616	616
query77	349	377	283	283
query78	9698	9739	9174	9174
query79	912	883	502	502
query80	518	406	336	336
query81	527	311	268	268
query82	578	127	104	104
query83	299	193	173	173
query84	315	119	102	102
query85	817	430	380	380
query86	314	234	230	230
query87	1996	1950	1812	1812
query88	3598	2631	2610	2610
query89	323	275	237	237
query90	1890	170	163	163
query91	157	146	128	128
query92	97	79	82	79
query93	973	990	580	580
query94	466	294	269	269
query95	577	390	307	307
query96	650	479	227	227
query97	2446	2444	2308	2308
query98	156	147	147	147
query99	782	763	655	655
Total cold run time: 233400 ms
Total hot run time: 150135 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.31 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit d278820aeb3af75c1125ff57ed869f288b396c2f, data reload: false

query1	0.01	0.01	0.01
query2	0.10	0.05	0.05
query3	0.26	0.08	0.08
query4	1.65	0.09	0.11
query5	0.24	0.25	0.24
query6	1.19	0.69	0.68
query7	0.04	0.01	0.01
query8	0.05	0.04	0.04
query9	0.40	0.34	0.34
query10	0.57	0.61	0.62
query11	0.20	0.14	0.16
query12	0.18	0.15	0.14
query13	0.50	0.49	0.50
query14	1.03	1.01	1.00
query15	0.63	0.61	0.60
query16	0.31	0.34	0.36
query17	1.12	1.13	1.13
query18	0.19	0.20	0.19
query19	2.13	1.97	1.94
query20	0.02	0.01	0.02
query21	15.48	0.19	0.13
query22	4.95	0.05	0.05
query23	16.26	0.30	0.12
query24	2.87	0.57	0.37
query25	0.10	0.07	0.04
query26	1.54	0.21	0.16
query27	0.04	0.04	0.02
query28	3.13	0.74	0.36
query29	12.50	4.08	3.31
query30	0.29	0.14	0.10
query31	2.78	0.56	0.32
query32	3.22	0.60	0.50
query33	3.22	3.25	3.23
query34	15.34	3.96	3.34
query35	3.38	3.34	3.32
query36	0.55	0.45	0.44
query37	0.09	0.06	0.07
query38	0.06	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.16	0.15
query41	0.08	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.99 s
Total hot run time: 24.31 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (10/10) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 66.60% (32002/48048)
Line Coverage 51.41% (337657/656807)
Region Coverage 46.75% (273106/584196)
Branch Coverage 48.49% (128735/265486)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (10/10) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.05% (35865/46549)
Line Coverage 62.30% (404822/649758)
Region Coverage 58.70% (341706/582078)
Branch Coverage 59.67% (158027/264837)

@morningman

Copy link
Copy Markdown
Contributor Author

run cloud_p0

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (10/10) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.05% (35865/46549)
Line Coverage 62.30% (404822/649758)
Region Coverage 58.70% (341706/582078)
Branch Coverage 59.67% (158027/264837)

@morningman
morningman merged commit 6b69bc4 into apache:master Oct 26, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants