Skip to content

[fix](inverted index) Close empty index files through the FileWriter lifecycle - #67702

Closed
luwei16 wants to merge 2 commits into
apache:masterfrom
luwei16:fix/core-6140-empty-index-master
Closed

luwei16 wants to merge 2 commits into
apache:masterfrom
luwei16:fix/core-6140-empty-index-master

Conversation

@luwei16

@luwei16 luwei16 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: N/A

Related PR: N/A

Problem Summary: An all-NULL Variant column creates no logical index directory, but its V2/V3 FileWriter has already been created. The empty-index close path only closed StreamSink, S3 and Packed writers, leaving LocalFileWriter and opaque wrappers open. Local writer destruction could consequently remove the empty file. Dispatch both close phases through FileWriter for every non-null, non-CLOSED writer, preserving the asynchronous protocol and returning close errors. Add implementation-independent lifecycle coverage, verify Local empty file persistence and exactly one StreamSink EOS/S3 empty-object commit, and make the Variant regression enforce its expected query error.

Release note

Persist empty V2/V3 index files for all FileWriter implementations, including wrappers, while preserving V1 and already-closed writer behavior.

Check List (For Author)

  • Test: Unit Test / Regression test / Manual test
    • 64 focused ASAN BE tests passed; the new baseline suite first demonstrated 13 failures before the fix.
    • test_variant_empty_index_file regression passed on the locally built cluster.
    • ASAN BE/FE product build, clang-format 16, check-format and build hygiene passed.
    • clang-tidy was attempted; an existing unmatched NOLINTEND in be/src/core/types.h:576 prevents a complete pass.
  • Behavior changed: Yes; explicitly close empty index files for every writer implementation and skip already-closed writers.
  • Does this need documentation: No

### What problem does this PR solve?

Issue Number: N/A

Related PR: N/A

Problem Summary: An all-NULL Variant column creates no logical index directory,
but its V2/V3 FileWriter has already been created. The empty-index close path
only closed StreamSink, S3 and Packed writers, leaving LocalFileWriter and
opaque wrappers open. Local writer destruction could consequently remove the
empty file. Dispatch both close phases through FileWriter for every non-null,
non-CLOSED writer, preserving the asynchronous protocol and returning close
errors. Add implementation-independent lifecycle coverage, verify Local empty
file persistence and exactly one StreamSink EOS/S3 empty-object commit, and
make the Variant regression enforce its expected query error.

### Release note

Persist empty V2/V3 index files for all FileWriter implementations, including
wrappers, while preserving V1 and already-closed writer behavior.

### Check List (For Author)

- Test: Unit Test / Regression test / Manual test
    - 64 focused ASAN BE tests passed; the new baseline suite first demonstrated 13 failures before the fix.
    - test_variant_empty_index_file regression passed on the locally built cluster.
    - ASAN BE/FE product build, clang-format 16, check-format and build hygiene passed.
    - clang-tidy was attempted; an existing unmatched NOLINTEND in be/src/core/types.h:576 prevents a complete pass.
- Behavior changed: Yes; explicitly close empty index files for every writer implementation and skip already-closed writers.
- Does this need documentation: No
@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?

@luwei16

luwei16 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@luwei16 luwei16 changed the title [fix](be) Close empty index files through the FileWriter lifecycle [fix](inverted index) Close empty index files through the FileWriter lifecycle Sep 9, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Dropping the last inverted index rebuilds the V2 index file without any logical index directories. Explicitly closing the writer preserves an empty file, so the reader now returns E-6004 instead of E-6003. Check the empty-file status and message in this regression, while keeping the missing-file expectation for indexes that have not been built.

### Release note

None

### Check List (For Author)

- Test: Regression test
    - test_drop_index_with_format_v2 and test_add_build_index_with_format_v2 passed locally on both Apache Doris master and SelectDB 3.1.
- Behavior changed: No (test expectations follow the empty-file close fix)
- Does this need documentation: No
@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17668	2998	3000	2998
q2	2095	257	228	228
q3	10233	910	535	535
q4	4675	247	204	204
q5	7680	555	379	379
q6	140	116	95	95
q7	521	492	389	389
q8	9478	853	949	853
q9	3965	2411	2413	2411
q10	6576	865	737	737
q11	402	206	182	182
q12	669	265	205	205
q13	18271	1526	1173	1173
q14	165	150	142	142
q15	q16	451	388	369	369
q17	1427	947	825	825
q18	3113	2248	2250	2248
q19	1111	899	795	795
q20	382	289	205	205
q21	5382	1614	1891	1614
q22	325	271	228	228
Total cold run time: 94729 ms
Total hot run time: 16815 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3339	3277	3260	3260
q2	512	389	360	360
q3	2213	2242	2315	2242
q4	1176	1173	885	885
q5	2178	2097	2105	2097
q6	170	123	93	93
q7	1030	908	868	868
q8	1565	1371	1390	1371
q9	3092	3055	3061	3055
q10	1851	1788	1629	1629
q11	350	268	246	246
q12	452	437	343	343
q13	1503	1516	1149	1149
q14	176	172	166	166
q15	q16	396	389	370	370
q17	3579	3281	3248	3248
q18	4770	4467	4753	4467
q19	875	955	874	874
q20	1008	983	818	818
q21	3784	3201	3126	3126
q22	399	340	335	335
Total cold run time: 34418 ms
Total hot run time: 31002 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 81593 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 1269a661736aa0599c3c87050116b5afc9b68c70, data reload: false

query5	4260	406	338	338
query6	386	132	124	124
query7	4969	412	221	221
query8	290	122	121	121
query9	8709	2870	2876	2870
query10	391	227	180	180
query11	5383	1041	924	924
query12	124	74	69	69
query13	1210	438	320	320
query14	6040	2205	2085	2085
query14_1	1961	1960	1952	1952
query15	176	122	118	118
query16	919	373	356	356
query17	793	468	361	361
query18	2332	332	236	236
query19	161	137	110	110
query20	74	69	71	69
query21	199	102	88	88
query22	5365	5426	5367	5367
query23	6748	6194	6191	6191
query23_1	6078	6199	5988	5988
query24	7301	1099	749	749
query24_1	759	755	777	755
query25	396	273	235	235
query26	1236	228	130	130
query27	2787	397	253	253
query28	4710	1487	1491	1487
query29	902	427	328	328
query30	245	157	130	130
query31	823	399	332	332
query32	125	78	69	69
query33	447	216	172	172
query34	1008	852	462	462
query35	381	394	345	345
query36	587	557	535	535
query37	120	80	69	69
query38	988	840	794	794
query39	492	507	501	501
query39_1	469	467	460	460
query40	199	88	73	73
query41	55	51	51	51
query42	77	71	69	69
query43	239	235	202	202
query44	1000	539	534	534
query45	107	107	102	102
query46	764	842	515	515
query47	758	765	707	707
query48	323	310	211	211
query49	534	242	176	176
query50	728	255	195	195
query51	8096	8107	7945	7945
query52	73	68	64	64
query53	215	197	148	148
query54	212	186	157	157
query55	74	65	66	65
query56	206	168	170	168
query57	684	778	671	671
query58	220	182	167	167
query59	1196	1221	1071	1071
query60	234	199	181	181
query61	154	143	157	143
query62	361	207	181	181
query63	177	144	145	144
query64	2740	700	630	630
query65	1608	1578	1662	1578
query66	1792	262	194	194
query67	10157	9965	9584	9584
query68	3021	1119	696	696
query69	357	223	190	190
query70	644	626	620	620
query71	242	178	167	167
query72	2333	1723	1405	1405
query73	643	602	336	336
query74	2007	1231	1149	1149
query75	1166	1106	942	942
query76	2361	713	482	482
query77	259	265	204	204
query78	3974	3765	3198	3198
query79	2268	804	587	587
query80	1595	333	275	275
query81	489	159	130	130
query82	617	130	99	99
query83	271	210	191	191
query84	285	106	91	91
query85	787	350	277	277
query86	377	185	183	183
query87	1013	982	895	895
query88	2771	2119	2119	2119
query89	286	189	176	176
query90	2001	127	129	127
query91	126	119	95	95
query92	80	61	71	61
query93	1400	1082	694	694
query94	644	242	227	227
query95	515	303	219	219
query96	812	560	298	298
query97	1075	1032	1023	1023
query98	140	132	132	132
query99	415	341	312	312
Total cold run time: 177732 ms
Total hot run time: 81593 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.08	0.04	0.04
query3	0.25	0.11	0.10
query4	1.60	0.09	0.11
query5	0.16	0.15	0.16
query6	1.28	0.70	0.67
query7	0.03	0.01	0.00
query8	0.04	0.03	0.03
query9	0.28	0.21	0.22
query10	0.35	0.36	0.34
query11	0.17	0.12	0.12
query12	0.15	0.12	0.12
query13	0.30	0.30	0.31
query14	0.44	0.45	0.44
query15	0.36	0.36	0.35
query16	0.23	0.22	0.22
query17	0.65	0.67	0.65
query18	0.16	0.15	0.16
query19	1.19	1.09	1.07
query20	0.01	0.02	0.01
query21	15.44	0.14	0.11
query22	5.08	0.04	0.04
query23	16.21	0.25	0.11
query24	3.09	0.31	0.27
query25	0.10	0.04	0.04
query26	0.78	0.18	0.13
query27	0.03	0.04	0.02
query28	3.66	0.56	0.26
query29	12.47	3.16	2.58
query30	0.28	0.11	0.12
query31	2.75	0.37	0.17
query32	3.52	0.30	0.22
query33	1.35	1.39	1.44
query34	15.37	2.16	1.77
query35	1.73	1.73	1.72
query36	0.46	0.30	0.29
query37	0.06	0.04	0.04
query38	0.04	0.04	0.04
query39	0.03	0.03	0.02
query40	0.12	0.07	0.07
query41	0.08	0.02	0.02
query42	0.03	0.02	0.03
query43	0.03	0.02	0.03
Total cold run time: 90.44 s
Total hot run time: 14.52 s

@luwei16

luwei16 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17585	3046	3021	3021
q2	2137	257	229	229
q3	10216	892	533	533
q4	4670	267	206	206
q5	7658	583	394	394
q6	145	120	97	97
q7	524	508	390	390
q8	9241	896	897	896
q9	3538	2432	2407	2407
q10	6511	882	705	705
q11	390	202	182	182
q12	620	266	208	208
q13	18146	1572	1198	1198
q14	167	153	145	145
q15	q16	436	399	372	372
q17	1351	823	808	808
q18	3156	2330	2318	2318
q19	1257	899	768	768
q20	390	285	210	210
q21	5614	1653	1852	1653
q22	332	267	230	230
Total cold run time: 94084 ms
Total hot run time: 16970 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3384	3297	3292	3292
q2	499	414	400	400
q3	2283	2403	2202	2202
q4	1220	1203	897	897
q5	2213	2171	2156	2156
q6	171	117	89	89
q7	1064	929	890	890
q8	1582	1393	1398	1393
q9	3240	3208	3198	3198
q10	1919	1847	1655	1655
q11	357	271	262	262
q12	455	433	343	343
q13	1507	1544	1185	1185
q14	167	175	156	156
q15	q16	400	399	366	366
q17	3706	3392	3341	3341
q18	4980	4531	5136	4531
q19	953	856	833	833
q20	1023	1003	863	863
q21	3906	3244	3200	3200
q22	416	341	339	339
Total cold run time: 35445 ms
Total hot run time: 31591 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 82902 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 21a5c2dc1427a8faae2444637c6c37c08c020489, data reload: false

query5	4248	414	350	350
query6	384	137	124	124
query7	4950	416	252	252
query8	292	129	121	121
query9	8699	2852	2813	2813
query10	373	219	184	184
query11	5366	1047	941	941
query12	124	75	69	69
query13	1221	430	323	323
query14	6044	2322	2156	2156
query14_1	2042	2025	2010	2010
query15	175	121	113	113
query16	904	380	362	362
query17	799	456	352	352
query18	2334	318	239	239
query19	161	144	112	112
query20	77	70	72	70
query21	205	108	89	89
query22	5617	5490	5463	5463
query23	6883	6533	6045	6045
query23_1	6396	6154	6198	6154
query24	7320	1096	778	778
query24_1	787	727	823	727
query25	433	303	283	283
query26	1227	238	125	125
query27	2780	411	256	256
query28	4684	1483	1472	1472
query29	910	432	330	330
query30	245	153	129	129
query31	806	406	329	329
query32	123	77	78	77
query33	453	208	178	178
query34	979	849	489	489
query35	403	392	337	337
query36	562	580	568	568
query37	118	80	70	70
query38	1029	874	836	836
query39	492	477	487	477
query39_1	448	457	454	454
query40	201	92	78	78
query41	54	54	52	52
query42	75	73	74	73
query43	243	244	215	215
query44	998	532	529	529
query45	113	109	106	106
query46	783	822	537	537
query47	761	782	712	712
query48	308	297	229	229
query49	528	234	190	190
query50	717	258	198	198
query51	8200	7963	7988	7963
query52	63	71	59	59
query53	190	194	148	148
query54	297	160	145	145
query55	75	58	54	54
query56	215	157	158	157
query57	712	685	624	624
query58	207	165	159	159
query59	1258	1258	1137	1137
query60	229	192	167	167
query61	110	105	116	105
query62	397	208	173	173
query63	173	142	137	137
query64	2705	692	598	598
query65	1671	1672	1664	1664
query66	1812	271	221	221
query67	9932	9823	10060	9823
query68	2726	1182	679	679
query69	331	213	191	191
query70	691	625	593	593
query71	249	175	180	175
query72	2238	1683	1514	1514
query73	643	589	333	333
query74	1577	1255	1171	1171
query75	1208	1133	982	982
query76	2297	715	517	517
query77	259	270	224	224
query78	4064	3795	3388	3388
query79	1214	805	612	612
query80	1220	338	307	307
query81	488	158	136	136
query82	625	130	101	101
query83	323	215	200	200
query84	302	116	96	96
query85	858	395	351	351
query86	388	184	178	178
query87	1037	985	927	927
query88	2790	2112	2139	2112
query89	285	194	172	172
query90	1944	131	126	126
query91	131	119	99	99
query92	78	63	72	63
query93	1328	1083	720	720
query94	622	242	244	242
query95	568	314	230	230
query96	848	581	262	262
query97	1097	1072	1046	1046
query98	142	138	135	135
query99	427	352	319	319
Total cold run time: 176534 ms
Total hot run time: 82902 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.08	0.04	0.04
query3	0.24	0.11	0.11
query4	1.60	0.10	0.08
query5	0.18	0.16	0.16
query6	1.26	0.70	0.68
query7	0.03	0.00	0.00
query8	0.05	0.04	0.03
query9	0.28	0.23	0.23
query10	0.34	0.33	0.36
query11	0.15	0.12	0.11
query12	0.15	0.12	0.13
query13	0.32	0.32	0.30
query14	0.46	0.46	0.46
query15	0.36	0.36	0.34
query16	0.20	0.22	0.24
query17	0.70	0.66	0.69
query18	0.19	0.17	0.16
query19	1.14	1.22	1.21
query20	0.02	0.01	0.01
query21	15.44	0.16	0.11
query22	5.08	0.05	0.04
query23	16.18	0.25	0.10
query24	2.99	0.32	0.25
query25	0.10	0.05	0.03
query26	0.76	0.17	0.12
query27	0.03	0.03	0.03
query28	3.64	0.59	0.28
query29	12.42	3.21	2.60
query30	0.25	0.11	0.12
query31	2.75	0.36	0.18
query32	3.53	0.32	0.23
query33	1.64	1.45	1.40
query34	15.37	2.20	1.84
query35	1.80	1.75	1.79
query36	0.44	0.29	0.29
query37	0.06	0.04	0.04
query38	0.05	0.03	0.03
query39	0.03	0.02	0.02
query40	0.12	0.08	0.07
query41	0.07	0.02	0.02
query42	0.04	0.02	0.02
query43	0.03	0.03	0.03
Total cold run time: 90.57 s
Total hot run time: 14.84 s

@luwei16 luwei16 closed this Sep 10, 2026
airborne12 added a commit that referenced this pull request Sep 16, 2026
…an ones (#67859)

### What problem does this PR solve?

Issue Number: N/A

Related PR: #67702 (closed)

Problem Summary:

A segment whose schema owns an inverted index always owns an index file,
even
when no logical index had anything to write: an all-NULL VARIANT column
extracts
no subcolumn, so no CLucene directory is ever opened and the container
is closed
with nothing in it. That empty container is committed by `close()`, not
by
`create_file()`, and `IndexFileWriter`'s empty-file path only closed
three named
implementations -- `StreamSinkFileWriter`, `S3FileWriter`,
`PackedFileWriter`.
`LocalFileWriter` and `HdfsFileWriter` were left open, and
`~LocalFileWriter`
aborts -- and DELETES -- a writer it was never asked to close. So on
local
storage the file silently disappeared and `show_nested_index_file`
reported
`E-6003 ... not found` where `E-6004 ... is empty` was the truth.

Closing through `FileWriter::state()` instead of a type whitelist fixes
that, but
on its own it is not enough: it makes one latent `IndexBuilder`
invariant
violation real, and it leaves a second `IndexBuilder` bug -- already
reachable
today on the sink-node and remote paths, where the empty file does
persist --
untouched.

**`IndexBuilder` writes a container for every segment it rewrites**,
including
when the ALTER leaves the schema with no index at all: dropping the last
index,
or adding an index whose column does not exist. Until now the local
writer's
destructor happened to delete those files. Once they are closed they
persist as
orphans that nothing ever links, copies, uploads, removes or checksums
--
`BetaRowset` decides whether to touch the container by asking
`has_inverted_or_ann_index()` of the rowset's own schema, and both
segment-writer
paths (`SegmentFlusher::_create_segment_writer`,
`VerticalBetaRowsetWriter`) already gate creation the same way.
`IndexBuilder`
was the one caller that did not.

**`IndexBuilder` refuses to read an empty container.**
`update_inverted_index_info()` tolerates `E-6003` ("this rowset predates
the
index") but not `E-6004` ("this rowset's index is empty"), even though
both mean
the same thing there -- there is nothing to carry over, build everything
from the
raw columns. `BUILD INDEX` / `ADD INDEX` / `DROP INDEX` on a table with
an
all-NULL VARIANT index therefore fails the whole ALTER with
`[E-6004]inverted index file ... is empty`.

So: three changes, one invariant.

> a rowset's schema owns an inverted/ANN index **iff** every one of its
segments
> owns an index file, possibly zero length

1. `IndexFileWriter::begin_close()` / `finish_close()` close the empty
container
through `FileWriter` for any non-null, non-CLOSED writer, preserving the
   two-phase asynchronous protocol and propagating close errors.
2. `IndexBuilder::handle_single_rowset()` writes no container when the
output
rowset schema owns no index -- on the drop path and the add path alike.
3. `IndexBuilder::update_inverted_index_info()` reads an empty container
exactly
   the way it reads a missing one.

Consumers already accept `E-6004`: queries downgrade unconditionally
(`SegmentIterator::_downgrade_without_index`), index compaction marks
the segment
ineligible and rebuilds, `CollectionStatistics` skips it. SNII is
untouched --
its container always carries a header, so it is never zero length, and
its
`has_container` probe stays `E-6003`-only.

#67702 made change 1 and then updated
`regression-test/plugins/plugin_curl_requester.groovy` and
`test_drop_index_with_format_v2.groovy` to expect `E-6004` after
dropping every
index. That expectation describes the orphan file, so this PR leaves
both at
`E-6003` and removes the orphan instead.

### Release note

Persist empty V2/V3 inverted index files through every FileWriter
implementation
instead of only the remote ones; stop failing ALTER on a segment whose
index file
is legitimately empty; stop leaving an unreferenced index file behind
when the
last index of a table is dropped.
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.

2 participants