From ba81009f635ccf0760476315ed3a934b60fee4a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= <marko.makela@mariadb.com>
Date: Wed, 26 Mar 2025 17:05:48 +0200
Subject: [PATCH] MDEV-34863 RAM Usage Changed Significantly Between 10.11
 Releases

innodb_buffer_pool_size_auto_min: A minimum innodb_buffer_pool_size that
a Linux memory pressure event can lead to shrinking the buffer pool to.
On a memory pressure event, we will attempt to shrink
innodb_buffer_pool_size halfway between its current value and
innodb_buffer_pool_size_auto_min. If innodb_buffer_pool_size_auto_min is
specified as 0 or not specified on startup, its default value will be
adjusted to innodb_buffer_pool_size_max, that is, memory pressure events
will be disregarded by default.

buf_pool_t::garbage_collect(): For up to 15 seconds, attempt to shrink
the buffer pool in response to a memory pressure event.

Reviewed by: Debarun Banerjee
---
 .../suite/innodb/r/mem_pressure,32bit.rdiff   |  11 ++
 mysql-test/suite/innodb/r/mem_pressure.result |  33 ++--
 .../innodb/t/innodb_buffer_pool_resize.opt    |   2 +-
 mysql-test/suite/innodb/t/mem_pressure.opt    |   1 +
 mysql-test/suite/innodb/t/mem_pressure.test   |  35 ++--
 .../sys_vars/r/sysvars_innodb,32bit.rdiff     |  71 +++++---
 .../suite/sys_vars/r/sysvars_innodb.result    |  13 +-
 .../suite/sys_vars/t/sysvars_innodb.test      |   1 -
 storage/innobase/buf/buf0buf.cc               | 159 +++++++++---------
 storage/innobase/handler/ha_innodb.cc         |  29 ++++
 storage/innobase/include/buf0buf.h            |   4 +
 11 files changed, 224 insertions(+), 135 deletions(-)
 create mode 100644 mysql-test/suite/innodb/r/mem_pressure,32bit.rdiff

Index: mariadb-10.11.11/mysql-test/suite/innodb/r/mem_pressure,32bit.rdiff
===================================================================
--- /dev/null
+++ mariadb-10.11.11/mysql-test/suite/innodb/r/mem_pressure,32bit.rdiff
@@ -0,0 +1,11 @@
+--- mem_pressure.result
++++ mem_pressure,32bit.result
+@@ -11,7 +11,7 @@
+ @@GLOBAL.innodb_buffer_pool_size_auto_min,
+ @@GLOBAL.innodb_buffer_pool_size_max;
+ @@GLOBAL.innodb_buffer_pool_size	@@GLOBAL.innodb_buffer_pool_size_auto_min	@@GLOBAL.innodb_buffer_pool_size_max
+-17825792	16777216	25165824
++17825792	16777216	18874368
+ CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
+ SET GLOBAL innodb_limit_optimistic_insert_debug=2;
+ SET STATEMENT unique_checks=0, foreign_key_checks=0 FOR
Index: mariadb-10.11.11/mysql-test/suite/innodb/r/mem_pressure.result
===================================================================
--- mariadb-10.11.11.orig/mysql-test/suite/innodb/r/mem_pressure.result
+++ mariadb-10.11.11/mysql-test/suite/innodb/r/mem_pressure.result
@@ -4,23 +4,34 @@
 set @save_dbug=@@debug_dbug;
 set @save_limit=@@GLOBAL.innodb_limit_optimistic_insert_debug;
 set GLOBAL innodb_max_purge_lag_wait=0;
+SET @innodb_buffer_pool_size= @@GLOBAL.innodb_buffer_pool_size;
+SET @innodb_buffer_pool_size_min= @@GLOBAL.innodb_buffer_pool_size_auto_min;
+SELECT
+@@GLOBAL.innodb_buffer_pool_size,
+@@GLOBAL.innodb_buffer_pool_size_auto_min,
+@@GLOBAL.innodb_buffer_pool_size_max;
+@@GLOBAL.innodb_buffer_pool_size	@@GLOBAL.innodb_buffer_pool_size_auto_min	@@GLOBAL.innodb_buffer_pool_size_max
+17825792	16777216	25165824
 CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
 SET GLOBAL innodb_limit_optimistic_insert_debug=2;
 SET STATEMENT unique_checks=0, foreign_key_checks=0 FOR
 INSERT INTO t1 SELECT * FROM seq_1_to_1000;
 SET GLOBAL innodb_limit_optimistic_insert_debug=@save_limit;
 DROP TABLE t1;
-SELECT CAST(VARIABLE_VALUE AS INTEGER) INTO @dirty_prev
-FROM INFORMATION_SCHEMA.GLOBAL_STATUS
-WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_dirty';
-set debug_dbug="d,trigger_garbage_collection";
-SET GLOBAL innodb_buffer_pool_size=@@innodb_buffer_pool_size;
-FOUND 1 /[Mm]emory pressure.*/ in mysqld.1.err
-SELECT CAST(VARIABLE_VALUE AS INTEGER) < @dirty_prev AS LESS_DIRTY_IS_GOOD
-FROM INFORMATION_SCHEMA.GLOBAL_STATUS
-WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_dirty';
-LESS_DIRTY_IS_GOOD
+SET STATEMENT debug_dbug="d,trigger_garbage_collection" FOR
+SET GLOBAL innodb_buffer_pool_size=@innodb_buffer_pool_size;
+FOUND 1 /Memory pressure event disregarded.*/ in mysqld.1.err
+SET STATEMENT debug_dbug="d,trigger_garbage_collection" FOR
+SET GLOBAL innodb_buffer_pool_size_auto_min=
+CAST(@innodb_buffer_pool_size/2 AS UNSIGNED),
+innodb_buffer_pool_size=@innodb_buffer_pool_size;
+Warnings:
+Warning	1292	Truncated incorrect innodb_buffer_pool_size_auto_min value: '8912896'
+select @@global.innodb_buffer_pool_size < @innodb_buffer_pool_size;
+@@global.innodb_buffer_pool_size < @innodb_buffer_pool_size
 1
-FOUND 1 /InnoDB: Memory pressure event freed.*/ in mysqld.1.err
+FOUND 1 /InnoDB: Memory pressure event shrunk.*/ in mysqld.1.err
 set debug_dbug=@save_dbug;
+SET GLOBAL innodb_buffer_pool_size= @innodb_buffer_pool_size;
+SET GLOBAL innodb_buffer_pool_size_auto_min=@innodb_buffer_pool_size_min;
 # End of 10.11 tests
Index: mariadb-10.11.11/mysql-test/suite/innodb/t/innodb_buffer_pool_resize.opt
===================================================================
--- mariadb-10.11.11.orig/mysql-test/suite/innodb/t/innodb_buffer_pool_resize.opt
+++ mariadb-10.11.11/mysql-test/suite/innodb/t/innodb_buffer_pool_resize.opt
@@ -1,3 +1,3 @@
 --innodb-buffer-pool-size=8M
---innodb-buffer-pool-size-max=24M
+--innodb-buffer-pool-size-max=25M
 --innodb-page-size=4k
Index: mariadb-10.11.11/mysql-test/suite/innodb/t/mem_pressure.opt
===================================================================
--- mariadb-10.11.11.orig/mysql-test/suite/innodb/t/mem_pressure.opt
+++ mariadb-10.11.11/mysql-test/suite/innodb/t/mem_pressure.opt
@@ -1,2 +1,3 @@
+--loose-innodb-buffer-pool-size-auto-min=17m
 --innodb-buffer-pool-size-max=17m
 --innodb-buffer-pool-size=17m
Index: mariadb-10.11.11/mysql-test/suite/innodb/t/mem_pressure.test
===================================================================
--- mariadb-10.11.11.orig/mysql-test/suite/innodb/t/mem_pressure.test
+++ mariadb-10.11.11/mysql-test/suite/innodb/t/mem_pressure.test
@@ -2,6 +2,7 @@
 --source include/not_embedded.inc
 --source include/have_innodb.inc
 --source include/have_sequence.inc
+--source include/word_size.inc
 
 --echo #
 --echo # MDEV-24670 avoid OOM by linux kernel co-operative memory management
@@ -14,6 +15,13 @@ set @save_limit=@@GLOBAL.innodb_limit_op
 # This is not an actual parameter, so there is no need to restore it.
 set GLOBAL innodb_max_purge_lag_wait=0;
 
+SET @innodb_buffer_pool_size= @@GLOBAL.innodb_buffer_pool_size;
+SET @innodb_buffer_pool_size_min= @@GLOBAL.innodb_buffer_pool_size_auto_min;
+SELECT
+@@GLOBAL.innodb_buffer_pool_size,
+@@GLOBAL.innodb_buffer_pool_size_auto_min,
+@@GLOBAL.innodb_buffer_pool_size_max;
+
 CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
 SET GLOBAL innodb_limit_optimistic_insert_debug=2;
 SET STATEMENT unique_checks=0, foreign_key_checks=0 FOR
@@ -23,32 +31,31 @@ SET GLOBAL innodb_limit_optimistic_inser
 
 DROP TABLE t1;
 
---disable_cursor_protocol
-SELECT CAST(VARIABLE_VALUE AS INTEGER) INTO @dirty_prev
-FROM INFORMATION_SCHEMA.GLOBAL_STATUS
-WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_dirty';
---enable_cursor_protocol
-
-set debug_dbug="d,trigger_garbage_collection";
-SET GLOBAL innodb_buffer_pool_size=@@innodb_buffer_pool_size;
+SET STATEMENT debug_dbug="d,trigger_garbage_collection" FOR
+SET GLOBAL innodb_buffer_pool_size=@innodb_buffer_pool_size;
 
 let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
-# either a fail or the pressure event
-let SEARCH_PATTERN= [Mm]emory pressure.*;
+let SEARCH_PATTERN= Memory pressure event disregarded.*;
+let SEARCH_WAIT= FOUND;
 --source include/search_pattern_in_file.inc
 
+SET STATEMENT debug_dbug="d,trigger_garbage_collection" FOR
+SET GLOBAL innodb_buffer_pool_size_auto_min=
+CAST(@innodb_buffer_pool_size/2 AS UNSIGNED),
+innodb_buffer_pool_size=@innodb_buffer_pool_size;
+
 # The garbage collection happens asynchronously after trigger, in a background
 # thread. So wait for it to happen to avoid sporadic failure.
 let $wait_condition=
-  SELECT CAST(VARIABLE_VALUE AS INTEGER) < @dirty_prev AS LESS_DIRTY_IS_GOOD
-  FROM INFORMATION_SCHEMA.GLOBAL_STATUS
-  WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_dirty';
+  select @@global.innodb_buffer_pool_size < @innodb_buffer_pool_size;
 --source include/wait_condition.inc
 eval $wait_condition;
-let SEARCH_PATTERN= InnoDB: Memory pressure event freed.*;
+let SEARCH_PATTERN= InnoDB: Memory pressure event shrunk.*;
 let SEARCH_WAIT= FOUND;
 --source include/search_pattern_in_file.inc
 
 set debug_dbug=@save_dbug;
+SET GLOBAL innodb_buffer_pool_size= @innodb_buffer_pool_size;
+SET GLOBAL innodb_buffer_pool_size_auto_min=@innodb_buffer_pool_size_min;
 
 --echo # End of 10.11 tests
Index: mariadb-10.11.11/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff
===================================================================
--- mariadb-10.11.11.orig/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff
+++ mariadb-10.11.11/mysql-test/suite/sys_vars/r/sysvars_innodb,32bit.rdiff
@@ -59,6 +59,21 @@
  VARIABLE_SCOPE	GLOBAL
 -VARIABLE_TYPE	BIGINT UNSIGNED
 +VARIABLE_TYPE	INT UNSIGNED
+ VARIABLE_COMMENT	Minimum innodb_buffer_pool_size for dynamic shrinking on memory pressure
+ NUMERIC_MIN_VALUE	0
+-NUMERIC_MAX_VALUE	18446744073701163008
+-NUMERIC_BLOCK_SIZE	8388608
++NUMERIC_MAX_VALUE	4292870144
++NUMERIC_BLOCK_SIZE	2097152
+ ENUM_VALUE_LIST	NULL
+ READ_ONLY	NO
+ COMMAND_LINE_ARGUMENT	REQUIRED
+@@ -227,11 +227,11 @@
+ SESSION_VALUE	NULL
+ DEFAULT_VALUE	0
+ VARIABLE_SCOPE	GLOBAL
+-VARIABLE_TYPE	BIGINT UNSIGNED
++VARIABLE_TYPE	INT UNSIGNED
  VARIABLE_COMMENT	Maximum innodb_buffer_pool_size
  NUMERIC_MIN_VALUE	0
 -NUMERIC_MAX_VALUE	18446744073701163008
@@ -68,7 +83,7 @@
  ENUM_VALUE_LIST	NULL
  READ_ONLY	YES
  COMMAND_LINE_ARGUMENT	REQUIRED
-@@ -227,7 +227,7 @@
+@@ -239,7 +239,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	0
  VARIABLE_SCOPE	GLOBAL
@@ -77,7 +92,7 @@
  VARIABLE_COMMENT	A number between [0, 100] that tells how oftern buffer pool dump status in percentages should be printed. E.g. 10 means that buffer pool dump status is printed when every 10% of number of buffer pool pages are dumped. Default is 0 (only start and end status is printed).
  NUMERIC_MIN_VALUE	0
  NUMERIC_MAX_VALUE	100
-@@ -347,7 +347,7 @@
+@@ -359,7 +359,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	5
  VARIABLE_SCOPE	GLOBAL
@@ -86,7 +101,7 @@
  VARIABLE_COMMENT	If the compression failure rate of a table is greater than this number more padding is added to the pages to reduce the failures. A value of zero implies no padding
  NUMERIC_MIN_VALUE	0
  NUMERIC_MAX_VALUE	100
-@@ -371,7 +371,7 @@
+@@ -383,7 +383,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	50
  VARIABLE_SCOPE	GLOBAL
@@ -95,7 +110,7 @@
  VARIABLE_COMMENT	Percentage of empty space on a data page that can be reserved to make the page compressible.
  NUMERIC_MIN_VALUE	0
  NUMERIC_MAX_VALUE	75
-@@ -659,7 +659,7 @@
+@@ -671,7 +671,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	600
  VARIABLE_SCOPE	GLOBAL
@@ -104,7 +119,7 @@
  VARIABLE_COMMENT	Maximum number of seconds that semaphore times out in InnoDB.
  NUMERIC_MIN_VALUE	1
  NUMERIC_MAX_VALUE	4294967295
-@@ -707,7 +707,7 @@
+@@ -719,7 +719,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	30
  VARIABLE_SCOPE	GLOBAL
@@ -113,7 +128,7 @@
  VARIABLE_COMMENT	Number of iterations over which the background flushing is averaged.
  NUMERIC_MIN_VALUE	1
  NUMERIC_MAX_VALUE	1000
-@@ -731,7 +731,7 @@
+@@ -743,7 +743,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	1
  VARIABLE_SCOPE	GLOBAL
@@ -122,7 +137,7 @@
  VARIABLE_COMMENT	Controls the durability/speed trade-off for commits. Set to 0 (write and flush redo log to disk only once per second), 1 (flush to disk at each commit), 2 (write to log at commit but flush to disk only once per second) or 3 (flush to disk at prepare and at commit, slower and usually redundant). 1 and 3 guarantees that after a crash, committed transactions will not be lost and will be consistent with the binlog and other transactional engines. 2 can get inconsistent and lose transactions if there is a power failure or kernel crash but not if mysqld crashes. 0 has no guarantees in case of crash. 0 and 2 can be faster than 1 or 3.
  NUMERIC_MIN_VALUE	0
  NUMERIC_MAX_VALUE	3
-@@ -755,7 +755,7 @@
+@@ -767,7 +767,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	1
  VARIABLE_SCOPE	GLOBAL
@@ -131,7 +146,7 @@
  VARIABLE_COMMENT	Set to 0 (don't flush neighbors from buffer pool), 1 (flush contiguous neighbors from buffer pool) or 2 (flush neighbors from buffer pool), when flushing a block
  NUMERIC_MIN_VALUE	0
  NUMERIC_MAX_VALUE	2
-@@ -791,7 +791,7 @@
+@@ -803,7 +803,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	0
  VARIABLE_SCOPE	GLOBAL
@@ -140,7 +155,7 @@
  VARIABLE_COMMENT	Helps to save your data in case the disk image of the database becomes corrupt. Value 5 can return bogus data, and 6 can permanently corrupt data.
  NUMERIC_MIN_VALUE	0
  NUMERIC_MAX_VALUE	6
-@@ -815,10 +815,10 @@
+@@ -827,10 +827,10 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	8000000
  VARIABLE_SCOPE	GLOBAL
@@ -153,7 +168,7 @@
  NUMERIC_BLOCK_SIZE	0
  ENUM_VALUE_LIST	NULL
  READ_ONLY	NO
-@@ -851,7 +851,7 @@
+@@ -863,7 +863,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	84
  VARIABLE_SCOPE	GLOBAL
@@ -171,7 +186,7 @@
  VARIABLE_COMMENT	InnoDB Fulltext search minimum token size in characters
  NUMERIC_MIN_VALUE	0
  NUMERIC_MAX_VALUE	16
-@@ -875,7 +875,7 @@
+@@ -887,7 +887,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	2000
  VARIABLE_SCOPE	GLOBAL
@@ -180,7 +195,7 @@
  VARIABLE_COMMENT	InnoDB Fulltext search number of words to optimize for each optimize table call 
  NUMERIC_MIN_VALUE	1000
  NUMERIC_MAX_VALUE	10000
-@@ -887,10 +887,10 @@
+@@ -899,10 +899,10 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	2000000000
  VARIABLE_SCOPE	GLOBAL
@@ -202,7 +217,7 @@
  VARIABLE_COMMENT	InnoDB Fulltext search parallel sort degree, will round up to nearest power of 2 number
  NUMERIC_MIN_VALUE	1
  NUMERIC_MAX_VALUE	16
-@@ -923,10 +923,10 @@
+@@ -935,10 +935,10 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	640000000
  VARIABLE_SCOPE	GLOBAL
@@ -215,7 +230,7 @@
  NUMERIC_BLOCK_SIZE	0
  ENUM_VALUE_LIST	NULL
  READ_ONLY	NO
-@@ -971,7 +971,7 @@
+@@ -983,7 +983,7 @@
  SESSION_VALUE	NULL
  DEFAULT_VALUE	200
  VARIABLE_SCOPE	GLOBAL
Index: mariadb-10.11.11/mysql-test/suite/sys_vars/r/sysvars_innodb.result
===================================================================
--- mariadb-10.11.11.orig/mysql-test/suite/sys_vars/r/sysvars_innodb.result
+++ mariadb-10.11.11/mysql-test/suite/sys_vars/r/sysvars_innodb.result
@@ -5,7 +5,6 @@ variable_name not in (
 'innodb_evict_tables_on_commit_debug', # one may want to override this
 'innodb_use_native_aio',            # default value depends on OS
 'innodb_log_file_buffering',        # only available on Linux and Windows
-'innodb_buffer_pool_size_auto_min', # only available on Linux for now
 'innodb_buffer_pool_load_pages_abort')            # debug build only, and is only for testing
 order by variable_name;
 VARIABLE_NAME	INNODB_ADAPTIVE_FLUSHING
@@ -212,6 +211,18 @@ NUMERIC_BLOCK_SIZE	1048576
 ENUM_VALUE_LIST	NULL
 READ_ONLY	NO
 COMMAND_LINE_ARGUMENT	REQUIRED
+VARIABLE_NAME	INNODB_BUFFER_POOL_SIZE_AUTO_MIN
+SESSION_VALUE	NULL
+DEFAULT_VALUE	0
+VARIABLE_SCOPE	GLOBAL
+VARIABLE_TYPE	BIGINT UNSIGNED
+VARIABLE_COMMENT	Minimum innodb_buffer_pool_size for dynamic shrinking on memory pressure
+NUMERIC_MIN_VALUE	0
+NUMERIC_MAX_VALUE	18446744073701163008
+NUMERIC_BLOCK_SIZE	8388608
+ENUM_VALUE_LIST	NULL
+READ_ONLY	NO
+COMMAND_LINE_ARGUMENT	REQUIRED
 VARIABLE_NAME	INNODB_BUFFER_POOL_SIZE_MAX
 SESSION_VALUE	NULL
 DEFAULT_VALUE	0
Index: mariadb-10.11.11/mysql-test/suite/sys_vars/t/sysvars_innodb.test
===================================================================
--- mariadb-10.11.11.orig/mysql-test/suite/sys_vars/t/sysvars_innodb.test
+++ mariadb-10.11.11/mysql-test/suite/sys_vars/t/sysvars_innodb.test
@@ -12,6 +12,5 @@ select VARIABLE_NAME, SESSION_VALUE, DEF
     'innodb_evict_tables_on_commit_debug', # one may want to override this
     'innodb_use_native_aio',            # default value depends on OS
     'innodb_log_file_buffering',        # only available on Linux and Windows
-    'innodb_buffer_pool_size_auto_min', # only available on Linux for now
     'innodb_buffer_pool_load_pages_abort')            # debug build only, and is only for testing
   order by variable_name;
Index: mariadb-10.11.11/storage/innobase/buf/buf0buf.cc
===================================================================
--- mariadb-10.11.11.orig/storage/innobase/buf/buf0buf.cc
+++ mariadb-10.11.11/storage/innobase/buf/buf0buf.cc
@@ -1018,97 +1018,98 @@ ATTRIBUTE_COLD void buf_mem_pressure_shu
 {
   mem_pressure_obj.join();
 }
+#endif
 
+#if defined __linux__ || !defined DBUG_OFF
 inline void buf_pool_t::garbage_collect() noexcept
 {
   mysql_mutex_lock(&mutex);
-  size_t freed= 0;
-
-#ifdef BTR_CUR_HASH_ADAPT
-  /* buf_LRU_free_page() will temporarily release and reacquire
-  buf_pool.mutex for invoking btr_search_drop_page_hash_index(). Thus,
-  we must protect ourselves with the hazard pointer. */
-rescan:
-#else
-  lru_hp.set(nullptr);
-#endif
-  for (buf_page_t *bpage= UT_LIST_GET_LAST(LRU), *prev; bpage; bpage= prev)
+  const size_t old_size{size_in_bytes}, min_size{size_in_bytes_auto_min};
+  const size_t reduce_size=
+    std::max(innodb_buffer_pool_extent_size,
+             ut_calc_align((old_size - min_size) / 2,
+                           innodb_buffer_pool_extent_size));
+  if (old_size < min_size + reduce_size ||
+      first_to_withdraw || old_size != size_in_bytes_requested)
   {
-    prev= UT_LIST_GET_PREV(LRU, bpage);
-#ifdef BTR_CUR_HASH_ADAPT
-    lru_hp.set(prev);
-#endif
-    auto state= bpage->state();
-    ut_ad(state >= buf_page_t::FREED);
-    ut_ad(bpage->in_LRU_list);
-
-    /* We try to free any pages that can be freed without writing out
-    anything. */
-    switch (bpage->oldest_modification()) {
-    case 0:
-    try_to_evict:
-      if (buf_LRU_free_page(bpage, true))
-      {
-      evicted:
-        freed++;
-#ifdef BTR_CUR_HASH_ADAPT
-        bpage= prev;
-        prev= lru_hp.get();
-        if (!prev && bpage)
-          goto rescan;
-#endif
-      }
-      continue;
-    case 1:
-      break;
-    default:
-      if (state >= buf_page_t::UNFIXED)
-        continue;
-    }
+    mysql_mutex_unlock(&mutex);
+    sql_print_information("InnoDB: Memory pressure event disregarded;"
+                          " innodb_buffer_pool_size=%zum,"
+                          " innodb_buffer_pool_size_min=%zum",
+                          old_size >> 20, min_size >> 20);
+    return;
+  }
+
+  size_t size= old_size - reduce_size;
+  size_t n_blocks_new= get_n_blocks(size);
+
+  ut_ad(UT_LIST_GET_LEN(withdrawn) == 0);
+  ut_ad(n_blocks_to_withdraw == 0);
+
+  n_blocks_to_withdraw= n_blocks - n_blocks_new;
+  first_to_withdraw= &get_nth_page(n_blocks_new)->page;
 
-    if (state < buf_page_t::READ_FIX && bpage->lock.u_lock_try(true))
+  size_in_bytes_requested= size;
+  mysql_mutex_unlock(&mutex);
+  mysql_mutex_lock(&flush_list_mutex);
+  page_cleaner_wakeup(true);
+  my_cond_wait(&done_flush_list, &flush_list_mutex.m_mutex);
+  mysql_mutex_unlock(&flush_list_mutex);
+# ifdef BTR_CUR_HASH_ADAPT
+  bool ahi_disabled= btr_search_disable();
+# endif /* BTR_CUR_HASH_ADAPT */
+  time_t start= time(nullptr);
+  mysql_mutex_lock(&mutex);
+
+  do
+  {
+    if (shrink(size))
     {
-      ut_ad(!bpage->is_io_fixed());
-      lsn_t oldest_modification= bpage->oldest_modification();
-      switch (oldest_modification) {
-      case 1:
-        mysql_mutex_lock(&flush_list_mutex);
-        oldest_modification= bpage->oldest_modification();
-        if (oldest_modification)
-        {
-          ut_ad(oldest_modification == 1);
-          delete_from_flush_list(bpage);
-        }
-        mysql_mutex_unlock(&flush_list_mutex);
-        /* fall through */
-      case 0:
-        bpage->lock.u_unlock(true);
-        goto try_to_evict;
-      default:
-        if (bpage->state() < buf_page_t::UNFIXED &&
-            oldest_modification <= log_sys.get_flushed_lsn())
-        {
-          release_freed_page(bpage);
-          goto evicted;
-        }
-        else
-          bpage->lock.u_unlock(true);
-      }
+      const size_t old_blocks{n_blocks};
+      n_blocks= n_blocks_new;
+
+      size_t s= n_blocks_new / BUF_READ_AHEAD_PORTION;
+      read_ahead_area= s >= READ_AHEAD_PAGES
+        ? READ_AHEAD_PAGES
+        : my_round_up_to_next_power(uint32(s));
+
+      os_total_large_mem_allocated-= reduce_size;
+      shrunk(size, reduce_size);
+      ibuf_max_size_update(srv_change_buffer_max_size);
+# ifdef BTR_CUR_HASH_ADAPT
+      if (ahi_disabled)
+        btr_search_enable(true);
+# endif
+      mysql_mutex_unlock(&mutex);
+      sql_print_information("InnoDB: Memory pressure event shrunk"
+                            " innodb_buffer_pool_size=%zum (%zu pages)"
+                            " from %zum (%zu pages)",
+                            size >> 20, n_blocks_new, old_size >> 20,
+                            old_blocks);
+      ut_d(validate());
+      return;
     }
   }
+  while (time(nullptr) - start < 15);
+
+  ut_ad(size_in_bytes > size_in_bytes_requested);
+  n_blocks_to_withdraw= 0;
+  first_to_withdraw= nullptr;
+  size_in_bytes_requested= size_in_bytes;
+
+  while (buf_page_t *b= UT_LIST_GET_FIRST(withdrawn))
+  {
+    UT_LIST_REMOVE(withdrawn, b);
+    UT_LIST_ADD_LAST(free, b);
+    ut_d(b->in_free_list= true);
+    ut_ad(b->state() == buf_page_t::NOT_USED);
+    b->lock.init();
+  }
 
-#if defined MADV_FREE
-  /* FIXME: Issue fewer calls for larger contiguous blocks of
-  memory. For now, we assume that this is acceptable, because this
-  code should be executed rarely. */
-  for (buf_page_t *bpage= UT_LIST_GET_FIRST(free); bpage;
-       bpage= UT_LIST_GET_NEXT(list, bpage))
-    madvise(bpage->frame, srv_page_size, MADV_FREE);
-#endif
   mysql_mutex_unlock(&mutex);
-  sql_print_information("InnoDB: Memory pressure event freed %zu pages",
-                        freed);
-  return;
+  sql_print_information("InnoDB: Memory pressure event failed to shrink"
+                        " innodb_buffer_pool_size=%zum", old_size);
+  ut_d(validate());
 }
 #endif
 
Index: mariadb-10.11.11/storage/innobase/handler/ha_innodb.cc
===================================================================
--- mariadb-10.11.11.orig/storage/innobase/handler/ha_innodb.cc
+++ mariadb-10.11.11/storage/innobase/handler/ha_innodb.cc
@@ -3711,6 +3711,25 @@ static MYSQL_SYSVAR_SIZE_T(buffer_pool_s
   nullptr, innodb_buffer_pool_size_update, 128U << 20, 2U << 20,
   size_t(-ssize_t(innodb_buffer_pool_extent_size)), 1U << 20);
 
+#if defined __linux__ || !defined DBUG_OFF
+static void innodb_buffer_pool_size_auto_min_update(THD*,st_mysql_sys_var*,
+                                                    void*, const void *save)
+  noexcept
+{
+  mysql_mutex_lock(&buf_pool.mutex);
+  buf_pool.size_in_bytes_auto_min= *static_cast<const size_t*>(save);
+  mysql_mutex_unlock(&buf_pool.mutex);
+}
+
+static MYSQL_SYSVAR_SIZE_T(buffer_pool_size_auto_min,
+                           buf_pool.size_in_bytes_auto_min,
+  PLUGIN_VAR_RQCMDARG,
+  "Minimum innodb_buffer_pool_size for dynamic shrinking on memory pressure",
+  nullptr, innodb_buffer_pool_size_auto_min_update, 0, 0,
+  size_t(-ssize_t(innodb_buffer_pool_extent_size)),
+  innodb_buffer_pool_extent_size);
+#endif
+
 static MYSQL_SYSVAR_SIZE_T(buffer_pool_size_max, buf_pool.size_in_bytes_max,
                            PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
                            "Maximum innodb_buffer_pool_size",
@@ -3815,6 +3834,13 @@ static int innodb_init_params()
                                               innodb_buffer_pool_extent_size);
 
   MYSQL_SYSVAR_NAME(buffer_pool_size).max_val= buf_pool.size_in_bytes_max;
+#if defined __linux__ || !defined DBUG_OFF
+  if (!buf_pool.size_in_bytes_auto_min ||
+      buf_pool.size_in_bytes_auto_min > buf_pool.size_in_bytes_max)
+    buf_pool.size_in_bytes_auto_min= buf_pool.size_in_bytes_max;
+  MYSQL_SYSVAR_NAME(buffer_pool_size_auto_min).max_val=
+    buf_pool.size_in_bytes_max;
+#endif
 
   if (innodb_buffer_pool_size < min)
   {
@@ -19860,6 +19886,9 @@ static MYSQL_SYSVAR_BOOL(encrypt_tempora
 static struct st_mysql_sys_var* innobase_system_variables[]= {
   MYSQL_SYSVAR(autoextend_increment),
   MYSQL_SYSVAR(buffer_pool_size),
+#if defined __linux__ || !defined DBUG_OFF
+  MYSQL_SYSVAR(buffer_pool_size_auto_min),
+#endif
   MYSQL_SYSVAR(buffer_pool_size_max),
   MYSQL_SYSVAR(buffer_pool_chunk_size),
   MYSQL_SYSVAR(buffer_pool_filename),
Index: mariadb-10.11.11/storage/innobase/include/buf0buf.h
===================================================================
--- mariadb-10.11.11.orig/storage/innobase/include/buf0buf.h
+++ mariadb-10.11.11/storage/innobase/include/buf0buf.h
@@ -1205,6 +1205,10 @@ class buf_pool_t
 public:
   /** The requested innodb_buffer_pool_size */
   size_t size_in_bytes_requested;
+#if defined __linux__ || !defined DBUG_OFF
+  /** The minimum allowed innodb_buffer_pool_size in garbage_collect() */
+  size_t size_in_bytes_auto_min;
+#endif
   /** The maximum allowed innodb_buffer_pool_size */
   size_t size_in_bytes_max;
 
