Skip to content
/ server Public

MDEV-38947 Reimplement SET GLOBAL innodb_buffer_pool_size#4729

Draft
dr-m wants to merge 1 commit into10.6from
MDEV-38947
Draft

MDEV-38947 Reimplement SET GLOBAL innodb_buffer_pool_size#4729
dr-m wants to merge 1 commit into10.6from
MDEV-38947

Conversation

@dr-m
Copy link
Contributor

@dr-m dr-m commented Mar 3, 2026

We deprecate and ignore the parameter innodb_buffer_pool_chunk_size and let the innodb_buffer_pool_size to be changed in arbitrary 1-megabyte increments.

innodb_buffer_pool_size_max: A new read-only startup parameter that specifies the maximum innodb_buffer_pool_size. On 64-bit systems other than IBM AIX the default is 8 TiB and the minimum 8 MiB. On other systems, the default and minimum are 0, and the value 0 will be replaced with the initial innodb_buffer_pool_size rounded up to the allocation unit (2 MiB or 8 MiB). The maximum value is 4GiB-2MiB on 32-bit systems and 16EiB-8MiB on 64-bit systems. This maximum is very likely to be limited further by the operating system.

The status variable Innodb_buffer_pool_resize_status will reflect the status of shrinking the buffer pool. When no shrinking is in progress, the string will be empty.

Unlike before, the execution of SET GLOBAL innodb_buffer_pool_size will block until the requested buffer pool size change has been implemented, or the execution is interrupted by a KILL statement a client disconnect, or server shutdown. If the
buf_flush_page_cleaner() thread notices that we are running out of memory, the operation may fail with ER_WRONG_USAGE.

SET GLOBAL innodb_buffer_pool_size will be refused if the server was started with --large-pages (even if no HugeTLB pages were successfully allocated). This functionality is somewhat exercised by the test main.large_pages, which now runs also on Microsoft Windows. On Linux, explicit HugeTLB mappings are apparently excluded from the reported Resident Set Size (RSS), and apparently unshrinkable between mmap(2) and munmap(2).

The buffer pool will be mapped to a contiguous virtual memory area that will be aligned and partitioned into extents of 8 MiB on 64-bit systems and 2 MiB on 32-bit systems.

Within an extent, the first few innodb_page_size blocks contain buf_block_t objects that will cover the page frames in the rest of the extent. The number of such frames is precomputed in the array first_page_in_extent[] for each innodb_page_size. In this way, there is a trivial mapping between page frames and block descriptors and we do not need any lookup tables like buf_pool.zip_hash or buf_pool_t::chunk_t::map.

We will always allocate the same number of block descriptors for an extent, even if we do not need all the buf_block_t in the last extent in case the innodb_buffer_pool_size is not an integer multiple of the of extents size.

The minimum innodb_buffer_pool_size is 256*5/4 pages. At the default innodb_page_size=16k this corresponds to 5 MiB. However, now that the innodb_buffer_pool_size includes the memory allocated for the block descriptors, the minimum would be innodb_buffer_pool_size=6m.

my_virtual_mem_reserve(), my_virtual_mem_commit(), my_virtual_mem_decommit(), my_virtual_mem_release(): New interface mostly by @vaintroub, to separately reserve and release virtual address space, as well as to commit and decommit memory within it.

The function my_virtual_mem_reserve() is only defined for Microsoft Windows. Other platforms should invoke my_large_virtual_alloc() instead.

my_large_virtual_alloc(): A new function, similar to my_large_malloc(), for other platforms than Microsoft Windows. For regular page size allocations, do not specify MAP_NORESERVE nor MAP_POPULATE, to preserve compatibility with my_large_malloc().

After my_virtual_mem_decommit(), the virtual memory range will be inaccessible.

opt_super_large_pages: Declare only on Solaris. Actually, this is specific to the SPARC implementation of Solaris, but because we lack access to a Solaris development environment, we will not revise this for other MMU and ISA.

buf_pool_t::chunk_t::create(): Remove.

buf_pool_t::create(): Initialize all n_blocks of the buf_pool.free list.

buf_pool_t::allocate(): Renamed from buf_LRU_get_free_only().

buf_pool_t::LRU_warned: Changed to Atomic_relaxed<bool>, only to be modified by the buf_flush_page_cleaner() thread.

buf_pool_t::shrink(): Attempt to shrink the buffer pool. There are 3 possible outcomes: SHRINK_DONE (success), SHRINK_IN_PROGRESS (the caller may keep trying), and SHRINK_ABORT (we seem to be running out of buffer pool). While traversing buf_pool.LRU, release the contended buf_pool.mutex once in every 32 iterations in order to reduce starvation. Use lru_scan_itr for efficient traversal, similar to buf_LRU_free_from_common_LRU_list(). When relocating a buffer page, invalidate the page identifier of the original page so that buf_pool_t::page_guess() will not accidentally match it.

buf_pool_t::shrunk(): Update the reduced size of the buffer pool in a way that is compatible with buf_pool_t::page_guess(), and invoke my_virtual_mem_decommit().

buf_pool_t::resize(): Before invoking shrink(), run one batch of buf_flush_page_cleaner() in order to prevent LRU_warn(). Abort if shrink() recommends it, or no blocks were withdrawn in the past 15 seconds, or the execution of the statement SET GLOBAL innodb_buffer_pool_size was interrupted. After successfully shrinking the buffer pool, announce the success. The size had already been updated in shrunk(). After failing to shrink the buffer pool, re-enable the adaptive hash index if it had been enabled before the resizing.

buf_pool_t::first_to_withdraw: The first block descriptor that is out of the bounds of the shrunk buffer pool.

buf_pool_t::withdrawn: The list of withdrawn blocks. If buf_pool_t::resize() is aborted before shrink() completes, we must be able to resurrect the withdrawn blocks in the free list.

buf_pool_t::contains_zip(): Added a parameter for the number of least significant pointer bits to disregard, so that we can find any pointers to within a block that is supposed to be free.

buf_pool_t::is_shrinking(): Return the total number or blocks that were withdrawn or are to be withdrawn.

buf_pool_t::to_withdraw(): Return the number of blocks that will need to be withdrawn.

buf_pool_t::usable_size(): Number of usable pages, considering possible in-progress attempt at shrinking the buffer pool.

buf_pool_t::page_guess(): Try to buffer-fix a guessed block pointer. Always check that the pointer is within the current buffer pool size before dereferencing it.

buf_pool_t::get_info(): Replaces buf_stats_get_pool_info().

innodb_init_param(): Refactored. We must first compute srv_page_size_shift and then determine the valid bounds of innodb_buffer_pool_size.

buf_buddy_shrink(): Replaces buf_buddy_realloc(). Part of the work is deferred to buf_buddy_condense_free(), which is being executed when we are not holding any buf_pool.page_hash latch.

buf_buddy_condense_free(): Do not relocate blocks.

buf_buddy_free_low(): Do not care about buffer pool shrinking. This will be handled by buf_buddy_shrink() and buf_buddy_condense_free().

buf_buddy_alloc_zip(): Assert !buf_pool.contains_zip() when we are allocating from the binary buddy system. Previously we were asserting this on multiple recursion levels.

buf_buddy_block_free(), buf_buddy_free_low(): Assert !buf_pool.contains_zip().

buf_buddy_alloc_from(): Remove the redundant parameter j.

buf_flush_LRU_list_batch(): Add the parameter to_withdraw to keep track of buf_pool.n_blocks_to_withdraw. Keep evicting as long as the buffer pool is being shrunk, for at most innodb_lru_scan_depth extra blocks. Disregard the flush limit for pages that are marked as freed in files.

buf_flush_LRU_to_withdraw(): Update the to_withdraw target during buf_flush_LRU_list_batch().

buf_pool_t::will_be_withdrawn(): Allow also ptr=nullptr (the condition will not hold for it).

buf_flush_sync_for_checkpoint(): Wait for pending writes, in order to guarantee progress even if the scheduler is unfair.

buf_do_LRU_batch(): Skip buf_free_from_unzip_LRU_list_batch() if we are shrinking the buffer pool. In that case, we want to minimize the page relocations and just finish as quickly as possible.

buf_LRU_check_size_of_non_data_objects(): Avoid a crash when the buffer pool is being shrunk.

trx_purge_attach_undo_recs(): Limit purge_sys.n_pages_handled() in every iteration, in case the buffer pool is being shrunk in the middle of a purge batch.

recv_sys_t::wait_for_pool(): Also wait for pending writes, so that previously written blocks can be evicted and reused.

This ports the following changes from the 10.11 branch, superceding #3107:

b692342 (MDEV-29445) #3826, 027d815, 58a3677, a096f12, df83d3d
669f719 (MDEV-36489) #3954
f1a8b7f (MDEV-36646) #4000
8fb0942 (MDEV-36759), 56e0be3 (MDEV-36780), bb48d7b (MDEV-36781) #4042
7b4b759 (MDEV-36868) #4062
cedfe8e (MDEV-37250) #4208
55e0c34 (MDEV-37263) #4212
21bb6a3 (MDEV-37447)
072c7dc (MDEV-38671) #4674
d4c0918 (MDEV-38958) #4740 4b8ba56 #4745

@dr-m dr-m self-assigned this Mar 3, 2026
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@dr-m dr-m requested a review from iMineLink March 3, 2026 12:08
@vaintroub
Copy link
Member

my name is mentioned. I would not design an interface that changes behavior dependent on global variable (my_use_large_pages or something). I do not think this is how I wrote it :)

Copy link
Contributor

@iMineLink iMineLink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I focused on analyzing the difference between this branch and a branch which would be same base 10.6 plus the cherry-pick of the backported commits as suggested by @dr-m :

git cherry-pick -X theirs b6923420f326ac030e4f3ef89a2acddb45eccb30 027d815546d45513ec597b490f2fa45b567802ba 58a36773090223c97d814a07d57ab35ebf803cc5 a096f12ff75595ce51fedf879b71640576f70e52 669f719cc21286020c95eec11f0d09b74f96639e f1a8b7fe95399ebe2a1c4a370e332d61dbf6891a 8fb09426b98583916ccfd4f8c49741adc115bac3 56e0be34bc5d1e967ad610a9b8e24c3f5553bdd8 bb48d7bc812baf7cbd71c9e41b29fac6288cec97 7b4b759f136f25336fdc12a5a705258a5846d224 cedfe8eca49506c6b4d2d6868f1014c72caaab36 55e0c34f4f00ca70ad8d6f0522efa94bb81f74fb 21bb6a3e348f89c5cf23d4ee688c57f6078c7b02 072c7dc774e7f31974eaa43ec1cbb3b742a1582e

Notable backport differences are in:

  • buf0buf.cc: the memory pressure system is not backported
  • ha_innodb.cc: dynamic shrinking under memory pressure is not backported

My experience with the 10.6 branch is otherwise limited, I noticed no suspiciously related failures in bb cr and found no issues in building and testing InnoDB suite locally.

@iMineLink
Copy link
Contributor

I reviewed the changes in 35d0250 and they look good to me.
Built and tested the InnoDB mtr suite locally without failures.
bb cr reports only failures that are already in 10.6.

Comment on lines +1089 to 1095
if (!my_virtual_mem_commit(memory, actual_size))
{
my_virtual_mem_release(memory_unaligned, size_unaligned);
memory= nullptr;
memory_unaligned= nullptr;
goto oom;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in #4740, we must invoke ut_dontdump() after each successful call to my_virtual_mem_commit(), in both buf_pool_t::create() and buf_pool_t::resize(), or otherwise the buffer pool will be included in the core dump. I tested the impact on Linux. It needs to be tested on FreeBSD as well.

Copy link
Contributor

@iMineLink iMineLink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dr-m dr-m requested a review from iMineLink March 6, 2026 09:57
Copy link
Contributor

@iMineLink iMineLink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional changes are only related to ut_dontdump() usage, backported from #4740.
Leaved few comments about that.

We deprecate and ignore the parameter innodb_buffer_pool_chunk_size
and let the buffer pool size to be changed in arbitrary 1-megabyte
increments.

innodb_buffer_pool_size_max: A new read-only startup parameter
that specifies the maximum innodb_buffer_pool_size. On 64-bit
systems other than IBM AIX the default is 8 TiB and the minimum
8 MiB. On other systems, the default and minimum are 0, and
the value 0 will be replaced with the initial innodb_buffer_pool_size
rounded up to the allocation unit (2 MiB or 8 MiB).  The maximum value
is 4GiB-2MiB on 32-bit systems and 16EiB-8MiB on 64-bit systems.
This maximum is very likely to be limited further by the operating system.

The status variable Innodb_buffer_pool_resize_status will reflect
the status of shrinking the buffer pool. When no shrinking is in
progress, the string will be empty.

Unlike before, the execution of SET GLOBAL innodb_buffer_pool_size
will block until the requested buffer pool size change has been
implemented, or the execution is interrupted by a KILL statement
a client disconnect, or server shutdown.  If the
buf_flush_page_cleaner() thread notices that we are running out of
memory, the operation may fail with ER_WRONG_USAGE.

SET GLOBAL innodb_buffer_pool_size will be refused
if the server was started with --large-pages (even if
no HugeTLB pages were successfully allocated). This functionality
is somewhat exercised by the test main.large_pages, which now runs
also on Microsoft Windows.  On Linux, explicit HugeTLB mappings are
apparently excluded from the reported Resident Set Size (RSS), and
apparently unshrinkable between mmap(2) and munmap(2).

The buffer pool will be mapped to a contiguous virtual memory area
that will be aligned and partitioned into extents of 8 MiB on
64-bit systems and 2 MiB on 32-bit systems.

Within an extent, the first few innodb_page_size blocks contain
buf_block_t objects that will cover the page frames in the rest
of the extent.  The number of such frames is precomputed in the
array first_page_in_extent[] for each innodb_page_size.
In this way, there is a trivial mapping between
page frames and block descriptors and we do not need any
lookup tables like buf_pool.zip_hash or buf_pool_t::chunk_t::map.

We will always allocate the same number of block descriptors for
an extent, even if we do not need all the buf_block_t in the last
extent in case the innodb_buffer_pool_size is not an integer multiple
of the of extents size.

The minimum innodb_buffer_pool_size is 256*5/4 pages.  At the default
innodb_page_size=16k this corresponds to 5 MiB.  However, now that the
innodb_buffer_pool_size includes the memory allocated for the block
descriptors, the minimum would be innodb_buffer_pool_size=6m.

my_virtual_mem_reserve(), my_virtual_mem_commit(),
my_virtual_mem_decommit(), my_virtual_mem_release():
New interface mostly by Vladislav Vaintroub, to separately
reserve and release virtual address space, as well as to
commit and decommit memory within it.

The function my_virtual_mem_reserve() is only defined for Microsoft Windows.
Other platforms should invoke my_large_virtual_alloc() instead.

my_large_virtual_alloc(): A new function, similar to my_large_malloc(),
for other platforms than Microsoft Windows.
For regular page size allocations, do not specify MAP_NORESERVE nor
MAP_POPULATE, to preserve compatibility with my_large_malloc().

After my_virtual_mem_decommit(), the virtual memory range will be
inaccessible.

opt_super_large_pages: Declare only on Solaris. Actually, this is
specific to the SPARC implementation of Solaris, but because we
lack access to a Solaris development environment, we will not revise
this for other MMU and ISA.

buf_pool_t::chunk_t::create(): Remove.

buf_pool_t::create(): Initialize all n_blocks of the buf_pool.free list.

buf_pool_t::allocate(): Renamed from buf_LRU_get_free_only().

buf_pool_t::LRU_warned: Changed to Atomic_relaxed<bool>,
only to be modified by the buf_flush_page_cleaner() thread.

buf_pool_t::shrink(): Attempt to shrink the buffer pool.
There are 3 possible outcomes: SHRINK_DONE (success),
SHRINK_IN_PROGRESS (the caller may keep trying),
and SHRINK_ABORT (we seem to be running out of buffer pool).
While traversing buf_pool.LRU, release the contended
buf_pool.mutex once in every 32 iterations in order to
reduce starvation. Use lru_scan_itr for efficient traversal,
similar to buf_LRU_free_from_common_LRU_list().
When relocating a buffer page, invalidate the page identifier
of the original page so that buf_pool_t::page_guess()
will not accidentally match it.

buf_pool_t::shrunk(): Update the reduced size of the buffer pool
in a way that is compatible with buf_pool_t::page_guess(),
and invoke my_virtual_mem_decommit().

buf_pool_t::resize(): Before invoking shrink(), run one batch of
buf_flush_page_cleaner() in order to prevent LRU_warn().
Abort if shrink() recommends it, or no blocks were withdrawn in
the past 15 seconds, or the execution of the statement
SET GLOBAL innodb_buffer_pool_size was interrupted.
After successfully shrinking the buffer pool, announce the success.
The size had already been updated in shrunk().  After failing to
shrink the buffer pool, re-enable the adaptive hash index
if it had been enabled before the resizing.

buf_pool_t::first_to_withdraw: The first block descriptor that is
out of the bounds of the shrunk buffer pool.

buf_pool_t::withdrawn: The list of withdrawn blocks.
If buf_pool_t::resize() is aborted before shrink() completes,
we must be able to resurrect the withdrawn blocks in the free list.

buf_pool_t::contains_zip(): Added a parameter for the
number of least significant pointer bits to disregard,
so that we can find any pointers to within a block
that is supposed to be free.

buf_pool_t::is_shrinking(): Return the total number or blocks that
were withdrawn or are to be withdrawn.

buf_pool_t::to_withdraw(): Return the number of blocks that will need to
be withdrawn.

buf_pool_t::usable_size(): Number of usable pages, considering possible
in-progress attempt at shrinking the buffer pool.

buf_pool_t::page_guess(): Try to buffer-fix a guessed block pointer.
Always check that the pointer is within the current buffer pool size
before dereferencing it.

buf_pool_t::get_info(): Replaces buf_stats_get_pool_info().

innodb_init_param(): Refactored. We must first compute
srv_page_size_shift and then determine the valid bounds of
innodb_buffer_pool_size.

buf_buddy_shrink(): Replaces buf_buddy_realloc().
Part of the work is deferred to buf_buddy_condense_free(),
which is being executed when we are not holding any
buf_pool.page_hash latch.

buf_buddy_condense_free(): Do not relocate blocks.

buf_buddy_free_low(): Do not care about buffer pool shrinking.
This will be handled by buf_buddy_shrink() and
buf_buddy_condense_free().

buf_buddy_alloc_zip(): Assert !buf_pool.contains_zip()
when we are allocating from the binary buddy system.
Previously we were asserting this on multiple recursion levels.

buf_buddy_block_free(), buf_buddy_free_low():
Assert !buf_pool.contains_zip().

buf_buddy_alloc_from(): Remove the redundant parameter j.

buf_flush_LRU_list_batch(): Add the parameter to_withdraw
to keep track of buf_pool.n_blocks_to_withdraw.
Keep evicting as long as the buffer pool is being shrunk,
for at most innodb_lru_scan_depth extra blocks.
Disregard the flush limit for pages that are marked as freed in files.

buf_flush_LRU_to_withdraw(): Update the to_withdraw target during
buf_flush_LRU_list_batch().

buf_pool_t::will_be_withdrawn(): Allow also ptr=nullptr (the condition
will not hold for it).

buf_flush_sync_for_checkpoint(): Wait for pending writes, in order
to guarantee progress even if the scheduler is unfair.

buf_do_LRU_batch(): Skip buf_free_from_unzip_LRU_list_batch()
if we are shrinking the buffer pool. In that case, we want
to minimize the page relocations and just finish as quickly
as possible.

buf_LRU_check_size_of_non_data_objects(): Avoid a crash when the
buffer pool is being shrunk.

trx_purge_attach_undo_recs(): Limit purge_sys.n_pages_handled()
in every iteration, in case the buffer pool is being shrunk
in the middle of a purge batch.

recv_sys_t::wait_for_pool(): Also wait for pending writes, so that
previously written blocks can be evicted and reused.

This ports the following changes from the 10.11 branch:
commit b692342 (MDEV-29445)
commit 027d815
commit 58a3677
commit a096f12
commit df83d3d
commit 669f719 (MDEV-36489)
commit f1a8b7f (MDEV-36646)
commit 8fb0942 (MDEV-36759)
commit 56e0be3 (MDEV-36780)
commit bb48d7b (MDEV-36781)
commit 7b4b759 (MDEV-36868)
commit cedfe8e (MDEV-37250)
commit 55e0c34 (MDEV-37263)
commit 21bb6a3 (MDEV-37447)
commit 072c7dc (MDEV-38671)
commit d4c0918 (MDEV-38958)
commit 4b8ba56

Reviewed by: Alessandro Vetere
@dr-m dr-m requested a review from iMineLink March 6, 2026 11:57
Copy link
Contributor

@iMineLink iMineLink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the changes to the ut_dontdump() calls, LGTM.

@dr-m dr-m marked this pull request as draft March 6, 2026 15:04
@dr-m
Copy link
Contributor Author

dr-m commented Mar 6, 2026

I moved this to draft state to indicate that we do not intend to push this to the 10.6 Community Server so close to the final end-of-life release. Our support customer is using MariaDB Enterprise Server, which will be maintained longer.

Furthermore, I now see that the test galera.galera_sst_mariabackup_use_memory is failing most likely due to these changes. I will check it and revise this next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants