Skip to content

Commit 7c5705c

Browse files
PXB-3862 : Address PR #1780 review comments
https://perconadev.atlassian.net/browse/PXB-3862 - Rework probe_storage() in page terms and reorder its phases: every read now fetches whole 16KB pages at page-aligned offsets (the same shape as the copy loop's reads, and aligned as O_DIRECT requires), and the sequential bandwidth phase runs first, on pages nothing has touched yet, so cross-phase caching can only understate the round trip measured afterwards - understating it shrinks the resulting cost, the safe direction. One file descriptor serves both phases: pread carries no file position, and closing/reopening would evict nothing (the OS page cache is keyed by inode; the device's internal cache is beyond user space either way). - Return std::optional<Probe_result> from probe_storage() instead of carrying a valid flag. - Print sizes in the calibration and refusal log lines through xtrabackup::utils::human_readable(), consistent with the rest of the log. - Also reject ERANGE overflow from strtoull when parsing --page-tracking-merge-gap. - Document why the probe candidate is always an InnoDB tablespace (the datafiles iterator walks the InnoDB fil system only - files of other engines never appear in it - and the changed-page map is keyed by InnoDB space id), and why the UNIV_PAGE_SIZE divisibility assertion in rf_page_tracking_init holds structurally; its release-build failure mode degrades to merge_gap = 0, the old strictly-consecutive reads - a performance fallback, never a correctness risk. - Pass the read-filter context to range_get_next_page() so it accumulates into the stat counters directly, removing the out-parameters (and, likewise, the block scope in probe_storage's sequential phase); reword the option help text around "merge" and reflow it. - Update the probe-candidate comment now that PXB-3502 (PR #1774, largest-files-first ordering) is merged: the running-max scan does not depend on iteration order. - Rename the internal read-filter field max_gap to merge_gap so code, comments and the option name all use one term. - Name the calibration margin READ_REQUEST_COST_MARGIN = 1.5 and document at the constant why that value (bounded from both sides by the two instrumented machines), instead of an inline * 2 / 3. - Have probe_storage() drop its own sample regions from the OS page cache (posix_fadvise DONTNEED, ~16MB, surgical) before measuring, so on buffered servers the probe times the device rather than pages a previous read left in RAM; a no-op under O_DIRECT.
1 parent 37d67dc commit 7c5705c

7 files changed

Lines changed: 211 additions & 151 deletions

File tree

storage/innobase/xtrabackup/src/changed_page_tracking.cc

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
2222
#include "backup_mysql.h"
2323
#include "common.h"
2424
#include "components/mysqlbackup/backup_comp_constants.h"
25+
#include "read_filt.h"
2526
#include "srv0srv.h"
2627
#include "xb0xb.h"
2728
#include "xtrabackup.h"
@@ -273,16 +274,16 @@ range to its last: the caller then issues one pread covering the whole
273274
range (sliced into --read-buffer-size pieces when larger). A gap is
274275
the count of unchanged pages strictly BETWEEN two changed pages, not
275276
their id difference: between changed pages 1 and 4 the gap is 2
276-
(pages 2 and 3). Gaps <= max_gap are combined across, so nearby
277+
(pages 2 and 3). Gaps <= merge_gap are combined across, so nearby
277278
changed pages form one larger sequential read instead of one read
278279
request each; the gap pages become filler - read, then dropped by the
279280
incremental write filter's LSN check: extra read volume, never backup
280-
content. A gap > max_gap ends the range; runs of consecutive changed
281+
content. A gap > merge_gap ends the range; runs of consecutive changed
281282
pages (gap 0) always stay whole.
282283
283284
changed pages [1,3,6,9] (gaps 1,2,2):
284-
max_gap=0: 4 reads: [1] [3] [6] [9]
285-
max_gap=2: 1 read: [1-9] (filler 2,4,5,7,8 read, dropped)
285+
merge_gap=0: 4 reads: [1] [3] [6] [9]
286+
merge_gap=2: 1 read: [1-9] (filler 2,4,5,7,8 read, dropped)
286287
287288
Counting: the loop inspects one neighbouring pair per iteration and
288289
refuses (breaks) BEFORE the counting code, so reaching that code means
@@ -291,16 +292,16 @@ right there. A refused gap becomes the space between two ranges and is
291292
counted as skipped pages by the caller (rf_page_tracking_get_next_batch)
292293
instead - each gap lands in exactly one of the two. Worked trace on the
293294
shared example in read_filt.h (changed pages 1,2,3,4,7,20,21,
294-
max_gap=4): (4,7) gap 2 -> filler_pages += 2, combined_gaps = 1;
295+
merge_gap=4): (4,7) gap 2 -> filler_pages += 2, combined_gaps = 1;
295296
(7,20) gap 12 -> park on 7, range [1-7] ends; the caller then counts
296297
20 - 8 = 12 skipped when the next call builds [20-21].
297298
298-
max_gap comes from --page-tracking-merge-gap: by default ("auto") the
299+
merge_gap comes from --page-tracking-merge-gap: by default ("auto") the
299300
storage's measured read request cost converted to pages of this
300301
tablespace's physical page size (see xb_io_probe.h). */
301-
void range_get_next_page(xb_page_set *page_set, ulint max_gap,
302-
ulint *filler_pages, ulint *combined_gaps) {
302+
void range_get_next_page(xb_page_set *page_set, xb_read_filt_ctxt_t *ctxt) {
303303
ut_ad(page_set->current_page_it != page_set->pages.end());
304+
const ulint merge_gap = ctxt->merge_gap;
304305

305306
while (true) {
306307
auto current_page = *page_set->current_page_it;
@@ -311,7 +312,7 @@ void range_get_next_page(xb_page_set *page_set, ulint max_gap,
311312
}
312313
auto next_page = *page_set->current_page_it;
313314
ut_ad(next_page > current_page);
314-
if (next_page > current_page + 1 + max_gap) {
315+
if (next_page > current_page + 1 + merge_gap) {
315316
/* gap too large to combine across: park the iterator on the last
316317
page of the current range so the read batch ends there */
317318
--page_set->current_page_it;
@@ -320,8 +321,8 @@ void range_get_next_page(xb_page_set *page_set, ulint max_gap,
320321
if (next_page > current_page + 1) {
321322
/* past the refusal check, so this gap of >= 1 unchanged pages was
322323
just combined into the range: its pages are filler */
323-
*filler_pages += next_page - current_page - 1;
324-
++*combined_gaps;
324+
ctxt->stat_filler_pages += next_page - current_page - 1;
325+
ctxt->stat_combined_gaps++;
325326
}
326327
}
327328
}

storage/innobase/xtrabackup/src/changed_page_tracking.h

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
2626
#include "common.h"
2727
#include "mysql.h"
2828

29+
/* the read-filter context (read_filt.h includes this header, so only a
30+
declaration is possible here) */
31+
struct xb_read_filt_ctxt_t;
32+
2933
namespace pagetracking {
3034
typedef std::set<page_no_t>::iterator page_iterator;
3135

@@ -64,19 +68,18 @@ return true if installed */
6468
bool is_component_installed(MYSQL *connection);
6569

6670
/** Move the current_page_it iterator to point to the last page id of the
67-
current block. Changed pages separated by gaps of at most max_gap
71+
current block. Changed pages separated by gaps of at most merge_gap
6872
unchanged pages belong to the same block, so that they are read with one
6973
sequential read.
7074
@param[in/out] page_set page_set
71-
@param[in] max_gap largest gap that may be combined across,
72-
in pages of the tablespace's physical
73-
page size
74-
@param[in/out] filler_pages incremented by the unchanged pages inside
75-
every gap combined into this block
76-
@param[in/out] combined_gaps incremented by the number of gaps combined
77-
into this block */
78-
void range_get_next_page(xb_page_set *page_set, ulint max_gap,
79-
ulint *filler_pages, ulint *combined_gaps);
75+
@param[in/out] ctxt read-filter context: ctxt->merge_gap bounds
76+
the gaps merged into this block (in pages
77+
of the tablespace's physical page size);
78+
ctxt->stat_filler_pages and
79+
ctxt->stat_combined_gaps accumulate the
80+
unchanged pages inside merged gaps and the
81+
number of gaps merged */
82+
void range_get_next_page(xb_page_set *page_set, xb_read_filt_ctxt_t *ctxt);
8083

8184
/** Set the backupid
8285
@param[in] connection MySQL connection handler

storage/innobase/xtrabackup/src/read_filt.cc

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
2929
#include "common.h"
3030
#include "dict0dict.h"
3131
#include "fil_cur.h"
32+
#include "utils.h"
3233
#include "xb0xb.h"
3334
#include "xb_io_probe.h"
3435
#include "xtrabackup.h"
@@ -46,7 +47,7 @@ static void common_init(
4647
ctxt->buffer_capacity = cursor->buf_size;
4748
ctxt->page_size = cursor->page_size;
4849
ctxt->space_id = cursor->space_id;
49-
ctxt->max_gap = 0;
50+
ctxt->merge_gap = 0;
5051
ctxt->stat_batches = 0;
5152
ctxt->stat_total_changed_pages = 0;
5253
ctxt->stat_groups = 0;
@@ -125,7 +126,7 @@ static void rf_page_tracking_init(xb_read_filt_ctxt_t *ctxt,
125126
common_init(ctxt, cursor);
126127
ctxt->filter_batch_end = 0;
127128

128-
/* Full-scan spaces never consult max_gap; spaces without changed pages
129+
/* Full-scan spaces never consult merge_gap; spaces without changed pages
129130
are not read at all. */
130131
if (space_id == dict_sys_t::s_dict_space_id ||
131132
full_scan_tables.find(space_id) != full_scan_tables.end() ||
@@ -140,9 +141,19 @@ static void rf_page_tracking_init(xb_read_filt_ctxt_t *ctxt,
140141
proportionally smaller one. In auto mode (the default) the limit is
141142
the read request cost - measured single-threaded at backup start, or
142143
the conservative fallback - converted to pages of this tablespace's
143-
physical page size: combine every gap cheaper than one saved read. */
144+
physical page size: combine every gap cheaper than one saved read.
145+
146+
The divisibility below holds for every InnoDB tablespace this filter
147+
can see: the page-tracking filter is only selected for spaces in the
148+
server's changed-page map (InnoDB by construction), and every valid
149+
physical page size - 1K-16K compressed, up to the 64K server page
150+
size uncompressed - is a power of two not larger than UNIV_PAGE_SIZE.
151+
Were it ever violated in a release build, the integer arithmetic
152+
degrades toward merge_gap = 0, i.e. the old strictly-consecutive reads:
153+
a performance fallback, never a correctness risk (the incremental
154+
write filter still gates every page by its LSN). */
144155
ut_ad(UNIV_PAGE_SIZE % ctxt->page_size == 0);
145-
ctxt->max_gap =
156+
ctxt->merge_gap =
146157
static_cast<ulint>(opt_page_tracking_merge_gap_auto
147158
? xb_read_request_cost / ctxt->page_size
148159
: uint64_t{opt_page_tracking_merge_gap} *
@@ -266,14 +277,9 @@ static void rf_page_tracking_get_next_batch(xb_fil_cur_t *cursor,
266277
}
267278

268279
ctxt->offset = next_page_id * ctxt->page_size;
269-
/* Find the end of the current page tracking block */
270-
{
271-
ulint filler = 0, combined = 0;
272-
pagetracking::range_get_next_page(space, ctxt->max_gap, &filler,
273-
&combined);
274-
ctxt->stat_filler_pages += filler;
275-
ctxt->stat_combined_gaps += combined;
276-
}
280+
/* Find the end of the current page tracking block; the walker
281+
adds the pages it merges across into ctxt's stat counters */
282+
pagetracking::range_get_next_page(space, ctxt);
277283
ut_ad(space->current_page_it != space->pages.end());
278284

279285
ctxt->filter_batch_end = (*space->current_page_it) + 1;
@@ -335,7 +341,7 @@ static void rf_page_tracking_deinit(xb_fil_cur_t *cursor) {
335341
xb::info() << std::fixed << "pagetracking: " << cursor->rel_path << ": "
336342
<< ctxt->stat_total_changed_pages << " changed pages in " << ranges
337343
<< " ranges (avg gap " << std::setprecision(1) << avg_gap
338-
<< " pages); merge-gap=" << ctxt->max_gap
344+
<< " pages); merge-gap=" << ctxt->merge_gap
339345
<< (opt_page_tracking_merge_gap_auto ? " (auto)" : "")
340346
<< " combined them into " << ctxt->stat_groups
341347
<< " reads: request reduction " << reduction
@@ -355,10 +361,11 @@ static void rf_page_tracking_deinit(xb_fil_cur_t *cursor) {
355361
avg_gap_bytes <= pagetracking::READ_REQUEST_COST_MAX_BYTES) {
356362
xb::info() << std::fixed << std::setprecision(1)
357363
<< "pagetracking: " << cursor->rel_path << ": typical gap "
358-
<< avg_gap << " pages (" << avg_gap_bytes / 1024
359-
<< "KB) costs more than one read request ("
360-
<< xb_read_request_cost / 1024
361-
<< "KB); reads stay individual - if sequential read "
364+
<< avg_gap << " pages ("
365+
<< xtrabackup::utils::human_readable(avg_gap_bytes)
366+
<< ") costs more than one read request ("
367+
<< xtrabackup::utils::human_readable(xb_read_request_cost)
368+
<< "); reads stay individual - if sequential read "
362369
"throughput is high, --page-tracking-merge-gap="
363370
<< static_cast<uint64_t>(avg_gap + 1.0) << " may be faster";
364371
}

storage/innobase/xtrabackup/src/read_filt.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,20 @@ struct xb_read_filt_ctxt_t {
3939
ulint filter_batch_end; /*!< the ending page id of the
4040
current changed page block in
4141
the page tracking */
42-
ulint max_gap; /*!< largest changed-page gap to
43-
combine into one read, in pages of
44-
page_size (--page-tracking-merge-gap
45-
scaled for compressed tablespaces,
46-
or the read request cost in
47-
pages) */
42+
ulint merge_gap; /*!< largest changed-page gap to
43+
combine into one read, in pages of
44+
page_size (--page-tracking-merge-gap
45+
scaled for compressed tablespaces,
46+
or the read request cost in
47+
pages) */
4848
/* Statistics accumulated while the file is read, reported by the
4949
filter's deinit in one log line; they never influence any decision.
5050
5151
Shared example used in the field comments below: changed pages
5252
5353
1,2,3,4 7 20,21 (3 runs of consecutive pages)
5454
55-
with max_gap = 4. A gap counts the unchanged pages BETWEEN two
55+
with merge_gap = 4. A gap counts the unchanged pages BETWEEN two
5656
changed pages (between 4 and 7 it is 2: pages 5,6 - not the id
5757
difference 3). The gap of 2 (pages 5,6) is combined across, so
5858
[1-7] becomes one read group; the gap of 12 (pages 8..19) is not,
@@ -79,7 +79,7 @@ struct xb_read_filt_ctxt_t {
7979
would have read: here 3, so combining
8080
saved one request */
8181
ulint stat_combined_gaps; /*!< gaps of unchanged pages combined
82-
across (gap <= max_gap), each joining
82+
across (gap <= merge_gap), each joining
8383
two ranges into one read. Example: 1
8484
(the 2-page gap between 4 and 7) */
8585
ulint stat_filler_pages; /*!< unchanged pages read only as
@@ -91,7 +91,7 @@ struct xb_read_filt_ctxt_t {
9191
amplification = (total_changed +
9292
filler) / total_changed */
9393
ulint stat_skipped_pages; /*!< unchanged pages in refused gaps
94-
(gap > max_gap): never read, seeked
94+
(gap > merge_gap): never read, seeked
9595
past. Example: 12 (pages 8..19).
9696
avg gap = (filler + skipped) /
9797
(ranges - 1) describes how scattered

0 commit comments

Comments
 (0)