Skip to content

Commit 5ba03ca

Browse files
committed
Bug #32089028 CONCURRENTLY UPDATING MANY JSON DOCUMENTS STEADILY INCREASES IBD FILE SIZE
Background: Currently the purge system groups the undo records based on table_id and then it is distributed to different purge threads. This way all undo records belonging to a single table is handled by the same purge thread. This grouping is done to avoid contention b/w purge threads. Our documentation says this: "If DML action is concentrated on a single table or a few tables, keep the setting low so that the threads do not contend with each other for access to the tables.". Problem: But, what if all the DMLs are happening on only one table. The grouping based on table_id makes the distribution of work between purge threads lopsided. For each purge batch, only one thread will be working on the undo records of the single table. So the purge is slower and the purge lag is higher. Increasing the number of purge threads will not help to purge faster. Solution: Auto tune solution. Overall Idea: 1. Create only as many groups as there are purge threads. 2. Distributed the undo records based on table_id between these groups. 3. Check if the undo records are uniformly distributed. 4. If they are not uniformly distributed, redistribute. Maximum (MAX) records per purge group: 1. Let total number of undo records in a purge batch be T. 2. Let M be the total number of purge threads. 3. Maximum number of records per group is (T + M)/M. Minimum (MIN) records per purge group: 1. Let MAX be the the maximum number of records per purge group. 2. Let M be the total number of purge threads. 3. If MAX > M, then MIN = MAX - M. 4. If MAX =< M, then MIN = 0. Redistribution: 1. Let i be the first group. 2. If the ith group has more records than it should, move extra records to the next group. 3. If it is the last group, move extra records to the first group. 4. Increment i. And go to step 1. 5. Single pass is sufficient. But if extra records are moved from last to first group, then one more pass is needed. In the 2nd pass, stop in the first group having < MAX records. What is uniform distribution in this context: 1. All groups have record count between MIN and MAX. rb#26108 approved by Debarun Banerjee <[email protected]>
1 parent b81014d commit 5ba03ca

24 files changed

+558
-176
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
SET GLOBAL innodb_fast_shutdown=0;
2+
# restart: --debug=d,innodb_purge_sleep_12
3+
CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 JSON);
4+
SET @data_1 = REPEAT('Ax', 16384);
5+
SET @data_2 = REPEAT('By', 16384);
6+
SET @data_3 = REPEAT('Cz', 16384);
7+
SET @data_4 = REPEAT('Pn', 16384);
8+
SET @json_doc_1 = CONCAT('["', @data_1, '", "', @data_2, '", "', @data_3, '", "', @data_4, '"]');
9+
INSERT INTO t1 (f1, f2) VALUES (1, @json_doc_1);
10+
start transaction;
11+
SELECT f1 FROM t1;
12+
f1
13+
1
14+
SET @e1 = REPEAT('Do', 16384);
15+
SET @e2 = REPEAT('Em', 16384);
16+
SET @e3 = REPEAT('Fi', 16384);
17+
SET @e4 = REPEAT('Gj', 16384);
18+
SET @json_doc_2 = CONCAT('["', @e1, '", "', @e2, '", "', @e3, '", "', @e4, '"]');
19+
UPDATE t1 SET f2 = @json_doc2;
20+
SET @e4 = REPEAT('Hu', 16384);
21+
SET @elem4 = CONCAT('"', @e4, '"');
22+
UPDATE t1 SET f2 = JSON_SET(f2, '$[3]', @elem4) WHERE f1 = 1;
23+
DELETE FROM t1;
24+
commit;
25+
DROP TABLE t1;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--max_allowed_packet=500M
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--source include/count_sessions.inc
2+
--source include/have_debug.inc
3+
4+
SET GLOBAL innodb_fast_shutdown=0;
5+
let $restart_parameters = restart: --debug=d,innodb_purge_sleep_12;
6+
--source include/restart_mysqld.inc
7+
8+
CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 JSON);
9+
SET @data_1 = REPEAT('Ax', 16384);
10+
SET @data_2 = REPEAT('By', 16384);
11+
SET @data_3 = REPEAT('Cz', 16384);
12+
SET @data_4 = REPEAT('Pn', 16384);
13+
SET @json_doc_1 = CONCAT('["', @data_1, '", "', @data_2, '", "', @data_3, '", "', @data_4, '"]');
14+
INSERT INTO t1 (f1, f2) VALUES (1, @json_doc_1);
15+
16+
connect (con1,localhost,root,,);
17+
connection con1;
18+
start transaction;
19+
SELECT f1 FROM t1;
20+
21+
connection default;
22+
SET @e1 = REPEAT('Do', 16384);
23+
SET @e2 = REPEAT('Em', 16384);
24+
SET @e3 = REPEAT('Fi', 16384);
25+
SET @e4 = REPEAT('Gj', 16384);
26+
SET @json_doc_2 = CONCAT('["', @e1, '", "', @e2, '", "', @e3, '", "', @e4, '"]');
27+
UPDATE t1 SET f2 = @json_doc2;
28+
29+
SET @e4 = REPEAT('Hu', 16384);
30+
SET @elem4 = CONCAT('"', @e4, '"');
31+
UPDATE t1 SET f2 = JSON_SET(f2, '$[3]', @elem4) WHERE f1 = 1;
32+
DELETE FROM t1;
33+
34+
connection con1;
35+
commit;
36+
connection default;
37+
disconnect con1;
38+
--source include/wait_innodb_all_purged.inc
39+
--source include/wait_until_count_sessions.inc
40+
41+
DROP TABLE t1;

storage/innobase/btr/btr0btr.cc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,7 +2269,8 @@ static rec_t *btr_insert_into_right_sibling(uint32_t flags, btr_cur_t *cursor,
22692269
/* We have to change the parent node pointer */
22702270

22712271
compressed = btr_cur_pessimistic_delete(&err, TRUE, &next_father_cursor,
2272-
BTR_CREATE_FLAG, false, 0, 0, 0, mtr);
2272+
BTR_CREATE_FLAG, false, 0, 0, 0, mtr,
2273+
nullptr, nullptr);
22732274

22742275
ut_a(err == DB_SUCCESS);
22752276

@@ -2826,8 +2827,9 @@ void btr_node_ptr_delete(dict_index_t *index, buf_block_t *block, mtr_t *mtr) {
28262827
/* Delete node pointer on father page */
28272828
btr_page_get_father(index, block, mtr, &cursor);
28282829

2829-
compressed = btr_cur_pessimistic_delete(&err, TRUE, &cursor, BTR_CREATE_FLAG,
2830-
false, 0, 0, 0, mtr);
2830+
compressed =
2831+
btr_cur_pessimistic_delete(&err, TRUE, &cursor, BTR_CREATE_FLAG, false, 0,
2832+
0, 0, mtr, nullptr, nullptr);
28312833
ut_a(err == DB_SUCCESS);
28322834

28332835
if (!compressed) {
@@ -3374,8 +3376,9 @@ ibool btr_compress(
33743376
lock_prdt_page_free_from_discard(block, lock_sys->prdt_page_hash);
33753377
lock_rec_free_all_from_discard_page(block);
33763378
} else {
3377-
compressed = btr_cur_pessimistic_delete(
3378-
&err, TRUE, &cursor2, BTR_CREATE_FLAG, false, 0, 0, 0, mtr);
3379+
compressed =
3380+
btr_cur_pessimistic_delete(&err, TRUE, &cursor2, BTR_CREATE_FLAG,
3381+
false, 0, 0, 0, mtr, nullptr, nullptr);
33793382
ut_a(err == DB_SUCCESS);
33803383

33813384
if (!compressed) {

storage/innobase/btr/btr0cur.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4625,7 +4625,7 @@ ibool btr_cur_pessimistic_delete(dberr_t *err, ibool has_reserved_extents,
46254625
btr_cur_t *cursor, uint32_t flags,
46264626
bool rollback, trx_id_t trx_id,
46274627
undo_no_t undo_no, ulint rec_type, mtr_t *mtr,
4628-
btr_pcur_t *pcur) {
4628+
btr_pcur_t *pcur, purge_node_t *node) {
46294629
DBUG_TRACE;
46304630

46314631
buf_block_t *block;
@@ -4670,7 +4670,8 @@ ibool btr_cur_pessimistic_delete(dberr_t *err, ibool has_reserved_extents,
46704670

46714671
/* The following call will restart the btr_mtr, which could change the
46724672
cursor position. */
4673-
btr_ctx.free_externally_stored_fields(trx_id, undo_no, rollback, rec_type);
4673+
btr_ctx.free_externally_stored_fields(trx_id, undo_no, rollback, rec_type,
4674+
node);
46744675

46754676
/* The cursor position could have changed now. */
46764677
if (pcur != nullptr) {

storage/innobase/dict/dict0dict.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5368,7 +5368,8 @@ dberr_t DDTableBuffer::remove(table_id_t id) {
53685368
DEBUG_SYNC_C("delete_metadata_before");
53695369

53705370
btr_cur_pessimistic_delete(&error, false, btr_pcur_get_btr_cur(&pcur),
5371-
BTR_CREATE_FLAG, false, 0, 0, 0, &mtr);
5371+
BTR_CREATE_FLAG, false, 0, 0, 0, &mtr, nullptr,
5372+
nullptr);
53725373
ut_ad(error == DB_SUCCESS);
53735374
}
53745375

storage/innobase/fsp/fsp0fsp.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,10 +2187,13 @@ page_no_t fseg_get_nth_frag_page_no(
21872187
mtr_t *mtr MY_ATTRIBUTE((unused)))
21882188
/*!< in/out: mini-transaction */
21892189
{
2190+
#ifdef UNIV_DEBUG
2191+
const std::size_t n_slots = FSEG_FRAG_ARR_N_SLOTS;
21902192
ut_ad(inode && mtr);
2191-
ut_ad(n < FSEG_FRAG_ARR_N_SLOTS);
2193+
ut_ad(n < n_slots);
21922194
ut_ad(mtr_memo_contains_page(mtr, inode, MTR_MEMO_PAGE_SX_FIX));
21932195
ut_ad(mach_read_from_4(inode + FSEG_MAGIC_N) == FSEG_MAGIC_N_VALUE);
2196+
#endif /* UNIV_DEBUG */
21942197
return (mach_read_from_4(inode + FSEG_FRAG_ARR + n * FSEG_FRAG_SLOT_SIZE));
21952198
}
21962199

@@ -3436,7 +3439,8 @@ static void fseg_free_page_low(fseg_inode_t *seg_inode,
34363439
/* The page is in the fragment pages of the segment */
34373440

34383441
for (i = 0;; i++) {
3439-
if (fseg_get_nth_frag_page_no(seg_inode, i, mtr) == page_id.page_no()) {
3442+
const page_no_t page_no = fseg_get_nth_frag_page_no(seg_inode, i, mtr);
3443+
if (page_no == page_id.page_no()) {
34403444
fseg_set_nth_frag_page_no(seg_inode, i, FIL_NULL, mtr);
34413445
break;
34423446
}

storage/innobase/gis/gis0rtree.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,8 +1633,9 @@ void rtr_node_ptr_delete(dict_index_t *index, btr_cur_t *sea_cur,
16331633
ibool compressed;
16341634
dberr_t err;
16351635

1636-
compressed = btr_cur_pessimistic_delete(&err, TRUE, sea_cur, BTR_CREATE_FLAG,
1637-
false, 0, 0, 0, mtr);
1636+
compressed =
1637+
btr_cur_pessimistic_delete(&err, TRUE, sea_cur, BTR_CREATE_FLAG, false, 0,
1638+
0, 0, mtr, nullptr, nullptr);
16381639
ut_a(err == DB_SUCCESS);
16391640

16401641
if (!compressed) {

storage/innobase/ibuf/ibuf0ibuf.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4014,7 +4014,7 @@ static MY_ATTRIBUTE((warn_unused_result)) ibool ibuf_delete_rec(
40144014
root = ibuf_tree_root_get(mtr);
40154015

40164016
btr_cur_pessimistic_delete(&err, TRUE, btr_pcur_get_btr_cur(pcur), 0, false,
4017-
0, 0, 0, mtr);
4017+
0, 0, 0, mtr, nullptr, nullptr);
40184018
ut_a(err == DB_SUCCESS);
40194019

40204020
#ifdef UNIV_IBUF_COUNT_DEBUG

storage/innobase/include/btr0cur.h

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -512,19 +512,20 @@ ibool btr_cur_optimistic_delete_func(
512512
occur, the cursor stays valid: it points to successor of
513513
deleted record on function exit
514514
@param[in] flags BTR_CREATE_FLAG or 0
515-
@param[in] rollback True if performing rollback, false otherwise.
516-
@param[in] trx_id The current transaction id.
517-
@param[in] undo_no Undo number of the transaction. This is needed for
518-
rollback to savepoint of partially updated LOB.
519-
@param[in] rec_type Undo record type.
520-
@param[in] mtr Mini-transaction
521-
@param[in] pcur Persistent cursor on the record to delete.
515+
@param[in] rollback True if performing rollback, false otherwise.
516+
@param[in] trx_id The current transaction id.
517+
@param[in] undo_no Undo number of the transaction. This is needed for
518+
rollback to savepoint of partially updated LOB.
519+
@param[in] rec_type Undo record type.
520+
@param[in] mtr The mini transaction
521+
@param[in] pcur Persistent cursor on the record to delete.
522+
@param[in,out] node purge node or nullptr
522523
@return true if compression occurred */
523524
ibool btr_cur_pessimistic_delete(dberr_t *err, ibool has_reserved_extents,
524525
btr_cur_t *cursor, uint32_t flags,
525526
bool rollback, trx_id_t trx_id,
526527
undo_no_t undo_no, ulint rec_type, mtr_t *mtr,
527-
btr_pcur_t *pcur = nullptr);
528+
btr_pcur_t *pcur, purge_node_t *node);
528529

529530
/** Parses a redo log record of updating a record in-place.
530531
@return end of log record or NULL */

0 commit comments

Comments
 (0)