Skip to content

Commit

Permalink
PS-9222 ALTER TABLE ALGORITHM=INSTANT FIX #2
Browse files Browse the repository at this point in the history
https://perconadev.atlassian.net/browse/PS-9222

Problem
=======
When writing to the redo log, an issue of column order change not
being recorded with INSTANT DDL was fixed by checking if the fields
are also reordered, then adding the columns into the list.
However when calculating the size of the buffer this fix doesn't take
account the extra fields that may be logged, and causing the assertion
on the buffer size failed eventually.

Solution
========
To calculate the buffer size correctly, we move the logic of finding
reordered fiedls before buffer size calculation, then count the number
of fields with the same logic when deciding if a field needs to be logged.
  • Loading branch information
VarunNagaraju committed Jun 28, 2024
1 parent 366031e commit f8b1da5
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 37 deletions.
46 changes: 46 additions & 0 deletions mysql-test/suite/innodb/r/instant_alter_stored_column_order.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
without prefix field in the index
CREATE TABLE t1 (c1 TINYTEXT COLLATE ascii_bin NOT NULL, c2 DATETIME(3) NOT NULL, c3 TEXT, UNIQUE KEY (c1(30)));
INSERT INTO t1 (c1, c2, c3) VALUE ('k1','2021-12-21','something');
INSERT INTO t1 (c1, c2, c3) VALUE ('k3','2021-12-21','something else');
ALTER TABLE t1
CHANGE c2 c2 DATETIME(3) NOT NULL AFTER c3,
ADD COLUMN c4 VARCHAR(18) NOT NULL,
ALGORITHM=INSTANT;
# Make sure nothing gets flushed on disk
SET GLOBAL innodb_log_checkpoint_now = ON;
SET GLOBAL innodb_page_cleaner_disabled_debug = 1;
SET GLOBAL innodb_dict_stats_disabled_debug = 1;
SET GLOBAL innodb_master_thread_disabled_debug = 1;
SET GLOBAL innodb_checkpoint_disabled = ON;
UPDATE t1 SET c4 = 'value' WHERE c1 = 'k1';
# Restart the server and reload the table to see if tables are corrupted.
# Kill and restart
# Run a select to confirm that the database started up successfully
SELECT * FROM t1;
c1 c3 c2 c4
k1 something 2021-12-21 00:00:00.000 value
k3 something else 2021-12-21 00:00:00.000
DROP TABLE t1;
with prefix field in the index
CREATE TABLE t1 (c1 TINYTEXT COLLATE ascii_bin NOT NULL, c2 DATETIME(3) NOT NULL, c3 TEXT, PRIMARY KEY (c1(30)));
INSERT INTO t1 (c1, c2, c3) VALUE ('k1','2021-12-21','something');
INSERT INTO t1 (c1, c2, c3) VALUE ('k3','2021-12-21','something else');
ALTER TABLE t1
CHANGE c2 c2 DATETIME(3) NOT NULL AFTER c3,
ADD COLUMN c4 VARCHAR(18) NOT NULL,
ALGORITHM=INSTANT;
# Make sure nothing gets flushed on disk
SET GLOBAL innodb_log_checkpoint_now = ON;
SET GLOBAL innodb_page_cleaner_disabled_debug = 1;
SET GLOBAL innodb_dict_stats_disabled_debug = 1;
SET GLOBAL innodb_master_thread_disabled_debug = 1;
SET GLOBAL innodb_checkpoint_disabled = ON;
UPDATE t1 SET c4 = 'value' WHERE c1 = 'k1';
# Restart the server and reload the table to see if tables are corrupted.
# Kill and restart
# Run a select to confirm that the database started up successfully
SELECT * FROM t1;
c1 c3 c2 c4
k1 something 2021-12-21 00:00:00.000 value
k3 something else 2021-12-21 00:00:00.000
DROP TABLE t1;
57 changes: 57 additions & 0 deletions mysql-test/suite/innodb/t/instant_alter_stored_column_order.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# PS-9222 Testing if ALGORITHM=INSTANT crashes server

--echo without prefix field in the index
CREATE TABLE t1 (c1 TINYTEXT COLLATE ascii_bin NOT NULL, c2 DATETIME(3) NOT NULL, c3 TEXT, UNIQUE KEY (c1(30)));
INSERT INTO t1 (c1, c2, c3) VALUE ('k1','2021-12-21','something');
INSERT INTO t1 (c1, c2, c3) VALUE ('k3','2021-12-21','something else');

ALTER TABLE t1
CHANGE c2 c2 DATETIME(3) NOT NULL AFTER c3,
ADD COLUMN c4 VARCHAR(18) NOT NULL,
ALGORITHM=INSTANT;

--echo # Make sure nothing gets flushed on disk
SET GLOBAL innodb_log_checkpoint_now = ON;
SET GLOBAL innodb_page_cleaner_disabled_debug = 1;
SET GLOBAL innodb_dict_stats_disabled_debug = 1;
SET GLOBAL innodb_master_thread_disabled_debug = 1;
SET GLOBAL innodb_checkpoint_disabled = ON;

UPDATE t1 SET c4 = 'value' WHERE c1 = 'k1';

--echo # Restart the server and reload the table to see if tables are corrupted.
--source include/kill_and_restart_mysqld.inc

-- echo # Run a select to confirm that the database started up successfully
SELECT * FROM t1;

# cleanup
DROP TABLE t1;

--echo with prefix field in the index
CREATE TABLE t1 (c1 TINYTEXT COLLATE ascii_bin NOT NULL, c2 DATETIME(3) NOT NULL, c3 TEXT, PRIMARY KEY (c1(30)));
INSERT INTO t1 (c1, c2, c3) VALUE ('k1','2021-12-21','something');
INSERT INTO t1 (c1, c2, c3) VALUE ('k3','2021-12-21','something else');

ALTER TABLE t1
CHANGE c2 c2 DATETIME(3) NOT NULL AFTER c3,
ADD COLUMN c4 VARCHAR(18) NOT NULL,
ALGORITHM=INSTANT;

--echo # Make sure nothing gets flushed on disk
SET GLOBAL innodb_log_checkpoint_now = ON;
SET GLOBAL innodb_page_cleaner_disabled_debug = 1;
SET GLOBAL innodb_dict_stats_disabled_debug = 1;
SET GLOBAL innodb_master_thread_disabled_debug = 1;
SET GLOBAL innodb_checkpoint_disabled = ON;

UPDATE t1 SET c4 = 'value' WHERE c1 = 'k1';

--echo # Restart the server and reload the table to see if tables are corrupted.
--source include/kill_and_restart_mysqld.inc

-- echo # Run a select to confirm that the database started up successfully
SELECT * FROM t1;

# cleanup
DROP TABLE t1;
83 changes: 46 additions & 37 deletions storage/innobase/mtr/mtr0log.cc
Original file line number Diff line number Diff line change
Expand Up @@ -515,18 +515,29 @@ constexpr size_t inst_col_info_size = 6;
@param[in] is_comp true if COMP
@param[in] is_versioned if table has row versions
@param[in] is_instant true if table has INSTANT cols
@param[in] changed_order array indicating fields changed position
@param[out] size_needed total size needed on REDO LOG */
static void log_index_get_size_needed(const dict_index_t *index, size_t size,
uint16_t n, bool is_comp,
bool is_versioned, bool is_instant,
size_t &size_needed) {
auto size_for_versioned_fields = [](const dict_index_t *ind) {
const std::unique_ptr<bool[]>&
changed_order, size_t &size_needed) {
auto size_for_versioned_fields = [n,
&changed_order](const dict_index_t *ind) {
size_t _size = 0;
/* 2 bytes for number of columns with version */
_size += 2;

size_t n_versioned_fields = ind->table->get_n_instant_add_cols() +
ind->table->get_n_instant_drop_cols();
size_t n_versioned_fields = 0;
for (size_t i = 0; i < n; i++) {
dict_field_t *field = ind->get_field(i);
const dict_col_t *col = field->col;
if (col->is_instant_added() || col->is_instant_dropped() ||
changed_order[i]) {
n_versioned_fields += 1;
}
}

ut_ad(n_versioned_fields != 0);

_size += n_versioned_fields * inst_col_info_size;
Expand Down Expand Up @@ -644,6 +655,7 @@ static bool close_and_reopen_log(byte *&log_ptr, const byte *&log_start,
const byte *&log_end, mtr_t *&mtr,
size_t &alloc, size_t &total) {
mlog_close(mtr, log_ptr);

ut_a(total > (ulint)(log_ptr - log_start));
total -= log_ptr - log_start;
alloc = total;
Expand Down Expand Up @@ -673,7 +685,8 @@ template <typename F>
@param[in] func callback to check size reopen log buffer */
static bool log_index_fields(const dict_index_t *index, uint16_t n,
bool is_versioned, std::vector<dict_field_t *> &f,
bool *changed_order, byte *&log_ptr, F &func) {
const std::unique_ptr<bool[]> &changed_order,
byte *&log_ptr, F &func) {
/* Write metadata for each field. Log the fields in their logical order. */
for (size_t i = 0; i < n; i++) {
dict_field_t *field = index->get_field(i);
Expand Down Expand Up @@ -799,9 +812,36 @@ bool mlog_open_and_write_index(mtr_t *mtr, const byte *rec,
n = DICT_INDEX_SPATIAL_NODEPTR_SIZE;
}

/* Ordinal position of an existing field can't be changed with INSTANT
algorithm. But when it is combined with ADD/DROP COLUMN, ordinal position
of a filed can be changed. This bool array of size #fields in index,
represents if ordinal position of an existing filed is changed. */

// Move the logic to find columns that changed order before calculating
// the buffer size
std::unique_ptr<bool[]> fields_with_changed_order = nullptr;
if (is_versioned) {
fields_with_changed_order = std::make_unique<bool[]>(n);

uint16_t phy_pos = 0;
for (size_t i = 0; i < n; i++) {
dict_field_t *field = index->get_field(i);
const dict_col_t *col = field->col;

if (col->is_instant_added() || col->is_instant_dropped()) {
continue;
}
if (col->get_col_phy_pos() >= phy_pos) {
phy_pos = col->get_col_phy_pos();
} else {
fields_with_changed_order[i] = true;
}
}
}

size_t size_needed = 0;
log_index_get_size_needed(index, size, n, is_comp, is_versioned, is_instant,
size_needed);
fields_with_changed_order, size_needed);
size_t total = size_needed;
size_t alloc = total;
if (alloc > mtr_buf_t::MAX_DATA_SIZE) {
Expand Down Expand Up @@ -846,37 +886,10 @@ bool mlog_open_and_write_index(mtr_t *mtr, const byte *rec,
return true;
};

/* Ordinal position of an existing field can't be changed with INSTANT
algorithm. But when it is combined with ADD/DROP COLUMN, ordinal position
of a filed can be changed. This bool array of size #fields in index,
represents if ordinal position of an existing filed is changed. */
bool *fields_with_changed_order = nullptr;
if (is_versioned) {
fields_with_changed_order = new bool[n];
memset(fields_with_changed_order, false, (sizeof(bool) * n));

uint16_t phy_pos = 0;
for (size_t i = 0; i < n; i++) {
dict_field_t *field = index->get_field(i);
const dict_col_t *col = field->col;

if (col->is_instant_added() || col->is_instant_dropped()) {
continue;
} else if (col->get_col_phy_pos() >= phy_pos) {
phy_pos = col->get_col_phy_pos();
} else {
fields_with_changed_order[i] = true;
}
}
}

if (is_comp) {
/* Write fields info. */
if (!log_index_fields(index, n, is_versioned, instant_fields_to_log,
fields_with_changed_order, log_ptr, f)) {
if (is_versioned) {
delete[] fields_with_changed_order;
}
return false;
}
} else if (is_versioned) {
Expand All @@ -890,10 +903,6 @@ bool mlog_open_and_write_index(mtr_t *mtr, const byte *rec,
}
}

if (is_versioned) {
delete[] fields_with_changed_order;
}

if (!instant_fields_to_log.empty()) {
ut_ad(is_versioned);
/* Log INSTANT ADD/DROP fields */
Expand Down

0 comments on commit f8b1da5

Please sign in to comment.