Skip to content

Commit 18e8462

Browse files
Merge pull request #1782 from jakub-nowakowski-percona/PXB-3502-9.7
PXB-3502 [9.7]: Process largest files first in all parallel phases
2 parents 59d7713 + c70f97d commit 18e8462

8 files changed

Lines changed: 837 additions & 124 deletions

File tree

storage/innobase/xtrabackup/src/backup_copy.cc

Lines changed: 168 additions & 116 deletions
Large diffs are not rendered by default.

storage/innobase/xtrabackup/src/xtrabackup.cc

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ Place, Suite 330, Boston, MA 02111-1307 USA
8585
#include <sql/current_thd.h>
8686
#include <sql/srv_session.h>
8787
#include <table_cache.h>
88+
#include <algorithm>
8889
#include <list>
8990
#include <set>
9091
#include <sstream>
92+
#include <string_view>
9193
#include "sql/signal_handler.h"
9294

9395
#include <api0api.h>
@@ -608,6 +610,32 @@ datafiles_iter_t *datafiles_iter_new(
608610
return (DB_SUCCESS);
609611
});
610612

613+
// Order tablespaces largest first so parallel consumers reduce the long
614+
// tail of work caused by late-discovered big files. File name is used as a
615+
// deterministic tie-breaker for equal-size files.
616+
std::sort(it->nodes.begin(), it->nodes.end(),
617+
[](const fil_node_t *lhs, const fil_node_t *rhs) {
618+
auto file_size = [](const fil_node_t *node) -> uint64_t {
619+
if (!node || !node->space) {
620+
return 0;
621+
}
622+
const page_size_t ps(node->space->flags);
623+
return static_cast<uint64_t>(node->size) * ps.physical();
624+
};
625+
626+
auto file_name = [](const fil_node_t *node) -> std::string_view {
627+
return node && node->name ? node->name : "";
628+
};
629+
630+
const auto lhs_size = file_size(lhs);
631+
const auto rhs_size = file_size(rhs);
632+
if (lhs_size != rhs_size) {
633+
return lhs_size > rhs_size;
634+
}
635+
636+
return file_name(lhs) < file_name(rhs);
637+
});
638+
611639
it->i = it->nodes.begin();
612640

613641
return it;
@@ -3284,10 +3312,15 @@ bool xtrabackup_copy_datafile_func(fil_node_t *node, uint thread_n,
32843312

32853313
if (xtrabackup_stream) {
32863314
xb::info() << action << " file with space_id " << node->space->id << " "
3287-
<< node_path;
3315+
<< node_path << ", size " << cursor.statinfo.st_size << " ("
3316+
<< xtrabackup::utils::human_readable(cursor.statinfo.st_size)
3317+
<< ")";
32883318
} else {
32893319
xb::info() << action << " file with space_id " << node->space->id << " "
3290-
<< node_path << " to " << dstfile->path;
3320+
<< node_path << " to " << dstfile->path << ", size "
3321+
<< cursor.statinfo.st_size << " ("
3322+
<< xtrabackup::utils::human_readable(cursor.statinfo.st_size)
3323+
<< ")";
32913324
}
32923325

32933326
/* The main copy loop */
@@ -5718,7 +5751,8 @@ void process_datadir_l2cbk(const char *datadir, const char *dbname,
57185751
(strlen(name) > suffix_len &&
57195752
strcmp(name + strlen(name) - suffix_len, suffix) == 0)) {
57205753
check_datadir_enctry_access(name, &statinfo);
5721-
func(datadir_entry_t(datadir, path, dbname, name, false), data);
5754+
func(datadir_entry_t(datadir, path, dbname, name, false, statinfo.st_size),
5755+
data);
57225756
}
57235757
}
57245758

@@ -5764,7 +5798,8 @@ void process_datadir_l1cbk(const char *datadir, const char *path,
57645798
(strlen(name) > suffix_len &&
57655799
strcmp(name + strlen(name) - suffix_len, suffix) == 0)) {
57665800
check_datadir_enctry_access(name, &statinfo);
5767-
func(datadir_entry_t(datadir, path, "", name, false), data);
5801+
func(datadir_entry_t(datadir, path, "", name, false, statinfo.st_size),
5802+
data);
57685803
}
57695804
}
57705805

storage/innobase/xtrabackup/src/xtrabackup.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,11 +335,10 @@ struct datadir_entry_t {
335335
std::string db_name;
336336
std::string file_name;
337337
std::string rel_path;
338-
bool is_empty_dir;
339-
ssize_t file_size;
338+
bool is_empty_dir = false;
339+
ssize_t file_size = -1;
340340

341-
datadir_entry_t()
342-
: datadir(), path(), db_name(), file_name(), rel_path(), is_empty_dir() {}
341+
datadir_entry_t() = default;
343342

344343
datadir_entry_t(const datadir_entry_t &) = default;
345344

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#
2+
# Test that decompression processes largest files first.
3+
# With --parallel=1 the processing order is deterministic and must be
4+
# strictly descending by file size.
5+
#
6+
7+
require_lz4
8+
9+
start_server --innodb_file_per_table
10+
11+
# Create tables with dramatically different sizes.
12+
mysql -e "CREATE TABLE t_large (a INT AUTO_INCREMENT PRIMARY KEY, b VARCHAR(255), c VARCHAR(255)) ENGINE=InnoDB" test
13+
mysql -e "CREATE TABLE t_medium (a INT AUTO_INCREMENT PRIMARY KEY, b VARCHAR(255)) ENGINE=InnoDB" test
14+
mysql -e "CREATE TABLE t_small (a INT PRIMARY KEY) ENGINE=InnoDB" test
15+
16+
# Fill tables
17+
# Use recursive CTE for compatibility with both Oracle MySQL and Percona Server.
18+
mysql test <<EOF
19+
INSERT INTO t_large (b, c)
20+
WITH RECURSIVE seq AS (SELECT 1 AS n UNION ALL SELECT n+1 FROM seq WHERE n < 500)
21+
SELECT REPEAT('x', 200), REPEAT('y', 200) FROM seq;
22+
INSERT INTO t_medium (b)
23+
WITH RECURSIVE seq AS (SELECT 1 AS n UNION ALL SELECT n+1 FROM seq WHERE n < 100)
24+
SELECT REPEAT('m', 200) FROM seq;
25+
INSERT INTO t_small VALUES (1);
26+
EOF
27+
mysql -e "FLUSH TABLES" test
28+
29+
###############################################################################
30+
# Test: Decompress with --parallel=1 processes largest files first
31+
###############################################################################
32+
33+
# Take a compressed backup
34+
xtrabackup --backup --target-dir=$topdir/backup --compress=lz4 --parallel=1
35+
36+
# Show compressed file sizes for debugging
37+
ls -lS $topdir/backup/test/*.lz4 2>/dev/null || true
38+
39+
# Decompress with --parallel=1
40+
xtrabackup --decompress --target-dir=$topdir/backup --parallel=1
41+
42+
# The decompress log lines have format: "decompressing ./test/t_xxx.ibd.lz4"
43+
# Check that .ibd.lz4 files for our test tables appear in largest-first order.
44+
grep "decompressing.*test/t_" $OUTFILE | head -10
45+
46+
large_line=$(grep -n "decompressing.*test/t_large" $OUTFILE | head -1 | cut -d: -f1)
47+
medium_line=$(grep -n "decompressing.*test/t_medium" $OUTFILE | head -1 | cut -d: -f1)
48+
small_line=$(grep -n "decompressing.*test/t_small" $OUTFILE | head -1 | cut -d: -f1)
49+
50+
vlog "Decompress order: t_large at line $large_line, t_medium at line $medium_line, t_small at line $small_line"
51+
52+
if [ -z "$large_line" ] || [ -z "$medium_line" ] || [ -z "$small_line" ]; then
53+
die "Could not find all tables in decompress log"
54+
fi
55+
56+
if [ "$large_line" -ge "$medium_line" ]; then
57+
die "FAIL: t_large (line $large_line) was not decompressed before t_medium (line $medium_line)"
58+
fi
59+
60+
if [ "$medium_line" -ge "$small_line" ]; then
61+
die "FAIL: t_medium (line $medium_line) was not decompressed before t_small (line $small_line)"
62+
fi
63+
64+
vlog "PASS: Decompress processes files in largest-first order"
65+
66+
###############################################################################
67+
# Verify the backup is usable (prepare + restore)
68+
###############################################################################
69+
70+
xtrabackup --prepare --target-dir=$topdir/backup
71+
72+
stop_server
73+
rm -rf $mysql_datadir
74+
75+
xtrabackup --copy-back --target-dir=$topdir/backup --parallel=1
76+
77+
start_server
78+
79+
# Verify data
80+
count=$(mysql -N -e "SELECT COUNT(*) FROM t_large" test)
81+
if [ "$count" -lt 500 ]; then
82+
die "FAIL: t_large has only $count rows after restore, expected >= 500"
83+
fi
84+
85+
vlog "PASS: Data verified after restore"
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#
2+
# Test that parallel processing handles largest RocksDB files first.
3+
# With --parallel=1 the processing order is deterministic and must be
4+
# strictly descending by file size.
5+
#
6+
7+
require_rocksdb
8+
9+
start_server
10+
11+
init_rocksdb
12+
13+
# Create a RocksDB table with enough data to produce multiple .sst files
14+
# of different sizes after compaction.
15+
mysql -e "CREATE TABLE t_large (a INT AUTO_INCREMENT PRIMARY KEY, b VARCHAR(255), c VARCHAR(255)) ENGINE=ROCKSDB" test
16+
mysql -e "CREATE TABLE t_small (a INT PRIMARY KEY) ENGINE=ROCKSDB" test
17+
18+
# Fill t_large to produce large .sst files (bulk insert for speed)
19+
# Use recursive CTE for compatibility with both Oracle MySQL and Percona Server.
20+
mysql test <<EOF
21+
SET SESSION cte_max_recursion_depth = 10000;
22+
INSERT INTO t_large (b, c)
23+
WITH RECURSIVE seq AS (SELECT 1 AS n UNION ALL SELECT n+1 FROM seq WHERE n < 1000)
24+
SELECT REPEAT('x', 200), REPEAT('y', 200) FROM seq;
25+
INSERT INTO t_small VALUES (1);
26+
EOF
27+
28+
# Force RocksDB to flush memtable to SST files
29+
mysql -e "SET GLOBAL rocksdb_force_flush_memtable_now = ON"
30+
31+
# Give compaction time to settle
32+
sleep 2
33+
34+
# Show .sst file sizes for debugging
35+
ls -lS $mysql_datadir/.rocksdb/*.sst 2>/dev/null >&2 || true
36+
37+
###############################################################################
38+
# Test 1: Backup with --parallel=1 processes largest .sst first
39+
###############################################################################
40+
41+
xtrabackup --backup --target-dir=$topdir/backup --parallel=1
42+
43+
# The log now prints ", size <bytes> (<human>)" for each file.
44+
# Extract sizes from .sst copy lines and verify non-increasing order.
45+
backup_sst_log=$topdir/backup_sst_lines.txt
46+
grep "Copying.*\.sst.*size [0-9]" $OUTFILE | grep -v "Done:" > $backup_sst_log || true
47+
48+
vlog "SST files copied during backup:"
49+
50+
prev_size=999999999999
51+
ordered=true
52+
count=0
53+
54+
while IFS= read -r line; do
55+
file_size=$(echo "$line" | sed -n 's/.*, size \([0-9]*\) .*/\1/p')
56+
file_name=$(echo "$line" | sed -n 's/.*Copying \([^ ]*\.sst\).*/\1/p')
57+
if [ -z "$file_size" ] || [ -z "$file_name" ]; then
58+
continue
59+
fi
60+
vlog " $file_name: $file_size bytes"
61+
if [ "$file_size" -gt "$prev_size" ]; then
62+
ordered=false
63+
vlog " ERROR: size $file_size > previous $prev_size (not largest-first)"
64+
fi
65+
prev_size=$file_size
66+
count=$((count + 1))
67+
done < $backup_sst_log
68+
69+
if [ "$count" -lt 2 ]; then
70+
die "Only $count .sst files found in backup log, need at least 2 to test ordering"
71+
fi
72+
73+
if [ "$ordered" = "false" ]; then
74+
die "FAIL: RocksDB .sst files were not backed up in largest-first order"
75+
fi
76+
77+
vlog "PASS: Backup processes $count RocksDB .sst files in largest-first order"
78+
79+
###############################################################################
80+
# Test 2: Copy-back with --parallel=1 processes largest .sst first
81+
###############################################################################
82+
83+
xtrabackup --prepare --target-dir=$topdir/backup
84+
85+
stop_server
86+
rm -rf $mysql_datadir
87+
88+
xtrabackup --copy-back --target-dir=$topdir/backup --parallel=1
89+
90+
# Exclude checkpoint lines (those are from the backup phase).
91+
copyback_sst_log=$topdir/copyback_sst_lines.txt
92+
grep "Copying.*\.sst.*size [0-9]" $OUTFILE | grep -v "Done:" | grep -v "checkpoint" > $copyback_sst_log || true
93+
94+
vlog "SST files copied during copy-back:"
95+
96+
prev_size=999999999999
97+
ordered=true
98+
count=0
99+
100+
while IFS= read -r line; do
101+
file_size=$(echo "$line" | sed -n 's/.*, size \([0-9]*\) .*/\1/p')
102+
file_name=$(echo "$line" | sed -n 's/.*Copying \([^ ]*\.sst\).*/\1/p')
103+
if [ -z "$file_size" ] || [ -z "$file_name" ]; then
104+
continue
105+
fi
106+
vlog " $file_name: $file_size bytes"
107+
if [ "$file_size" -gt "$prev_size" ]; then
108+
ordered=false
109+
vlog " ERROR: size $file_size > previous $prev_size (not largest-first)"
110+
fi
111+
prev_size=$file_size
112+
count=$((count + 1))
113+
done < $copyback_sst_log
114+
115+
if [ "$count" -lt 2 ]; then
116+
die "Only $count .sst files found in copy-back log, need at least 2 to test ordering"
117+
fi
118+
119+
if [ "$ordered" = "false" ]; then
120+
die "FAIL: RocksDB .sst files were not copied back in largest-first order"
121+
fi
122+
123+
vlog "PASS: Copy-back processes $count RocksDB .sst files in largest-first order"

0 commit comments

Comments
 (0)