Skip to content

Commit 55f48cf

Browse files
PXB-3862 : Handle OS-cached pages in the storage probe
On a buffered server the probe's sample regions may already sit in the OS page cache (warmed by the server or a previous scan), and a cached read times RAM, not the device: on a fully cached tablespace the round trip measured 1us and the read request cost collapsed to the 64KiB floor, understating how much merging the storage deserves. The probe already dropped its samples with posix_fadvise(DONTNEED), but newer kernels cache a sequentially read file in blocks of up to 2MB (XFS since kernel 5.17, ext4 since 6.13) and ignore the advice unless it covers a whole block, so the page-sized evictions did nothing there. Evict a whole 2MB-aligned window around every sample instead, right before reading it. The probe gives up ~40MB of cache on a multi-GB file; the copy itself still reads a warm file from RAM. Verified against a fully cached 21GB tablespace: calibration now reports device numbers (178us round trip) while the backup reads only 79MB from the device.
1 parent cbefa1b commit 55f48cf

1 file changed

Lines changed: 32 additions & 17 deletions

File tree

storage/innobase/xtrabackup/src/xb_io_probe.h

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,35 @@ struct Probe_result {
5757
uint64_t bw_bytes_per_sec{0}; /*!< sequential read bandwidth */
5858
};
5959

60+
/** Drop a byte range of the file from the OS page cache, so the probe
61+
times the device rather than RAM (the range may be warm from the server
62+
or a previous scan). Newer kernels cache a sequentially read file in
63+
blocks of up to 2MB and ignore the advice unless it covers a whole
64+
block, so the range is widened to full 2MB multiples. The advice skips
65+
dirty pages; under O_DIRECT it is a no-op.
66+
67+
@param[in] fd file the probe reads from
68+
@param[in] offset start of the byte range about to be read
69+
@param[in] length length of that range */
70+
inline void evict_from_page_cache(int fd, uint64_t offset, uint64_t length) {
71+
#ifdef POSIX_FADV_DONTNEED
72+
constexpr uint64_t EVICT_SIZE_BYTES = 2 * 1024 * 1024;
73+
/* the file is a row of 2MB units; dividing a byte position by the
74+
unit size gives the number of the unit it falls in. Evict every unit
75+
the range touches, from the one holding its first byte to the one
76+
holding its last byte, in full. */
77+
const uint64_t first_unit = offset / EVICT_SIZE_BYTES;
78+
const uint64_t last_unit = (offset + length - 1) / EVICT_SIZE_BYTES;
79+
::posix_fadvise(fd, first_unit * EVICT_SIZE_BYTES,
80+
(last_unit - first_unit + 1) * EVICT_SIZE_BYTES,
81+
POSIX_FADV_DONTNEED);
82+
#else
83+
(void)fd;
84+
(void)offset;
85+
(void)length;
86+
#endif
87+
}
88+
6089
/** Measure the storage behind a file.
6190
6291
Every read below fetches whole 16KB pages at page-aligned offsets - the
@@ -122,26 +151,11 @@ inline std::optional<Probe_result> probe_storage(const char *path,
122151
return (std::nullopt);
123152
}
124153

125-
#ifdef POSIX_FADV_DONTNEED
126-
/* On a buffered server the sample regions may already sit in the OS
127-
page cache (warmed by the server or a previous backup); measuring
128-
them would time RAM, not the device. Drop the cached pages of just
129-
the ~16MB this probe is about to read - surgical, so a live server
130-
loses next to no cache warmth. The advice skips dirty pages, and the
131-
RTT median tolerates a few residual hits; under O_DIRECT it is a
132-
no-op. */
133-
::posix_fadvise(fd, (file_pages / 2) * PROBE_PAGE_BYTES,
134-
SEQ_SAMPLES * SEQ_CHUNK_BYTES, POSIX_FADV_DONTNEED);
135-
for (int i = 1; i <= RTT_SAMPLES; i++) {
136-
::posix_fadvise(fd,
137-
((file_pages / (RTT_SAMPLES + 1)) * i) * PROBE_PAGE_BYTES,
138-
PROBE_PAGE_BYTES, POSIX_FADV_DONTNEED);
139-
}
140-
#endif
141-
142154
/* contiguous pages from the middle of the file onward -> bandwidth
143155
(PROBE_MIN_FILE_BYTES guarantees the chunks fit after the midpoint) */
144156
Probe_result result;
157+
evict_from_page_cache(fd, (file_pages / 2) * PROBE_PAGE_BYTES,
158+
SEQ_SAMPLES * SEQ_CHUNK_BYTES);
145159
uint64_t seq_page_no = file_pages / 2;
146160
uint64_t seq_bytes = 0;
147161
const auto seq_start = clock::now();
@@ -172,6 +186,7 @@ inline std::optional<Probe_result> probe_storage(const char *path,
172186
std::vector<uint64_t> rtt_samples;
173187
for (int i = 1; i <= RTT_SAMPLES; i++) {
174188
const uint64_t page_no = (file_pages / (RTT_SAMPLES + 1)) * i;
189+
evict_from_page_cache(fd, page_no * PROBE_PAGE_BYTES, PROBE_PAGE_BYTES);
175190
const auto start = clock::now();
176191
if (::pread(fd, buf, PROBE_PAGE_BYTES, page_no * PROBE_PAGE_BYTES) !=
177192
static_cast<ssize_t>(PROBE_PAGE_BYTES)) {

0 commit comments

Comments
 (0)