Skip to content

Conversation

@dataroaring
Copy link
Contributor

Summary

  • Move edit log operations outside the write lock in DatabaseTransactionMgr
  • Reduces lock hold time from milliseconds to microseconds
  • Enables higher concurrency for multi-table workloads in the same database

Changes

  • Add unprotectUpdateInMemoryState() for in-memory updates inside lock
  • Add persistTransactionState() for edit log writes outside lock
  • Refactor transaction lifecycle methods to use the new pattern:
    • beginTransaction
    • preCommitTransaction2PC
    • commitTransaction (both overloads)
    • finishTransaction
    • abortTransaction / abortTransaction2PC
    • removeUselessTxns

Safety Guarantees

  • In-memory state updated atomically within write lock before release
  • Edit log failures call System.exit(-1), preventing inconsistent state
  • Replay path unchanged (uses isReplay=true, skips edit log write)
  • ACID properties preserved

Test plan

  • Run DatabaseTransactionMgrTest unit tests
  • Concurrent load testing with multiple tables in same database
  • Verify lock hold time reduction via metrics

Fixes: #53642

Add support for `unique_key_update_mode` property in routine load to enable
flexible partial columns update. This allows different rows in the same batch
to update different columns, unlike fixed partial update where all rows must
update the same columns.

Changes:
- Add `unique_key_update_mode` property to CreateRoutineLoadInfo with values:
  UPSERT (default), UPDATE_FIXED_COLUMNS, UPDATE_FLEXIBLE_COLUMNS
- Add validation for flexible partial update constraints (JSON format only,
  no jsonpaths, no fuzzy_parse, no COLUMNS clause, no WHERE clause, table
  must have skip_bitmap column enabled)
- Update RoutineLoadJob to persist and restore the update mode
- Update KafkaRoutineLoadJob to pass update mode to task info
- Support ALTER ROUTINE LOAD to change unique_key_update_mode
- Add regression tests covering basic usage and error cases
- Fix HashMap ordering issue in gsonPostProcess for backward compatibility
- Add validation when ALTER changes mode to UPDATE_FLEXIBLE_COLUMNS
- Add comprehensive ALTER test cases for flexible partial update validation
1. Fix checkstyle: line length exceeds 120 characters
   - Split long exception message string to comply with 120-character limit

2. Add shared parseUniqueKeyUpdateMode() helper methods in CreateRoutineLoadInfo
   - parseUniqueKeyUpdateMode(String): returns TUniqueKeyUpdateMode or null
   - parseAndValidateUniqueKeyUpdateMode(String): validates and throws on error
   - Replaces duplicated switch/if-else logic across 4 files

3. Add OlapTable.validateForFlexiblePartialUpdate() method
   - Centralizes table-level validation (MoW, skip_bitmap, light_schema_change, variant)
   - Used by CreateRoutineLoadInfo, RoutineLoadJob, and NereidsStreamLoadPlanner

4. Update all callers to use shared validation methods
   - Reduces code duplication and ensures consistent error messages

5. Allow jsonpaths, WHERE clause, and MERGE/DELETE with flexible partial update
   - Removed restrictions that blocked these features
1. Fix exception type mismatch in KafkaRoutineLoadJob.replayModifyProperties
   - Changed catch block from DdlException to UserException since
     modifyPropertiesInternal now throws UserException

2. Fix setSchemaForPartialUpdate not called for flexible partial update
   - Changed condition from isPartialUpdate to check both
     UPDATE_FIXED_COLUMNS and UPDATE_FLEXIBLE_COLUMNS modes
   - Aligns with StreamLoadHandler behavior

3. Update tests to allow WHERE clause and jsonpaths with flexible partial update
   - Tests 4, 7, 16, 18 now verify these features work correctly
   - Added expected output for new success test cases
- Test parseUniqueKeyUpdateMode() with valid/invalid mode strings
- Test parseAndValidateUniqueKeyUpdateMode() with exception handling
- Test backward compatibility: partial_columns=true maps to UPDATE_FIXED_COLUMNS
- Test unique_key_update_mode takes precedence over partial_columns
Test the backward compatibility and precedence logic directly
without calling gsonPostProcess() which requires origStmt
Use 'can only support' format instead of 'requires' to match
existing test expectations in test_flexible_partial_update_restricts.groovy
Move edit log operations outside the write lock to improve transaction
throughput for concurrent operations on different tables within the
same database.

Changes:
- Add unprotectUpdateInMemoryState() for in-memory updates inside lock
- Add persistTransactionState() for edit log writes outside lock
- Refactor beginTransaction, commitTransaction, finishTransaction,
  abortTransaction, and removeUselessTxns to use the new pattern
- Refactor unprotectUpsertTransactionState to delegate to new methods

This reduces lock hold time from milliseconds (I/O bound) to microseconds
(memory only), enabling higher concurrency for multi-table workloads.

Safety is maintained because:
- In-memory state is updated atomically within the write lock
- Edit log failures call System.exit(-1), preventing inconsistent state
- Replay path remains unchanged (uses isReplay=true)

Fixes: apache#53642
Copilot AI review requested due to automatic review settings January 17, 2026 06:13
@hello-stephen
Copy link
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?

@dataroaring
Copy link
Contributor Author

run buildall

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces two major changes: (1) reduces lock contention in DatabaseTransactionMgr by moving edit log operations outside write locks, and (2) adds support for flexible partial update mode in routine load operations.

Changes:

  • Refactored DatabaseTransactionMgr to split in-memory state updates from edit log persistence for reduced lock contention
  • Added comprehensive support for unique_key_update_mode property with new UPDATE_FLEXIBLE_COLUMNS mode
  • Introduced validation framework for flexible partial update constraints in OlapTable

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
fe/fe-core/src/main/java/org/apache/doris/transaction/DatabaseTransactionMgr.java Core refactoring: splits unprotectUpsertTransactionState into unprotectUpdateInMemoryState (inside lock) and persistTransactionState (outside lock); applies pattern to transaction lifecycle methods
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/CreateRoutineLoadInfo.java Adds parsing, validation, and property management for unique_key_update_mode with backward compatibility for partial_columns
fe/fe-core/src/main/java/org/apache/doris/load/routineload/RoutineLoadJob.java Updates job properties handling to support new update mode; adds validation logic for ALTER operations
fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java Adds validateForFlexiblePartialUpdate() method to centralize table-level constraint validation
fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsStreamLoadPlanner.java Refactors flexible partial update validation to use centralized OlapTable method
fe/fe-core/src/main/java/org/apache/doris/nereids/load/NereidsRoutineLoadTaskInfo.java Updates constructor to accept TUniqueKeyUpdateMode instead of boolean isPartialUpdate flag
fe/fe-core/src/main/java/org/apache/doris/load/routineload/KafkaRoutineLoadJob.java Updates method signature and calls to support UserException for ALTER operations
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AlterRoutineLoadCommand.java Adds validation for unique_key_update_mode property during ALTER ROUTINE LOAD
regression-test/suites/load_p0/routine_load/test_routine_load_flexible_partial_update.groovy Comprehensive 1414-line test suite with 21 test cases covering flexible partial update feature
regression-test/data/load_p0/routine_load/test_routine_load_flexible_partial_update.out Expected test outputs for flexible partial update test cases
fe/fe-core/src/test/java/org/apache/doris/load/routineload/RoutineLoadJobTest.java Unit tests for unique_key_update_mode parsing, validation, and backward compatibility

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

writeUnlock();
}
// Persist edit log outside lock to reduce lock contention
persistTransactionState(transactionState);
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The persistTransactionState method is called outside the write lock with a transactionState variable that could potentially be null if an exception is thrown inside the try block before line 372. If any of the checks before line 372 throw an exception (such as checkRunningTxnExceedLimit at line 369), transactionState will remain null and calling persistTransactionState(transactionState) at line 384 would result in a NullPointerException. Consider adding a null check before calling persistTransactionState, or wrapping the call in a condition that ensures transactionState is not null.

Suggested change
persistTransactionState(transactionState);
if (transactionState != null) {
persistTransactionState(transactionState);
}

Copilot uses AI. Check for mistakes.
(
"max_batch_interval" = "10",
"format" = "json",
"jsonpaths" = '["\\$.id", "\\$.name", "\\$.score"]',
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent string escaping for jsonpaths property. Line 276 uses single quotes with backslash escaping '["\$.id", ...]', while line 982 uses double quotes with triple backslash escaping "[\"\$.id\", ...]". Both tests should use the same escaping pattern for consistency and readability. Consider standardizing on one approach throughout the test file.

Suggested change
"jsonpaths" = '["\\$.id", "\\$.name", "\\$.score"]',
"jsonpaths" = "[\\\"\\$.id\\", \\\"\\$.name\\", \\\"\\$.score\\"]",

Copilot uses AI. Check for mistakes.
@doris-robot
Copy link

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

------ Round 1 ----------------------------------
q1	17655	4262	4123	4123
q2	2013	351	242	242
q3	10169	1237	693	693
q4	10205	838	300	300
q5	7517	2071	1790	1790
q6	188	171	136	136
q7	918	771	651	651
q8	9271	1381	1123	1123
q9	4759	4653	4542	4542
q10	6757	1815	1411	1411
q11	511	290	278	278
q12	676	737	578	578
q13	17769	3812	3097	3097
q14	284	286	288	286
q15	581	508	508	508
q16	680	696	612	612
q17	653	735	557	557
q18	6535	6463	6536	6463
q19	1109	975	611	611
q20	395	356	258	258
q21	2954	2447	2247	2247
q22	1064	988	945	945
Total cold run time: 102663 ms
Total hot run time: 31451 ms

----- Round 2, with runtime_filter_mode=off -----
q1	4082	4051	4028	4028
q2	345	403	313	313
q3	2091	2584	2179	2179
q4	1289	1776	1289	1289
q5	4119	4055	4070	4055
q6	216	170	128	128
q7	1884	1803	1671	1671
q8	2773	2448	2433	2433
q9	7301	7288	7172	7172
q10	2416	2700	2356	2356
q11	524	484	455	455
q12	710	775	641	641
q13	3705	3998	3367	3367
q14	422	331	282	282
q15	627	604	565	565
q16	641	668	615	615
q17	1119	1302	1404	1302
q18	8101	8075	7785	7785
q19	876	860	822	822
q20	1990	2103	1885	1885
q21	4712	4475	4234	4234
q22	1039	1018	969	969
Total cold run time: 50982 ms
Total hot run time: 48546 ms

@doris-robot
Copy link

TPC-DS: Total hot run time: 174418 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 96357c83754b3cbdeae0f7c23b6a7a7d23995652, data reload: false

query5	4433	613	486	486
query6	329	230	205	205
query7	4216	465	258	258
query8	339	265	244	244
query9	8726	2905	2877	2877
query10	544	361	334	334
query11	15309	15330	14967	14967
query12	180	113	114	113
query13	1253	501	380	380
query14	5786	3024	2822	2822
query14_1	2601	2616	2633	2616
query15	196	192	176	176
query16	978	469	457	457
query17	1067	648	519	519
query18	2412	424	320	320
query19	210	215	183	183
query20	116	111	110	110
query21	209	135	116	116
query22	3787	3927	3818	3818
query23	15939	15680	15361	15361
query23_1	15513	15592	15448	15448
query24	7175	1521	1153	1153
query24_1	1149	1189	1159	1159
query25	507	429	382	382
query26	1235	257	150	150
query27	2776	435	272	272
query28	4609	2163	2152	2152
query29	736	541	411	411
query30	308	240	207	207
query31	799	623	551	551
query32	90	79	74	74
query33	511	355	309	309
query34	904	878	540	540
query35	715	744	692	692
query36	872	917	847	847
query37	130	95	84	84
query38	2657	2705	2587	2587
query39	786	753	742	742
query39_1	740	713	709	709
query40	216	159	118	118
query41	67	68	65	65
query42	103	101	107	101
query43	427	439	420	420
query44	1303	742	742	742
query45	187	185	172	172
query46	832	943	573	573
query47	1424	1510	1436	1436
query48	315	320	240	240
query49	597	446	361	361
query50	607	269	211	211
query51	3765	3764	3879	3764
query52	103	104	92	92
query53	295	321	267	267
query54	277	263	257	257
query55	80	81	77	77
query56	304	305	335	305
query57	979	1017	929	929
query58	276	266	260	260
query59	2066	2175	2032	2032
query60	349	331	327	327
query61	153	147	148	147
query62	375	346	306	306
query63	295	271	268	268
query64	4969	1345	1065	1065
query65	3766	3714	3726	3714
query66	1472	449	329	329
query67	15610	15574	15540	15540
query68	2487	1100	779	779
query69	451	374	332	332
query70	973	955	939	939
query71	318	316	298	298
query72	5485	3363	3409	3363
query73	615	736	320	320
query74	8830	8708	8560	8560
query75	2715	2814	2471	2471
query76	2276	1051	685	685
query77	361	393	310	310
query78	9748	9795	9169	9169
query79	1472	903	571	571
query80	1308	563	491	491
query81	537	268	237	237
query82	986	148	110	110
query83	332	253	236	236
query84	249	109	98	98
query85	898	511	420	420
query86	410	304	295	295
query87	2826	2840	2757	2757
query88	3463	2566	2560	2560
query89	374	341	335	335
query90	2012	174	163	163
query91	188	157	138	138
query92	73	82	68	68
query93	1073	895	544	544
query94	644	322	285	285
query95	577	388	322	322
query96	634	503	231	231
query97	2353	2383	2346	2346
query98	213	200	197	197
query99	594	598	517	517
Total cold run time: 246600 ms
Total hot run time: 174418 ms

@doris-robot
Copy link

ClickBench: Total hot run time: 27.05 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 96357c83754b3cbdeae0f7c23b6a7a7d23995652, data reload: false

query1	0.06	0.05	0.05
query2	0.08	0.05	0.04
query3	0.26	0.08	0.09
query4	1.60	0.11	0.11
query5	0.27	0.26	0.25
query6	1.14	0.66	0.66
query7	0.03	0.02	0.02
query8	0.05	0.03	0.04
query9	0.57	0.50	0.49
query10	0.56	0.54	0.56
query11	0.14	0.09	0.10
query12	0.15	0.10	0.11
query13	0.59	0.58	0.59
query14	0.94	0.94	0.96
query15	0.78	0.78	0.79
query16	0.41	0.41	0.43
query17	1.03	1.00	0.98
query18	0.23	0.22	0.21
query19	1.88	1.75	1.76
query20	0.02	0.01	0.01
query21	15.46	0.28	0.14
query22	4.86	0.06	0.04
query23	15.68	0.28	0.10
query24	1.84	0.69	0.58
query25	0.08	0.10	0.07
query26	0.14	0.12	0.14
query27	0.05	0.05	0.10
query28	5.33	1.06	0.89
query29	12.56	3.89	3.17
query30	0.28	0.14	0.13
query31	2.81	0.61	0.40
query32	3.23	0.55	0.45
query33	3.02	3.05	3.11
query34	16.20	5.11	4.45
query35	4.44	4.44	4.43
query36	0.63	0.51	0.50
query37	0.10	0.06	0.07
query38	0.07	0.04	0.04
query39	0.04	0.03	0.04
query40	0.17	0.14	0.13
query41	0.08	0.03	0.04
query42	0.04	0.03	0.03
query43	0.04	0.04	0.04
Total cold run time: 97.94 s
Total hot run time: 27.05 s

@hello-stephen
Copy link
Contributor

FE UT Coverage Report

Increment line coverage 27.61% (45/163) 🎉
Increment coverage report
Complete coverage report

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.

[Enhancement] reduce lock contention in DatabaseTransactionMgr

3 participants