Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PS-9222 Include reordered fields when calculating mlog buffer size #5321

Merged
merged 2 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions mysql-test/suite/innodb/r/instant_alter_index_prefix.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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 ADD COLUMN c4 VARCHAR(18) NOT NULL, ALGORITHM=INSTANT;
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 c2 c3 c4
k1 2021-12-21 00:00:00.000 something value
k3 2021-12-21 00:00:00.000 something else
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 ADD COLUMN c4 VARCHAR(18) NOT NULL, ALGORITHM=INSTANT;
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 c2 c3 c4
k1 2021-12-21 00:00:00.000 something value
k3 2021-12-21 00:00:00.000 something else
DROP TABLE t1;
44 changes: 44 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,44 @@
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;
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;
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;
50 changes: 50 additions & 0 deletions mysql-test/suite/innodb/t/instant_alter_index_prefix.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# PS-9222 Testing if ALGORITHM=INSTANT crashes server
source include/have_debug.inc;

--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 ADD COLUMN c4 VARCHAR(18) NOT NULL, ALGORITHM=INSTANT;

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 ADD COLUMN c4 VARCHAR(18) NOT NULL, ALGORITHM=INSTANT;

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;
56 changes: 56 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,56 @@
# PS-9222 Testing if ALGORITHM=INSTANT crashes server
source include/have_debug.inc;

--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;

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;

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;
88 changes: 48 additions & 40 deletions storage/innobase/mtr/mtr0log.cc
Original file line number Diff line number Diff line change
Expand Up @@ -515,18 +515,28 @@ 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) {
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,
const std::unique_ptr<bool[]> &changed_order, size_t &size_needed) {

Choose a reason for hiding this comment

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

⚠️ misc-unused-parameters ⚠️
parameter changed_order is unused

Suggested change
const std::unique_ptr<bool[]> &changed_order, size_t &size_needed) {
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 +654,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 +684,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,
VarunNagaraju marked this conversation as resolved.
Show resolved Hide resolved
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 +811,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;
satya-bodapati marked this conversation as resolved.
Show resolved Hide resolved
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 +885,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_phy_pos() >= phy_pos) {
phy_pos = col->get_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 +902,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
Loading