Skip to content

[fix](constraint) Fix NPE in PrimaryKeyConstraint when loading old metadata - #61342

Merged
englefly merged 1 commit into
apache:masterfrom
englefly:compat-PrimaryKeyConstraint
Mar 17, 2026
Merged

englefly merged 1 commit into
apache:masterfrom
englefly:compat-PrimaryKeyConstraint

Conversation

@englefly

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

When FE starts with -r (metadata_failure_recovery) or without a checkpoint image, it replays all journal logs from the beginning. Old journal entries may contain PrimaryKeyConstraint objects that were serialized before the fields foreignTables, foreignTableNameStrs, or foreignTableInfos were added to the class.

Gson uses Unsafe to instantiate objects during deserialization, bypassing the constructor and its field initializers. If a field is absent from the JSON, Gson leaves it as null rather than using the declared initializer (e.g. = new HashSet<>()). Calling .isEmpty() or any method on a null collection then throws a NullPointerException.

Fix: remove final from the three collection fields (so they can be reassigned in gsonPostProcess) and add null-guards at the top of gsonPostProcess() to initialize them to empty collections when absent.

Impact on -r mode: when FE is started with --metadata_failure_recovery it replays every journal from journal #1, encountering the oldest serialized PrimaryKeyConstraint objects. Without this fix, FE always crashes during -r recovery if any PrimaryKeyConstraint was persisted before these fields existed.

Issue Number: close #xxx

Related PR: #xxx

Problem Summary:

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • 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

…tadata

When FE starts with -r (metadata_failure_recovery) or without a
checkpoint image, it replays all journal logs from the beginning.
Old journal entries may contain PrimaryKeyConstraint objects that were
serialized before the fields foreignTables, foreignTableNameStrs, or
foreignTableInfos were added to the class.

Gson uses Unsafe to instantiate objects during deserialization,
bypassing the constructor and its field initializers. If a field is
absent from the JSON, Gson leaves it as null rather than using the
declared initializer (e.g. `= new HashSet<>()`). Calling .isEmpty()
or any method on a null collection then throws a NullPointerException.

Fix: remove `final` from the three collection fields (so they can be
reassigned in gsonPostProcess) and add null-guards at the top of
gsonPostProcess() to initialize them to empty collections when absent.

Impact on -r mode: when FE is started with --metadata_failure_recovery
it replays every journal from journal #1, encountering the oldest
serialized PrimaryKeyConstraint objects. Without this fix, FE always
crashes during -r recovery if any PrimaryKeyConstraint was persisted
before these fields existed.

Co-Authored-By: Claude Sonnet 4.6 <[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?

@englefly

Copy link
Copy Markdown
Contributor Author

run buildall

@doris-robot

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

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17626	4496	4265	4265
q2	q3	10645	800	507	507
q4	4685	357	253	253
q5	7541	1198	1012	1012
q6	172	176	145	145
q7	769	856	651	651
q8	9284	1449	1383	1383
q9	4806	4690	4717	4690
q10	6308	1909	1651	1651
q11	464	259	242	242
q12	762	570	471	471
q13	18044	2930	2187	2187
q14	233	231	217	217
q15	q16	728	724	677	677
q17	717	825	442	442
q18	5879	5336	5176	5176
q19	1148	984	622	622
q20	534	483	391	391
q21	4504	1854	1408	1408
q22	331	280	271	271
Total cold run time: 95180 ms
Total hot run time: 26661 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	4895	4685	4671	4671
q2	q3	3861	4393	3807	3807
q4	863	1214	822	822
q5	4097	4386	4304	4304
q6	184	174	144	144
q7	1778	1673	1549	1549
q8	2456	2729	2556	2556
q9	7704	7459	7268	7268
q10	3754	4083	3634	3634
q11	541	437	417	417
q12	486	585	430	430
q13	2690	3116	2577	2577
q14	284	336	280	280
q15	q16	725	820	707	707
q17	1139	1361	1275	1275
q18	7115	6705	6603	6603
q19	909	907	994	907
q20	2086	2139	2180	2139
q21	3992	3499	3362	3362
q22	495	441	379	379
Total cold run time: 50054 ms
Total hot run time: 47831 ms

@doris-robot

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

query5	4330	644	501	501
query6	326	226	215	215
query7	4214	473	269	269
query8	338	253	227	227
query9	8734	2698	2708	2698
query10	520	377	374	374
query11	6927	5076	4865	4865
query12	183	128	127	127
query13	1271	460	351	351
query14	5819	3697	3449	3449
query14_1	2842	2795	2864	2795
query15	216	198	175	175
query16	996	479	466	466
query17	894	686	585	585
query18	2429	431	338	338
query19	208	205	174	174
query20	129	126	121	121
query21	215	133	115	115
query22	13300	14003	14699	14003
query23	16068	15944	15612	15612
query23_1	15511	15673	15472	15472
query24	7224	1626	1223	1223
query24_1	1230	1241	1229	1229
query25	558	478	405	405
query26	1532	259	164	164
query27	2831	504	300	300
query28	4912	1952	1895	1895
query29	867	611	476	476
query30	301	236	193	193
query31	1018	958	875	875
query32	80	75	70	70
query33	512	328	284	284
query34	896	893	535	535
query35	637	679	602	602
query36	1166	1142	955	955
query37	141	98	84	84
query38	2970	2947	2972	2947
query39	875	864	830	830
query39_1	808	796	794	794
query40	243	155	135	135
query41	63	59	58	58
query42	261	260	254	254
query43	245	257	221	221
query44	
query45	200	192	188	188
query46	903	1008	639	639
query47	2137	2132	2076	2076
query48	328	335	227	227
query49	629	475	383	383
query50	687	277	214	214
query51	4061	4046	3992	3992
query52	259	263	253	253
query53	295	337	287	287
query54	306	268	280	268
query55	88	83	80	80
query56	355	330	311	311
query57	1953	1845	1729	1729
query58	283	274	270	270
query59	2787	2956	2732	2732
query60	334	341	329	329
query61	155	145	150	145
query62	631	582	541	541
query63	309	283	273	273
query64	5036	1267	1012	1012
query65	
query66	1495	461	368	368
query67	24310	24236	24388	24236
query68	
query69	409	316	279	279
query70	990	881	1000	881
query71	335	310	286	286
query72	2803	2734	2605	2605
query73	540	552	316	316
query74	9640	9576	9419	9419
query75	2865	2761	2478	2478
query76	2297	1027	659	659
query77	369	381	317	317
query78	10913	11106	10456	10456
query79	1326	771	587	587
query80	1367	656	570	570
query81	551	264	238	238
query82	1014	154	123	123
query83	350	271	258	258
query84	307	129	105	105
query85	984	497	436	436
query86	412	301	325	301
query87	3109	3094	3032	3032
query88	3510	2664	2629	2629
query89	434	376	345	345
query90	2007	175	173	173
query91	163	167	140	140
query92	75	72	68	68
query93	957	833	493	493
query94	643	315	279	279
query95	594	343	317	317
query96	667	509	225	225
query97	2453	2473	2382	2382
query98	234	225	219	219
query99	1046	979	926	926
Total cold run time: 250199 ms
Total hot run time: 168375 ms

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Mar 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

@github-actions

Copy link
Copy Markdown
Contributor

PR approved by anyone and no changes requested.

@englefly

Copy link
Copy Markdown
Contributor Author

run nonConcurrent

@englefly
englefly merged commit 917c999 into apache:master Mar 17, 2026
29 of 31 checks passed
@englefly
englefly deleted the compat-PrimaryKeyConstraint branch March 17, 2026 03:46
yiguolei pushed a commit that referenced this pull request Sep 20, 2026
…ding old metadata (#68206)

Cherry-pick of #61342 to branch-4.2

[fix](constraint) Fix NPE in PrimaryKeyConstraint when loading old
metadata

On branch-4.2 the class only has the `foreignTables` collection (the
newer
`foreignTableNameStrs`/`foreignTableInfos` fields come from a later
refactor that is
not part of this branch), so only that field is guarded. A unit test
covering
deserialization of metadata without the `ft` field was added.

---------

Signed-off-by: Aurélien Pupier <[email protected]>
Co-authored-by: Aurélien Pupier <[email protected]>
morningman added a commit that referenced this pull request Nov 5, 2026
…0163)

### What problem does this PR solve?

Issue Number: None

Related PR: #68206, #68636, #68368

Problem Summary:

**In short.** Three branch-4.2 backports of master fixes each added a
test: #68206 (of #61342), #68636 (of #57006) and #68368 (of #66354).
Master has the three fixes but none of the tests. This PR ports them.
Two guard a fix that has no test on master; the third keeps the
ClickBench `query1` shape case from failing whenever the exact row count
of `hits` is cached. The PR changes tests only.

**Background, and what each test covers.**

- *Old primary key metadata (#61342, #68206).* A `PrimaryKeyConstraint`
written before its foreign table collections existed has no `ft`, `ftn`
or `ftni` in its JSON. Gson builds the object without its field
initializers, so these stay null unless `gsonPostProcess` fills them in,
and an FE that replays the journal from the start (without an image, or
with `metadata_failure_recovery`) failed with a NullPointerException.
#61342 added the null checks without a test; #68206 added one on
branch-4.2 for `ft`, the only collection there. The new
`PrimaryKeyConstraintTest` removes all three from a serialized
constraint, loads it, and adds, renames and removes a foreign table on
it.
- *Shadow index columns (#57006, #68636).* During a schema change the
new schema names a changed column `__doris_shadow_<name>`.
`Index.getColumnUniqueIds` compares the index's column names with the
schema's after `Column.removeNamePrefix`, so the new tablet metadata
keeps the index column's unique id. #57006 has no test; #68636 added one
to `IndexTest`, ported here to JUnit 5.
- *ClickBench query1 shape (#66354, #68368).*
`shape_check/clickbench/query1` checks the plan of `SELECT COUNT(*) FROM
hits`. Once `SimpleAggCacheMgr` has cached the exact row count of
`hits`, `REWRITE_SIMPLE_AGG_TO_CONSTANT` turns the count into a constant
and the shape into `PhysicalOneRowRelation`, so the case fails whenever
that cache is warm when it runs. It did on P0 for #69830 (build 1087320:
expected `--hashAgg[GLOBAL]`, real `--PhysicalOneRowRelation`), a PR
that does not touch the planner. On branch-4.2, #68368 disables the rule
in this case together with the cost model change it backports, which
master has from #66354. This PR disables it the same way; the expected
output does not change.

**Results.**

| Change | Test | Result |
|---|---|---|
| none | `PrimaryKeyConstraintTest` (new), `IndexTest` (one new case),
`ConstraintManagerTest`, `ConstraintPersistTest` | pass |
| `gsonPostProcess` without its null checks | `PrimaryKeyConstraintTest`
| fails: NullPointerException while loading |
| `getColumnUniqueIds` without `removeNamePrefix` |
`IndexTest#testGetColumnUniqueIdsForShadowColumn` | fails: `expected
[107] but was []` |
| `query1` as on master, row count of `hits` cached (local cluster) |
`shape_check/clickbench/query1` | fails: `PhysicalOneRowRelation` |
| `query1` with this PR, same cache | `shape_check/clickbench/query1` |
passes |

On the local cluster, `explain shape plan SELECT COUNT(*) FROM hits`
gives `hashAgg[GLOBAL]` the first time and `PhysicalOneRowRelation`
three seconds later, once the asynchronous row count load has finished;
with the rule disabled it stays `hashAgg[GLOBAL]`.

Co-authored-by: minghong <[email protected]>
Co-authored-by: Jack <[email protected]>
Co-authored-by: feiniaofeiafei <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer. dev/4.2.0-merged reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants