Skip to content

[fix](variant) Identify Variant internal columns by exact path - #70060

Open
eldenmoon wants to merge 1 commit into
apache:masterfrom
eldenmoon:branch-variant-reserved-path
Open

eldenmoon wants to merge 1 commit into
apache:masterfrom
eldenmoon:branch-variant-reserved-path

Conversation

@eldenmoon

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: N/A

Related PR: #69995 (found during its review; this defect predates that PR and also exists on branch-4.1 and branch-4.2)

Problem Summary:

A user JSON key such as x__DORIS_VARIANT_SPARSE__y is an ordinary Variant path. Several BE paths identify the internal sparse and doc-value columns by substring (find("__DORIS_VARIANT_SPARSE__"), find(DOC_VALUE_COLUMN_PATH)), so such a key is mistaken for a physical binary column:

  • V3 external column meta (variant_ext_meta_writer.cpp): the user subcolumn is embedded in the root's children with no external path-index entry. The reader accepts only the exact sparse name or .b<n> buckets there and ignores the child, so the values read as NULL, and the next compaction no longer sees the path and drops them.
  • Compaction statistics (variant_stats_calculator.cpp, any storage format): a materialized user column with the marker text is passed to the sparse statistics code, which casts it to ColumnMap.
  • Doc-value marker: writer selection, read plans, stats filtering and NestedGroup path filtering use the same substring test, and the reader parses the bucket with std::stoi, which throws for a user key.

Reproduction on an ASAN build of master, with variant_max_subcolumns_count=1 and three separate inserts {p:701,hot_a:11}/{hot_a:12}, {p:702,hot_b:21}/{hot_b:22}, {p:703,hot_c:31}/{hot_c:32} where p = "x__DORIS_VARIANT_SPARSE__y":

Case master
V3, p materialized at load v[p] is NULL right after the load
FULL compaction promotes p (3 > 2) stops in the statistics calculator: Bad cast from type:doris::ColumnNullable to doris::ColumnMap
key containing __DORIS_VARIANT_DOC_VALUE__ in V3 reader throws std::invalid_argument from std::stoi

Fix:

  • One helper, variant_util::variant_binary_column_bucket, recognizes only the relative names writers generate: the single sparse column and canonical .b<n> sparse and doc buckets (.b01, .b1tail, .b1.child are user paths). It replaces the substring tests at every site and the separate bucket parsers in the reader and the doc compact writer.
  • Existing V3 segments: an embedded child that is not an internal name is registered as an ordinary subcolumn and read through the existing reader cache. This runs once per segment open, with no per-row work. Values that an earlier compaction already removed cannot be recovered.

Out of scope: a user key exactly equal to an internal name (for example a top-level __DORIS_VARIANT_SPARSE__) still collides with the physical column; on an ASAN build master stops at the reader's col.has_variant_statistics() check.

Release note

Fix wrong reads, compaction failures and possible data loss for Variant keys that contain the internal sparse or doc-value column names, and make subcolumns hidden by older V3 segments readable again.

Check List (For Author)

  • Test
    • Regression test: reserved_internal_path_key, DUP/MOW × V2/V3 × ordinary/doc × 1/3 shards. Each case checks load-time materialization and two FULL compactions (a new rowset in between; MOW also overwrites rows), comparing every row's v[p] and the whole Variant.
    • Unit Test: exact identity against writer-generated names and malformed suffixes, V3 metadata classification, statistics, an inline round trip, and recovery of a V3 segment written with the old classifier. The classifier, reader and statistics tests fail on master (the sparse statistics test aborts with the bad cast above). Affected BE UT classes: 894 passed, 4 skipped (not applicable in this build).
    • Manual test: V3 segments written by unmodified master are read correctly by the fixed BE, and their values survive two FULL compactions.
    • Full variant_p0: all suites pass except outfile_csv_variant_doc_value (object storage 403) and the old-directory double_write_schema_change_with_variant (public 1 vs true), which fail the same way on unmodified master.
  • Behavior changed:
    • Yes. Keys containing internal column names are ordinary paths.
  • Does this need documentation?
    • No.

Check List (For Reviewer who merge this PR)

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

🤖 Generated with Claude Code

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#69995

Problem Summary:

Several BE paths identified the internal sparse and doc-value columns of a Variant by substring, so a user
key such as x__DORIS_VARIANT_SPARSE__y was treated as an internal column. With storage_format V3 the
key's metadata was embedded in the root without a path-index entry and the reader ignored it, so its
values read as NULL and a later compaction dropped them. FULL compaction could also pass such a scalar
column to the sparse statistics code, which expects a Map, and a doc-marker key made the reader throw
from std::stoi.

Recognize only the relative names writers generate (the single sparse column and canonical .b<n> sparse
and doc buckets) with one helper, and use it at every site. Read ordinary children embedded by older V3
writers as regular subcolumns; this is per-segment metadata work with no per-row cost. A key exactly equal
to an internal name is out of scope.

### Release note

Fix wrong reads, compaction failures and possible data loss for Variant keys that contain the internal
sparse or doc-value column names, and make subcolumns hidden by older V3 segments readable again.

### Check List (For Author)

- Test: Unit Test and Regression test.
  - BE UT: exact identity against writer-generated names and malformed bucket suffixes, V3 metadata
    classification, statistics, inline round trip, and recovery of a V3 segment written with the old
    classifier. Affected test classes: 894 passed, 4 skipped.
  - Regression: reserved_internal_path_key, DUP/MOW x V2/V3 x ordinary/doc x 1/3 shards, load-time
    materialization and two FULL compactions, checking each row's value and the whole Variant.
  - variant_p0: all suites pass except two that fail the same way on unmodified master.
- Behavior changed: Yes. Keys containing internal column names are ordinary paths.
- Does this need documentation: No.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@eldenmoon

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17612	5199	5071	5071
q2	2152	332	260	260
q3	10237	1373	822	822
q4	4677	463	345	345
q5	7431	814	540	540
q6	185	185	154	154
q7	745	802	604	604
q8	9258	1314	1088	1088
q9	5429	4154	4166	4154
q10	6818	1305	1027	1027
q11	441	259	242	242
q12	647	421	286	286
q13	18070	3527	3036	3036
q14	280	272	244	244
q15	q16	714	714	669	669
q17	1196	736	593	593
q18	6904	6195	6101	6101
q19	1112	963	611	611
q20	392	345	226	226
q21	5251	2203	2321	2203
q22	349	303	261	261
Total cold run time: 99900 ms
Total hot run time: 28537 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5709	5568	5871	5568
q2	689	577	536	536
q3	5242	5409	4935	4935
q4	1982	2139	1556	1556
q5	5270	5198	5713	5198
q6	264	197	148	148
q7	2307	2001	1835	1835
q8	3056	2661	2666	2661
q9	8062	8023	7897	7897
q10	4012	3628	3277	3277
q11	635	469	429	429
q12	689	729	519	519
q13	3527	3993	3295	3295
q14	324	329	298	298
q15	q16	735	717	631	631
q17	8079	6990	6911	6911
q18	12980	12153	12821	12153
q19	954	820	839	820
q20	2156	2164	1907	1907
q21	5543	4572	4681	4572
q22	490	438	425	425
Total cold run time: 72705 ms
Total hot run time: 65571 ms

@hello-stephen

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

query5	4309	600	433	433
query6	421	212	190	190
query7	4803	444	228	228
query8	324	166	155	155
query9	8753	3196	3192	3192
query10	478	306	249	249
query11	5991	3887	3577	3577
query12	147	89	89	89
query13	1255	447	346	346
query14	6096	3973	3647	3647
query14_1	3431	3437	3426	3426
query15	218	202	194	194
query16	938	434	404	404
query17	879	623	514	514
query18	2407	410	301	301
query19	197	158	129	129
query20	82	82	78	78
query21	204	128	105	105
query22	12960	13189	12774	12774
query23	13343	12735	12409	12409
query23_1	12540	12680	12681	12680
query24	7362	812	455	455
query24_1	476	476	464	464
query25	499	392	337	337
query26	1254	254	134	134
query27	2769	438	274	274
query28	4597	2034	2017	2017
query29	1544	592	447	447
query30	305	219	183	183
query31	1008	923	824	824
query32	132	92	93	92
query33	529	289	233	233
query34	892	878	520	520
query35	734	747	638	638
query36	815	811	746	746
query37	127	103	84	84
query38	1771	1752	1654	1654
query39	803	798	745	745
query39_1	727	718	740	718
query40	218	117	100	100
query41	72	71	67	67
query42	87	81	85	81
query43	343	366	315	315
query44	1315	699	735	699
query45	181	178	169	169
query46	850	974	574	574
query47	2940	2929	2932	2929
query48	307	311	223	223
query49	585	423	295	295
query50	691	279	228	228
query51	10560	10494	10566	10494
query52	79	92	73	73
query53	192	207	158	158
query54	251	207	191	191
query55	80	75	71	71
query56	256	227	224	224
query57	1621	1632	1579	1579
query58	291	265	252	252
query59	2135	2172	2030	2030
query60	291	257	236	236
query61	171	166	163	163
query62	393	330	293	293
query63	191	163	165	163
query64	2875	1020	831	831
query65	3317	3272	3253	3253
query66	1780	417	296	296
query67	20319	20140	19910	19910
query68	2992	1008	632	632
query69	370	284	249	249
query70	830	800	765	765
query71	299	217	228	217
query72	2726	2582	2354	2354
query73	526	521	302	302
query74	4624	4552	4357	4357
query75	2342	2263	1916	1916
query76	2311	1007	578	578
query77	338	384	282	282
query78	9946	9770	9152	9152
query79	1901	841	536	536
query80	1220	423	348	348
query81	585	321	269	269
query82	591	136	108	108
query83	297	193	185	185
query84	315	118	105	105
query85	874	429	368	368
query86	394	241	228	228
query87	2025	1950	1800	1800
query88	3888	2823	2781	2781
query89	334	281	239	239
query90	1866	175	169	169
query91	163	150	129	129
query92	100	80	91	80
query93	1125	1002	580	580
query94	643	310	272	272
query95	586	393	303	303
query96	680	505	244	244
query97	2486	2462	2317	2317
query98	169	153	151	151
query99	770	777	637	637
Total cold run time: 236639 ms
Total hot run time: 151615 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.09	0.05	0.05
query3	0.26	0.08	0.07
query4	1.61	0.09	0.09
query5	0.22	0.24	0.25
query6	1.18	0.69	0.67
query7	0.04	0.01	0.00
query8	0.05	0.04	0.04
query9	0.40	0.33	0.36
query10	0.58	0.62	0.58
query11	0.21	0.15	0.15
query12	0.19	0.15	0.15
query13	0.50	0.49	0.50
query14	1.02	1.02	1.00
query15	0.63	0.61	0.61
query16	0.35	0.35	0.32
query17	1.14	1.11	1.08
query18	0.22	0.20	0.19
query19	2.16	1.95	2.00
query20	0.02	0.01	0.02
query21	15.49	0.19	0.14
query22	4.88	0.05	0.06
query23	16.29	0.31	0.13
query24	3.13	0.58	0.38
query25	0.14	0.07	0.05
query26	1.29	0.22	0.16
query27	0.04	0.04	0.04
query28	3.27	0.75	0.34
query29	12.48	4.26	3.32
query30	0.27	0.13	0.10
query31	2.77	0.56	0.33
query32	3.23	0.60	0.51
query33	3.14	3.14	3.20
query34	15.36	4.02	3.40
query35	3.30	3.34	3.36
query36	0.56	0.44	0.47
query37	0.10	0.06	0.06
query38	0.05	0.04	0.03
query39	0.04	0.04	0.04
query40	0.17	0.15	0.15
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.05	0.04	0.03
Total cold run time: 97.06 s
Total hot run time: 24.26 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100% (0/0) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.68% (35898/46213)
Line Coverage 63.02% (403722/640659)
Region Coverage 59.56% (341407/573256)
Branch Coverage 60.72% (158126/260429)

@eldenmoon

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Selected model is at capacity. Please try a different model.
Workflow run: https://github.com/apache/doris/actions/runs/41817710300

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@eldenmoon

Copy link
Copy Markdown
Member 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.

Static review completed for PR 70060 at head d5749a0, against base 61906ce. No new P0, P1, P2, or P3 findings were verified, and there are no existing blocking inline findings to carry forward. The user focus file is empty, so no additional focus concern applies.

The change's goal is met: exact writer-generated Variant sparse/doc names are classified consistently, marker-containing user paths remain ordinary data, and legacy V3 embedded ordinary children are recoverable. R1 was dismissed after tracing the shared bucket helper through writer dispatch, V2 doc parsing, external metadata classification, reader plans, statistics, path-stat admission, physical-name constructors, and compaction schema generation. R2 was dismissed after tracing root children, relative-path normalization, ordinal -1 handling, embedded-map precedence, external metadata loading, ColumnReader creation, cache access, and the existing metadata lock. R3 was dismissed after tracing V2 DOC shredding/finalization, row and null propagation, scalar versus Variant writer selection, sparse/doc readers, V3 externalization, old segment compatibility, compaction, and the added tests.

Critical review checkpoints: data correctness and visibility are preserved by the ordinary writer/reader path plus direct legacy metadata recovery; the patch is focused around one classifier and its call sites. The new metadata map is immutable after initialization, and existing shared/exclusive metadata locking covers the mutable tree and external loading. No unusual lifecycle or static-initialization issue was found. V2/V3 storage compatibility was specifically traced, including old embedded metadata. Parallel query, compaction, checksum, and cache paths were checked. Conditional parsing rejects malformed, signed, leading-zero, trailing, nested, and overflowing bucket suffixes while preserving exact generated names. The BE unit and regression changes provide targeted coverage for these paths, but they were inspected only and not executed. No new observability, transaction/EditLog, FE-BE variable, configuration, thrift/protobuf, or external write protocol change was introduced. The added map metadata accounting and parser work are bounded and consistent with the surrounding design.

Validation was static-only: authoritative diff, current source, required AGENTS.md files, review skill, tests, ledger, and live PR state were inspected; no build, test, benchmark, cluster, or runtime validation was run. All three assigned round-1 scopes returned no new valuable findings, every risk item has an explicit dismissal with evidence, and the review converged in round 1.

Review converged after 1 round(s).

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.

2 participants