Skip to content

Commit

Permalink
MDEV-27812 Allow SET GLOBAL innodb_log_file_size
Browse files Browse the repository at this point in the history
We support online log resizing by replicating the current ib_logfile0
to a new file ib_logfile101, which will eventually replace the
ib_logfile0 on the first applicable log checkpoint.

Unless the log is located in a persistent memory file system (PMEM),
an attempt to SET GLOBAL innodb_log_file_size to less than
innodb_log_buffer_size will be refused. (With PMEM, a.k.a. mmap()
based log, that parameter has no meaning.)

Should the server be killed while the log was being resized,
both files ib_logfile0 and ib_logfile101 may exist on startup,
and since commit 3b06415
the extra file ib_logfile101 will be removed.

We will initiate checkpoint flushing by invoking buf_flush_ahead(),
to let buf_flush_page_cleaner() write out pages until the
buf_flush_async_lsn target has been reached.

On a log checkpoint, if the new checkpoint LSN is not older than
log_sys.resize_lsn (the start LSN of the ib_logfile101),
we can switch files and complete the log resizing. Else, we will
attempt to switch files on the next checkpoint.

Log resizing can be aborted by killing the connection that is
executing the SET GLOBAL statement.

If the ib_logfile101 wraps around to the beginning, we must
advance the log_sys.resize_lsn. In the resized log file,
the sequence bit will always be written as 1 (no wrap-around).

The log will be duplicated in log_t::resize_write(), invoked by
mtr_t::finish_write().

When the log is being written via system calls (not PMEM), the initial
log_sys.resize_lsn is the current log_sys.first_lsn, plus an integer
multiple of log_sys.block_size, corresponding to the LSN at the start
of the block that was written by log_sys.write_lsn. The log_sys.resize_buf
will be of the same size as the log_sys.buf. During resizing, the
contents of log_sys.buf and log_sys.resize_buf will be identical,
except that the sequence bit of each mini-transaction will always be 1 in
log_sys.resize_buf. If resizing is in progress, log_t::write_buf()
will write log_sys.resize_buf to log_sys.resize_log (ib_logfile101).
If the file would wrap around, the buffer will be written to
log_sys.START_OFFSET and the log_sys.resize_lsn advanced accordingly.

When using mmap() on /dev/shm or a PMEM mount -o dax file system,
the initial log_sys.resize_lsn will be the log_sys.lsn at the time
the resizing is initiated. If the log file wraps around during resizing,
then the log_sys.resize_lsn will be advanced by
(log_sys.resize_target - log_sys.START_OFFSET).

log_t::resize_start(), log_t::resize_abort(), log_t::write_checkpoint():
Unless the log is mmap() based, acquire flush_lock and write_lock.
In any case, acquire exclusive log_sys.latch to prevent race conditions.

log_t::resize_rename(): Renamed from log_t::rename_resized(),
and moved some code to the previous sole caller srv_start().

Thanks to Vladislav Vaintroub for helpful review comments
and to Matthias Leich for testing this, in particular, testing
crash recovery, multiple concurrent SET GLOBAL innodb_log_file_size
and frequently killed connections.
  • Loading branch information
dr-m committed Mar 2, 2022
1 parent 24a1795 commit 177345d
Show file tree
Hide file tree
Showing 12 changed files with 742 additions and 86 deletions.
47 changes: 47 additions & 0 deletions mysql-test/suite/innodb/r/log_file_size_online.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
SET GLOBAL innodb_log_file_size=4194304;
SHOW VARIABLES LIKE 'innodb_log_file_size';
Variable_name Value
innodb_log_file_size 4194304
SELECT global_value FROM information_schema.system_variables
WHERE variable_name = 'innodb_log_file_size';
global_value
4194304
CREATE TABLE t (
a INT PRIMARY KEY AUTO_INCREMENT,
b CHAR(255) NOT NULL)
ENGINE=INNODB;
INSERT INTO t SELECT NULL, REPEAT('a', 255) FROM seq_1_to_20000;
# restart: --innodb-log-file-size=4194304
SELECT COUNT(*) FROM t;
COUNT(*)
20000
SHOW VARIABLES LIKE 'innodb_log_file_size';
Variable_name Value
innodb_log_file_size 4194304
FOUND 1 /InnoDB: Resized log to 4\.000MiB/ in mysqld.1.err
UPDATE t SET b='' WHERE a<10;
SET GLOBAL innodb_log_file_size=5242880;
SHOW VARIABLES LIKE 'innodb_log_file_size';
Variable_name Value
innodb_log_file_size 5242880
SELECT global_value FROM information_schema.system_variables
WHERE variable_name = 'innodb_log_file_size';
global_value
5242880
# restart
SELECT * FROM t WHERE a<10;
a b
1
2
3
4
5
6
7
8
9
SHOW VARIABLES LIKE 'innodb_log_file_size';
Variable_name Value
innodb_log_file_size 5242880
FOUND 1 /InnoDB: Resized log to 5\.000MiB/ in mysqld.1.err
DROP TABLE t;
11 changes: 11 additions & 0 deletions mysql-test/suite/innodb/t/log_file_size_online.combinations
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[encrypted]
--plugin-load-add=$FILE_KEY_MANAGEMENT_SO
--loose-file-key-management
--loose-file-key-management-filename=$MYSQL_TEST_DIR/std_data/logkey.txt
--innodb-encrypt-log=ON
--innodb-log-file-size=5M

[slow]
--innodb-flush-sync=OFF
--innodb-encrypt-log=OFF
--innodb-log-file-size=5M
43 changes: 43 additions & 0 deletions mysql-test/suite/innodb/t/log_file_size_online.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--source include/have_innodb.inc
--source include/have_sequence.inc

let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.1.err;

SET GLOBAL innodb_log_file_size=4194304;
SHOW VARIABLES LIKE 'innodb_log_file_size';
SELECT global_value FROM information_schema.system_variables
WHERE variable_name = 'innodb_log_file_size';

CREATE TABLE t (
a INT PRIMARY KEY AUTO_INCREMENT,
b CHAR(255) NOT NULL)
ENGINE=INNODB;

INSERT INTO t SELECT NULL, REPEAT('a', 255) FROM seq_1_to_20000;

--let $restart_parameters=--innodb-log-file-size=4194304
--source include/restart_mysqld.inc

SELECT COUNT(*) FROM t;

SHOW VARIABLES LIKE 'innodb_log_file_size';
let SEARCH_PATTERN = InnoDB: Resized log to 4\\.000MiB;
--source include/search_pattern_in_file.inc

UPDATE t SET b='' WHERE a<10;

SET GLOBAL innodb_log_file_size=5242880;
SHOW VARIABLES LIKE 'innodb_log_file_size';
SELECT global_value FROM information_schema.system_variables
WHERE variable_name = 'innodb_log_file_size';

--let $restart_parameters=
--source include/restart_mysqld.inc

SELECT * FROM t WHERE a<10;

SHOW VARIABLES LIKE 'innodb_log_file_size';
let SEARCH_PATTERN = InnoDB: Resized log to 5\\.000MiB;
--source include/search_pattern_in_file.inc

DROP TABLE t;
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ COUNT(@@GLOBAL.innodb_log_file_size)
1
1 Expected
'#---------------------BS_STVARS_035_02----------------------#'
SET @@GLOBAL.innodb_log_file_size=1;
ERROR HY000: Variable 'innodb_log_file_size' is a read only variable
Expected error 'Read only variable'
SET @@GLOBAL.innodb_log_file_size=10485760;
SELECT COUNT(@@GLOBAL.innodb_log_file_size);
COUNT(@@GLOBAL.innodb_log_file_size)
1
Expand Down
2 changes: 1 addition & 1 deletion mysql-test/suite/sys_vars/r/sysvars_innodb.result
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,7 @@ NUMERIC_MIN_VALUE 4194304
NUMERIC_MAX_VALUE 18446744073709551615
NUMERIC_BLOCK_SIZE 4096
ENUM_VALUE_LIST NULL
READ_ONLY YES
READ_ONLY NO
COMMAND_LINE_ARGUMENT REQUIRED
VARIABLE_NAME INNODB_LOG_GROUP_HOME_DIR
SESSION_VALUE NULL
Expand Down
5 changes: 2 additions & 3 deletions mysql-test/suite/sys_vars/t/innodb_log_file_size_basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ SELECT COUNT(@@GLOBAL.innodb_log_file_size);
# Check if Value can set #
####################################################################

--error ER_INCORRECT_GLOBAL_LOCAL_VAR
SET @@GLOBAL.innodb_log_file_size=1;
--echo Expected error 'Read only variable'
#--error ER_INCORRECT_GLOBAL_LOCAL_VAR
SET @@GLOBAL.innodb_log_file_size=10485760;

SELECT COUNT(@@GLOBAL.innodb_log_file_size);
--echo 1 Expected
Expand Down
121 changes: 114 additions & 7 deletions storage/innobase/buf/buf0flu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1717,30 +1717,135 @@ inline void log_t::write_checkpoint(lsn_t end_lsn) noexcept
mach_write_to_8(my_assume_aligned<8>(c), next_checkpoint_lsn);
mach_write_to_8(my_assume_aligned<8>(c + 8), end_lsn);
mach_write_to_4(my_assume_aligned<4>(c + 60), my_crc32c(0, c, 60));

lsn_t resizing= resize_lsn.load(std::memory_order_relaxed);

#ifdef HAVE_PMEM
if (is_pmem())
{
if (resizing > 1 && resizing <= next_checkpoint_lsn)
{
memcpy_aligned<64>(resize_buf + CHECKPOINT_1, c, 64);
header_write(resize_buf, resizing, is_encrypted());
pmem_persist(resize_buf, resize_target);
}
pmem_persist(c, 64);
}
else
#endif
{
n_pending_checkpoint_writes++;
latch.wr_unlock();
log_write_and_flush_prepare();
/* FIXME: issue an asynchronous write */
log.write(offset, {c, get_block_size()});
if (srv_file_flush_method != SRV_O_DSYNC)
ut_a(log.flush());
latch.wr_lock(SRW_LOCK_CALL);
if (resizing > 1 && resizing <= next_checkpoint_lsn)
{
byte *buf= static_cast<byte*>(aligned_malloc(4096, 4096));
memset_aligned<4096>(buf, 0, 4096);
header_write(buf, resizing, is_encrypted());
resize_log.write(0, {buf, 4096});
aligned_free(buf);
resize_log.write(CHECKPOINT_1, {c, get_block_size()});
latch.wr_lock(SRW_LOCK_CALL);
/* FIXME: write and flush outside exclusive latch */
ut_a(flush(write_buf<false>()));
}
else
{
if (srv_file_flush_method != SRV_O_DSYNC)
ut_a(log.flush());
latch.wr_lock(SRW_LOCK_CALL);
}
n_pending_checkpoint_writes--;
resizing= resize_lsn.load(std::memory_order_relaxed);
}

ut_ad(!n_pending_checkpoint_writes);
next_checkpoint_no++;
last_checkpoint_lsn= next_checkpoint_lsn;
const lsn_t checkpoint_lsn{next_checkpoint_lsn};
last_checkpoint_lsn= checkpoint_lsn;

DBUG_PRINT("ib_log", ("checkpoint ended at " LSN_PF ", flushed to " LSN_PF,
next_checkpoint_lsn, get_flushed_lsn()));
checkpoint_lsn, get_flushed_lsn()));
lsn_t resizing_completed= 0;

if (resizing > 1 && resizing <= checkpoint_lsn)
{
ut_ad(is_pmem() == !resize_flush_buf);

if (!is_pmem())
{
if (srv_file_flush_method != SRV_O_DSYNC)
ut_a(resize_log.flush());
IF_WIN(log.close(),);
}

if (resize_rename())
{
/* Resizing failed. Discard the log_sys.resize_log. */
#ifdef HAVE_PMEM
if (is_pmem())
my_munmap(resize_buf, resize_target);
else
#endif
{
ut_free_dodump(resize_buf, buf_size);
ut_free_dodump(resize_flush_buf, buf_size);
#ifdef _WIN32
ut_ad(!log.is_opened());
bool success;
log.m_file=
os_file_create_func(get_log_file_path().c_str(),
OS_FILE_OPEN | OS_FILE_ON_ERROR_NO_EXIT,
OS_FILE_NORMAL, OS_LOG_FILE, false, &success);
ut_a(success);
ut_a(log.is_opened());
#endif
}
}
else
{
/* Adopt the resized log. */
#ifdef HAVE_PMEM
if (is_pmem())
{
my_munmap(buf, file_size);
buf= resize_buf;
buf_free= START_OFFSET + (get_lsn() - resizing);
}
else
#endif
{
IF_WIN(,log.close());
std::swap(log, resize_log);
ut_free_dodump(buf, buf_size);
ut_free_dodump(flush_buf, buf_size);
buf= resize_buf;
flush_buf= resize_flush_buf;
}
srv_log_file_size= resizing_completed= file_size= resize_target;
first_lsn= resizing;
set_capacity();
}
ut_ad(!resize_log.is_opened());
resize_buf= nullptr;
resize_flush_buf= nullptr;
resize_target= 0;
resize_lsn.store(0, std::memory_order_relaxed);
}

latch.wr_unlock();
log_resize_release();

if (UNIV_LIKELY(resizing <= 1));
else if (resizing > checkpoint_lsn)
buf_flush_ahead(resizing, false);
else if (resizing_completed)
ib::info() << "Resized log to " << ib::bytes_iec{resizing_completed}
<< "; start LSN=" << resizing;
else
sql_print_error("InnoDB: Resize of log failed at " LSN_PF,
get_flushed_lsn());
}

/** Initiate a log checkpoint, discarding the start of the log.
Expand All @@ -1758,7 +1863,9 @@ static bool log_checkpoint_low(lsn_t oldest_lsn, lsn_t end_lsn)
ut_ad(!recv_no_log_write);

if (oldest_lsn == log_sys.last_checkpoint_lsn ||
(oldest_lsn == end_lsn && oldest_lsn == log_sys.last_checkpoint_lsn +
(oldest_lsn == end_lsn &&
!log_sys.resize_in_progress() &&
oldest_lsn == log_sys.last_checkpoint_lsn +
(log_sys.is_encrypted()
? SIZE_OF_FILE_CHECKPOINT + 8 : SIZE_OF_FILE_CHECKPOINT)))
{
Expand Down
56 changes: 54 additions & 2 deletions storage/innobase/handler/ha_innodb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18461,6 +18461,57 @@ buffer_pool_load_abort(
}
}

static void innodb_log_file_size_update(THD *thd, st_mysql_sys_var*,
void *var, const void *save)
{
ut_ad(var == &srv_log_file_size);
mysql_mutex_unlock(&LOCK_global_system_variables);

if (high_level_read_only)
ib_senderrf(thd, IB_LOG_LEVEL_ERROR, ER_READ_ONLY_MODE);
else if (!log_sys.is_pmem() &&
*static_cast<const ulonglong*>(save) < log_sys.buf_size)
my_printf_error(ER_WRONG_ARGUMENTS,
"innodb_log_file_size must be at least"
" innodb_log_buffer_size=%zu", MYF(0), log_sys.buf_size);
else
{
switch (log_sys.resize_start(*static_cast<const ulonglong*>(save))) {
case log_t::RESIZE_NO_CHANGE:
break;
case log_t::RESIZE_IN_PROGRESS:
my_printf_error(ER_WRONG_USAGE,
"innodb_log_file_size change is already in progress",
MYF(0));
break;
case log_t::RESIZE_FAILED:
ib_senderrf(thd, IB_LOG_LEVEL_ERROR, ER_CANT_CREATE_HANDLER_FILE);
break;
case log_t::RESIZE_STARTED:
for (timespec abstime;;)
{
if (thd_kill_level(thd))
{
log_sys.resize_abort();
break;
}

set_timespec(abstime, 5);
mysql_mutex_lock(&buf_pool.flush_list_mutex);
const bool in_progress(buf_pool.get_oldest_modification(LSN_MAX) <
log_sys.resize_in_progress());
if (in_progress)
my_cond_timedwait(&buf_pool.do_flush_list,
&buf_pool.flush_list_mutex.m_mutex, &abstime);
mysql_mutex_unlock(&buf_pool.flush_list_mutex);
if (!log_sys.resize_in_progress())
break;
}
}
}
mysql_mutex_lock(&LOCK_global_system_variables);
}

/** Update innodb_status_output or innodb_status_output_locks,
which control InnoDB "status monitor" output to the error log.
@param[out] var current value
Expand Down Expand Up @@ -19270,9 +19321,10 @@ static MYSQL_SYSVAR_SIZE_T(log_buffer_size, log_sys.buf_size,
NULL, NULL, 16U << 20, 2U << 20, SIZE_T_MAX, 4096);

static MYSQL_SYSVAR_ULONGLONG(log_file_size, srv_log_file_size,
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
PLUGIN_VAR_RQCMDARG,
"Redo log size in bytes.",
NULL, NULL, 96 << 20, 4 << 20, std::numeric_limits<ulonglong>::max(), 4096);
nullptr, innodb_log_file_size_update,
96 << 20, 4 << 20, std::numeric_limits<ulonglong>::max(), 4096);

static MYSQL_SYSVAR_UINT(old_blocks_pct, innobase_old_blocks_pct,
PLUGIN_VAR_RQCMDARG,
Expand Down
Loading

0 comments on commit 177345d

Please sign in to comment.