ある日 InnoDB Cluster をコンテナ上で動かしてたがメモリを期待していた以上に消費していたので調べていた。

その時は innodb_dedicated_server を 1 にしてた。

innodb_dedicated_server

MySQL 8.0 から追加された変数で自動で innodb_buffer_pool_size とかを自動で決めてくれる便利もの。
dev.mysql.com

ソースを覗いてみる

下記ソースコードたちは MySQL 8.0.18 のやつ

buffer_pool_size が決まるところ

ha_Innodb.cc:3992 で innodb_dedicated_server が ON の場合の分岐が走り、
ha_innodb.cc:3999 でサーバーにあるメモリ量を取得して innodb_buffer_pool_size が決まる。

ha_innodb.cc の innodb_buffer_pool_size_init 部分 ↓

/** Initialize and normalize innodb_buffer_pool_size. */
static void innodb_buffer_pool_size_init() {
#ifdef UNIV_DEBUG
ulong srv_buf_pool_instances_org = srv_buf_pool_instances;
#endif /* UNIV_DEBUG */
acquire_sysvar_source_service();
/* If innodb_dedicated_server == ON */
if (srv_dedicated_server && sysvar_source_svc != nullptr) {
static const char *variable_name = "innodb_buffer_pool_size";
enum enum_variable_source source;
if (!sysvar_source_svc->get(
variable_name, static_cast<unsigned int>(strlen(variable_name)),
&source)) {
if (source == COMPILED) {
double server_mem = get_sys_mem();
if (server_mem < 1.0) {
;
} else if (server_mem <= 4.0) {
srv_buf_pool_size = static_cast<ulint>(server_mem * 0.5 * GB);
} else
srv_buf_pool_size = static_cast<ulint>(server_mem * 0.75 * GB);
} else {
ib::warn(ER_IB_MSG_533)
<< "Option innodb_dedicated_server"
" is ignored for"
" innodb_buffer_pool_size because"
" innodb_buffer_pool_size="
<< srv_buf_pool_curr_size << " is specified explicitly.";
}
}
}
release_sysvar_source_service();
if (srv_buf_pool_size >= BUF_POOL_SIZE_THRESHOLD) {
if (srv_buf_pool_instances == srv_buf_pool_instances_default) {
#if defined(_WIN32) && !defined(_WIN64)
/* Do not allocate too large of a buffer pool on
      Windows 32-bit systems, which can have trouble
      allocating larger single contiguous memory blocks. */
srv_buf_pool_instances =
ut_min(static_cast<ulong>(MAX_BUFFER_POOLS),
static_cast<ulong>(srv_buf_pool_size / (128 * 1024 * 1024)));
#else  /* defined(_WIN32) && !defined(_WIN64) */
/* Default to 8 instances when size > 1GB. */
srv_buf_pool_instances = 8;
#endif /* defined(_WIN32) && !defined(_WIN64) */
}
} else {
/* If buffer pool is less than 1 GiB, assume fewer
    threads. Also use only one buffer pool instance. */
if (srv_buf_pool_instances != srv_buf_pool_instances_default &&
srv_buf_pool_instances != 1) {
/* We can't distinguish whether the user has explicitly
      started mysqld with --innodb-buffer-pool-instances=0,
      (srv_buf_pool_instances_default is 0) or has not
      specified that option at all. Thus we have the
      limitation that if the user started with =0, we
      will not emit a warning here, but we should actually
      do so. */
ib::info(ER_IB_MSG_534)
<< "Adjusting innodb_buffer_pool_instances"
" from "
<< srv_buf_pool_instances
<< " to 1"
" since innodb_buffer_pool_size is less than "
<< BUF_POOL_SIZE_THRESHOLD / (1024 * 1024) << " MiB";
}
srv_buf_pool_instances = 1;
}
#ifdef UNIV_DEBUG
if (srv_buf_pool_debug &&
srv_buf_pool_instances_org != srv_buf_pool_instances_default) {
srv_buf_pool_instances = srv_buf_pool_instances_org;
};
#endif /* UNIV_DEBUG */
srv_buf_pool_chunk_unit = buf_pool_adjust_chunk_unit(srv_buf_pool_chunk_unit);
srv_buf_pool_size = buf_pool_size_align(srv_buf_pool_size);
ut_ad(srv_buf_pool_chunk_unit >= srv_buf_pool_chunk_unit_min);
ut_ad(srv_buf_pool_chunk_unit <= srv_buf_pool_chunk_unit_max);
ut_ad(srv_buf_pool_chunk_unit % srv_buf_pool_chunk_unit_blk_sz == 0);
ut_ad(srv_buf_pool_chunk_unit % UNIV_PAGE_SIZE == 0);
ut_ad(0 ==
srv_buf_pool_size % (srv_buf_pool_chunk_unit * srv_buf_pool_instances));
ut_ad(srv_buf_pool_chunk_unit * srv_buf_pool_instances <= srv_buf_pool_size);
srv_buf_pool_curr_size = srv_buf_pool_size;
}

メモリサイズを返しているところ

get_sys_mem() でサーバーに割り当てられたメモリ量を取得している。

ha_innodb.cc:297 ↓

static double get_mem_sysconf() {
return (((double)sysconf(_SC_PHYS_PAGES)) *
((double)sysconf(_SC_PAGESIZE) / GB));
}

_SC_PHYS_PAGES

物理メモリのページ数

_SC_PAGESIZE

バイト単位でのページサイズ

MySQL はメモリの管理方式としてはページング方式を使っていて物理メモリのページ数と1ページ辺りのサイズをかけて返してるらしい。
_SC_PHYS_PAGES は物理マシンのページ数を返すのでコンテナ(プロセス)で動かす場合では不都合が起きる。

似たようなことを経験したことがあったからある程度予測はついていた。
rarirure.rip

C 読めないし、MySQL Server はソースが膨大でコンパイルして開くまで30分かかった。
間違ってたら(補足があったら)教えてくれると助かります🙏