MDEV-32067 InnoDB linear read ahead had better be logical#4600
MDEV-32067 InnoDB linear read ahead had better be logical#4600Thirunarayanan wants to merge 1 commit into
Conversation
|
|
1fc14cb to
7fa9c70
Compare
7fa9c70 to
93d5f7d
Compare
dr-m
left a comment
There was a problem hiding this comment.
I only reviewed a small part of this so far. Please debug this with undo tablespace truncation enabled.
93d5f7d to
6aea84a
Compare
iMineLink
left a comment
There was a problem hiding this comment.
The new logic is a step forward towards more precise and efficient read-ahead logic.
I think that it can be extended easily to non MRR queries, by always passing a context to row_search_mvcc() initialized with the default 64 pages when no explicit limit is present.
| { | ||
| /* Calculate pages needed, with some buffer for safety */ | ||
| uint pages_needed= | ||
| (uint)((limit + records_per_page - 1) / records_per_page); |
There was a problem hiding this comment.
Maybe return 0 if records_per_page is 0 ?
| /* Use table statistics to estimate records per page */ | ||
| ha_rows total_rows= table->file->stats.records; | ||
| ha_rows index_pages= table->file->stats.data_file_length / | ||
| table->file->stats.block_size; |
There was a problem hiding this comment.
maybe return 0 if table->file->stats.block_size is 0?
| /* Apply bounds based on key size */ | ||
| uint key_length= key_info->key_length; | ||
| uint page_size= table->file->stats.block_size; | ||
| uint max_records= page_size / key_length; |
There was a problem hiding this comment.
maybe return 0 if key_length is 0 ?
|
|
||
| uint max_pages_for_limit= 0; | ||
| if (limit_hint == HA_POS_ERROR); | ||
| else if (limit_hint > 2) |
There was a problem hiding this comment.
Maybe rewrite condition as limit_hint != HA_POS_ERROR && limit_hint > 2 ? Can you please comment on the behavior when limit_hint==1 ?
| if (!height && first && first_access) | ||
| buf_read_ahead_linear(page_id_t(block->page.id().space(), page)); | ||
| } | ||
| if (latch_mode != BTR_MODIFY_TREE); |
There was a problem hiding this comment.
It seems this is idiomatic but maybe:
if (latch_mode != BTR_MODIFY_TREE) { /* Do nothing */ }
else ...
is more expressive?
| /* On other BLOB pages except the first the BLOB header | ||
| always is at the page data start: */ | ||
|
|
||
| offset = FIL_PAGE_DATA; |
There was a problem hiding this comment.
Maybe this is not useful now that offset = FIL_PAGE_DATA is in the for loop?
| { | ||
| skip: | ||
| space->release(); | ||
| return; |
There was a problem hiding this comment.
Maybe do not stop at first cached page?
| if (space->is_stopping()) | ||
| break; | ||
| buf_pool_t::hash_chain &chain= buf_pool.page_hash.cell_get(new_low.fold()); | ||
| space->reacquire(); |
There was a problem hiding this comment.
Since space->acquire() as added earlier, I suppose extra space->release() calls are needed to keep old behavior.
I'm looking at the early exit paths goto func_exit;, I suppose space->release() should be called before jumping.
6aea84a to
249de0f
Compare
The traditional linear read-ahead, enabled by innodb_read_ahead_threshold, only helps when consecutively accessed pages have adjacent page numbers. That is rarely true for B-tree leaf pages: after splits, merges and page reuse, the logical order of leaves has nothing to do with their physical page numbers. So for a scan of scattered leaves the old read-ahead either did nothing or read the wrong pages. This replaces linear read-ahead of index pages with a logical read-ahead that prefetches the actual leaf pages a scan is going to visit, discovered from the B-tree during traversal. After this change, the exact nonzero value of innodb_read_ahead_threshold matters only for the read-ahead of undo log pages. records_in_range() reports the first and last leaf page of a range, and the optimizer forwards that extent to the engine through handler::advise_page_range() so read-ahead can be sized and bounded to the scan. buf_read_ahead_undo(): Renamed from buf_read_ahead_linear(). Now invoked only for undo log pages, whose page numbers are sequential. No longer called on BLOB pages or index pages. buf_read_ahead_one(): Read ahead a single leaf page. Cannot be disabled. buf_read_ahead_pages(): Read ahead a set of known B-tree leaf pages. buf_read_ahead_random(): Split into a decision part (which extent to read, in buf0buf.cc) and an action part (issue the reads). The pending-read throttle now compares os_aio_pending_reads_approx() with buf_pool.curr_size() (a page count) instead of curr_pool_size() (bytes), which had made the throttle a no-op. buf_read_ahead_pages() releases the tablespace reference on a failed/corrupted page. btr_read_ahead_t: A context which records the level-1 page and the child page number of the last node pointer collected, so read-ahead can be resumed later. btr_read_ahead_collect(): scan the node pointers of a PAGE_LEVEL=1 page from a given record in scan order, appending child page numbers. btr_cur_t::search_leaf(), btr_cur_t::open_leaf(): At PAGE_LEVEL=1, after the descent has located the child to follow, harvest leaf page numbers starting at that child in the scan direction. These are exactly the leaves the cursor will visit, so the prefetch is precise regardless of physical page numbers. btr_read_ahead_resume_rec(): Locate the record from which read-ahead should resume, by re-finding the last harvested child by value. It walks only genuine records, so a concurrent page reorganization cannot cause an invalid read (a stored byte offset could). btr_pcur_move_to_next_page(), btr_pcur_move_backward_from_page(): Invoke buf_read_ahead_one() on the following/preceding sibling. btr_copy_blob_prefix(): Simplified; no longer relies on FIL_PAGE_PREV/NEXT. advise_page_range(): Records the scan's estimated leaf extent. Used only to size and bound read-ahead; it never gates the pages the scan actually reads. mrr_readahead_from_scan_range() derives the read-ahead ceiling from the advised extent, falling back to a LIMIT-based estimate. The window ramps up from small number of pages, doubling on each refill toward the ceiling, so single-row probes such as index_first() for MIN()/MAX() prefetch only a few leaves while long scans reach full depth. start_readahead(): For a just-positioned scan, prefetch the collected batch and set up the rolling cursor. readahead_refill(): As the scan advances (general_fetch()), resume the level-1 harvest under an index S-latch, chain across level-1 siblings, and prefetch the next batch bounded by the advised last leaf, or to the end of the index when the extent is unknown. Read-ahead is started from index_read() (range and full index scans), rnd_init()/general_fetch() (full table scans), and, at one-page depth, from row_merge_read_clustered_index() (OPTIMIZE/ALTER rebuild). general_fetch() sets active_handler_stats before issuing read-ahead so prefetched pages are attributed to the query. A truncation race is closed with a new STOPPING_READS tablespace flag: mtr_t::commit_shrink() sets it before shrinking and clears it after, and buf_read_ahead_undo() re-checks space->is_stopping(). trx_undo_get_prev_rec(), trx_undo_get_prev_rec_from_prev_page(): take the trx_undo_t object instead of a long parameter list and always latch shared. handler::multi_range_read_info_const() and DsMrr_impl::dsmrr_info_const() Passed a page_range parameter; the default MRR implementation aggregates the per-range leaf extents. opt_range.cc carries it through the chosen QUICK_RANGE_SELECT, and QUICK_RANGE_SELECT::reset() calls advise_page_range() before multi_range_read_init(). main.analyze_stmt_prefetch_count: corrected for double counting now that pages_read_count includes waits on prefetched pages.
249de0f to
72e3242
Compare
MDEV-32067 InnoDB linear read ahead had better be logical