Skip to content

[fix](dictionary) stabilize insert target during database drop#65476

Draft
zclllyybb wants to merge 1 commit into
apache:masterfrom
zclllyybb:agent/fix-dictionary-insert-target-race
Draft

[fix](dictionary) stabilize insert target during database drop#65476
zclllyybb wants to merge 1 commit into
apache:masterfrom
zclllyybb:agent/fix-dictionary-insert-target-race

Conversation

@zclllyybb

@zclllyybb zclllyybb commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary:

A dictionary refresh can retain a Dictionary object while DROP DATABASE removes its owner database. The old path repeatedly recovered the database through dictionary.getDatabase() and checked existence by database/name, so it could either dereference a null database or accept a newly created dictionary with the same name.

This change establishes one insert-target invariant: resolve the database and table/dictionary together, then carry that exact pair through authorization, sink binding, retry validation, and executor construction. Dictionary loads additionally validate canonical dictionary identity by ID before planning and after execution. Database drop now removes dictionary IDs from the canonical map, matching replay behavior and preventing same-name ABA.

Ordinary internal-table inserts keep their existing table read-lock/write-lock and transaction ordering. The paired target only closes the pre-lock metadata lookup window and adds database ID to retry validation.

Release note

Fix concurrent dictionary refresh and database drop returning an internal null-pointer error or accepting stale dictionary metadata.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (targeted isolated FE/BE cluster race)
    • 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. Concurrent target removal now returns a deterministic analysis error instead of an internal null-pointer error; same-name recreation is treated as a different dictionary generation.
  • Does this need documentation?

    • No.
    • Yes.

Resolve insert targets as a database-table pair so authorization and retry validation do not recover the database through a stale table object.

Carry the dictionary owner database through Nereids sink binding and executor creation. Remove dropped dictionary IDs from the canonical map and validate exact dictionary identity before and after loading to reject same-name recreation.

Add deterministic FE unit races for normal inserts and dictionary loads, plus a debug-point regression covering concurrent REFRESH DICTIONARY and DROP DATABASE.

Tests: ./run-fe-ut.sh --run org.apache.doris.nereids.trees.plans.commands.insert.InsertTargetDropRaceTest

Tests: ./run-regression-test.sh --run -f regression-test/suites/dictionary_p0/test_create_drop_sync.groovy

Tests: ./build.sh --fe

Tests: ./build.sh --be
@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?

@zclllyybb

Copy link
Copy Markdown
Contributor Author

run buildall

@zclllyybb

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.

Automated review found two issues, so I am requesting changes.

Critical checkpoint conclusions:

  • Goal and correctness: the paired database/table target flow addresses the main stale-target path, but the dictionary load still has a post-check drop race that can journal an unreplayable version decrement.
  • Scope: the implementation is focused on FE dictionary insert/drop handling and related tests.
  • Concurrency, lifecycle, and persistence: M1 covers a remaining drop/commit-failure interleaving that can leave OP_DICTIONARY_DEC_VERSION after OP_DROP_DICTIONARY.
  • Parallel paths and config/session propagation: normal insert and dictionary insert paths were checked; I did not find an additional session/config propagation issue.
  • Tests: the added race coverage is useful, but M2 notes that the dictionary test gate is not deterministic for the intended block-before-planning path.
  • Validation: static review only. This checkout is shallow and lacks thirdparty/installed and executable thirdparty/installed/bin/protoc, so I did not run FE/regression tests.
  • User focus: no additional user-provided review focus was supplied.

Subagent conclusions: optimizer-rewrite produced no candidates and converged with NO_NEW_VALUABLE_FINDINGS. tests-session-config proposed TC-1, which was accepted as M2. After M1 and M2 were in the ledger, both live subagents returned NO_NEW_VALUABLE_FINDINGS in convergence round 1.

try {
if (!dictionaryIds.containsKey(dictionary.getDbName())
|| !dictionaryIds.get(dictionary.getDbName()).containsKey(dictionary.getName())) {
if (!isCurrentDictionaryWithoutLock(dictionary)) {

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.

This identity check is still too early for the failure path below. After this block releases the dictionary-manager read lock, a concurrent DROP DICTIONARY/DROP DATABASE can remove this same dictionary and log OP_DROP_DICTIONARY; if commitNowVersion() then returns false, lines 527-528 still call logDictionaryDecVersion(dictionary) for an object that is no longer current. On replay the drop removes the dictionary, and replayDecreaseVersion() calls getDictionary(dbName, dictName), so the later decrement entry cannot be applied. Please recheck/hold the dictionary identity before logging the decrement, and skip the rollback journal entry once the dictionary has already been dropped.


Dictionary dictionary = Env.getCurrentEnv().getDictionaryManager().getDictionary(dbName, "dic1");
try {
await(() -> dictionary.getStatus() == DictionaryStatus.LOADING);

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.

Waiting for LOADING does not guarantee the load thread has reached DictionaryManager.dataLoad.blockBeforePlan. dataLoad() sets LOADING before it resolves/checks the database and before it enters the debug-point loop, so this test can drop the database early and still pass through the new database == null || !isCurrentDictionary(...) branch without exercising the stale captured-database/planning path. Please synchronize on a point reached after the debug point is entered, or make the debug point signal the test, so this race test fails if the planning path regresses.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17593	3959	4105	3959
q2	2005	311	193	193
q3	10324	1375	792	792
q4	4691	467	338	338
q5	7514	841	567	567
q6	182	170	142	142
q7	771	841	620	620
q8	9419	1583	1584	1583
q9	6124	4330	4339	4330
q10	6831	1769	1500	1500
q11	522	340	306	306
q12	743	558	444	444
q13	18142	3319	2730	2730
q14	262	259	240	240
q15	q16	790	786	705	705
q17	928	882	988	882
q18	6963	5697	5499	5499
q19	1438	1174	1051	1051
q20	794	635	524	524
q21	5891	2650	2417	2417
q22	421	362	301	301
Total cold run time: 102348 ms
Total hot run time: 29123 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4335	4243	4232	4232
q2	289	328	209	209
q3	4520	4886	4381	4381
q4	2036	2155	1345	1345
q5	4354	4298	4533	4298
q6	228	174	127	127
q7	1720	1628	1817	1628
q8	2558	2146	2116	2116
q9	7759	7787	7754	7754
q10	4739	4647	4294	4294
q11	566	454	416	416
q12	737	743	536	536
q13	3342	3542	2870	2870
q14	329	303	291	291
q15	q16	749	740	655	655
q17	1338	1314	1312	1312
q18	8049	7235	6988	6988
q19	1046	1037	1094	1037
q20	2181	2221	1943	1943
q21	5200	4526	4457	4457
q22	522	450	402	402
Total cold run time: 56597 ms
Total hot run time: 51291 ms

@hello-stephen

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

query5	4333	633	491	491
query6	473	230	230	230
query7	5006	613	343	343
query8	346	195	177	177
query9	8767	4110	4107	4107
query10	505	348	284	284
query11	5921	2357	2114	2114
query12	158	108	100	100
query13	1276	591	443	443
query14	6287	5308	4979	4979
query14_1	4328	4308	4300	4300
query15	217	205	182	182
query16	1038	481	463	463
query17	1140	742	588	588
query18	2742	477	357	357
query19	210	194	153	153
query20	116	110	107	107
query21	236	162	137	137
query22	13655	13570	13476	13476
query23	17167	16459	16093	16093
query23_1	16267	16278	16207	16207
query24	7714	1777	1286	1286
query24_1	1320	1322	1311	1311
query25	567	464	401	401
query26	1325	351	209	209
query27	2591	623	397	397
query28	4427	2028	2018	2018
query29	1074	643	504	504
query30	348	270	233	233
query31	1124	1105	989	989
query32	112	67	62	62
query33	537	324	326	324
query34	1203	1158	663	663
query35	768	771	663	663
query36	1391	1403	1281	1281
query37	151	117	91	91
query38	1894	1715	1652	1652
query39	939	953	912	912
query39_1	907	915	938	915
query40	244	160	138	138
query41	72	78	76	76
query42	93	95	91	91
query43	320	323	281	281
query44	1465	832	782	782
query45	195	196	177	177
query46	1019	1171	749	749
query47	2414	2293	2183	2183
query48	404	404	295	295
query49	570	422	311	311
query50	1071	407	354	354
query51	10882	10742	10693	10693
query52	85	86	79	79
query53	268	288	210	210
query54	273	250	245	245
query55	75	70	64	64
query56	289	293	303	293
query57	1405	1392	1293	1293
query58	291	253	249	249
query59	1553	1627	1448	1448
query60	299	265	252	252
query61	155	146	149	146
query62	681	641	585	585
query63	235	207	210	207
query64	2775	1007	825	825
query65	4856	4823	4739	4739
query66	1778	510	390	390
query67	28936	29359	29296	29296
query68	3146	1593	955	955
query69	452	298	255	255
query70	1067	970	968	968
query71	374	344	311	311
query72	3059	2733	2441	2441
query73	845	769	408	408
query74	5131	4941	4750	4750
query75	2608	2599	2219	2219
query76	2361	1165	774	774
query77	371	381	289	289
query78	12359	12321	11710	11710
query79	1379	1143	758	758
query80	1294	542	466	466
query81	520	328	280	280
query82	623	156	125	125
query83	367	317	285	285
query84	279	165	126	126
query85	985	581	523	523
query86	424	315	285	285
query87	1843	1819	1754	1754
query88	3701	2810	2789	2789
query89	460	426	355	355
query90	1924	203	194	194
query91	208	194	160	160
query92	63	63	56	56
query93	1655	1488	1025	1025
query94	724	361	354	354
query95	780	566	454	454
query96	1069	870	357	357
query97	2683	2666	2537	2537
query98	213	208	205	205
query99	1129	1160	1035	1035
Total cold run time: 265680 ms
Total hot run time: 180359 ms

@hello-stephen

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

query1	0.00	0.00	0.01
query2	0.09	0.04	0.05
query3	0.25	0.14	0.14
query4	1.61	0.14	0.14
query5	0.24	0.23	0.22
query6	1.26	1.10	1.07
query7	0.04	0.00	0.00
query8	0.05	0.03	0.04
query9	0.40	0.30	0.33
query10	0.59	0.56	0.56
query11	0.19	0.14	0.14
query12	0.19	0.14	0.15
query13	0.46	0.46	0.48
query14	1.01	1.01	1.00
query15	0.60	0.59	0.59
query16	0.33	0.35	0.32
query17	1.09	1.10	1.06
query18	0.22	0.21	0.21
query19	2.02	1.93	1.98
query20	0.02	0.01	0.01
query21	15.43	0.19	0.12
query22	4.99	0.06	0.05
query23	16.14	0.31	0.12
query24	2.93	0.41	0.33
query25	0.12	0.06	0.03
query26	0.81	0.21	0.15
query27	0.05	0.03	0.04
query28	3.49	0.91	0.55
query29	12.49	4.03	3.23
query30	0.28	0.15	0.17
query31	2.77	0.59	0.31
query32	3.23	0.59	0.49
query33	3.11	3.31	3.25
query34	15.62	4.24	3.51
query35	3.50	3.55	3.56
query36	0.56	0.43	0.43
query37	0.09	0.07	0.07
query38	0.05	0.04	0.03
query39	0.04	0.02	0.03
query40	0.18	0.16	0.16
query41	0.08	0.04	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.7 s
Total hot run time: 25.03 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