테스트배경
aurora3 (mysql8) 부터는 드디어 컬럼추가할 때, 컬럼 드랍할 때 수행 즉시 완료된다 ! (다른 DDL, index 추가 등은 아직 아닙니다..!)
기존에는 aurora (mysql 5.7) 에서는 inplace 방식으로 진행되기 때문에 테이블 사이즈에 따라 한참 걸려서 큰 테이블에 대한 컬럼 추가 작업은 어려움이 있었는데 이젠 instant로 바로 반영된다.
기존 inplace 방식을 간단히 살펴보고, instant 는 어떻게 수행되도록 변경되었는지를 확인해본다
algorithm=inplace
컬럼 추가 기준으로 봤을 때 (인덱스 추가는 조금 다름)
- inplace는 DDL이 반영된 임시테이블을 하나 만들고
- 데이터를 원본테이블에서 읽어서 부어주고,
- DDL반영 중 들어온 변경사항들을 마저 반영해준 뒤,
- 메타데이터 락을 획득하여 DML의 추가유입을 막고
- 임시테이블과 원본테이블을 바꿔치기 하는 식으로 진행 됩니다.
DBA가 트래픽이 많은시간, 배치잡이 도는 시간을 피해서 DDL을 수행하려는 이유가 바로 2,3,4번에 있습니다.
inplace 의 대략적인 프로세스를 살펴보자면 아래와 같습니다.
(2,3 번 과정은 너무 복잡해서 다 이해하지 못하여 생략,,,)
--------------------------- 1번
- mysql_alter_table
- open_and_process_table
- MDL_context::acquire_lock
- open_table_get_mdl_lock
- create_table_impl
- rea_create_base_table
- dd::create_table
- ha_innobase::check_if_supported_inplace_alter: COPY 방식인지, INPLACE 방식인지 체크
- mysql_inplace_alter_table
- MDL_context::upgrade_shared_lock: 다른 DDL이 또 들어오지 않도록 metadata lock을 건다, DML은 가능한 metadata lock
- THD_STAGE_INFO(thd, stage_alter_inplace_prepare): prepare 단계로 진입
- handler::ha_prepare_inplace_alter_table:
- ha_innobase::prepare_inplace_alter_table
- ha_innobase::prepare_inplace_alter_table_impl
- row_create_table_for_mysql: 임시 테이블 생성
--------------------------- 2번
- THD_STAGE_INFO(thd, stage_alter_inplace)
- handler::ha_inplace_alter_table
- ha_innobase::inplace_alter_table
- ha_innobase::inplace_alter_table_impl
------------ 아래 부터는 Primary key 를 읽으면서 기존 데이터 복사 진행
- row_merge_build_indexes:
- stage->begin_phase_read_pk:
- begin_phase_read_pk:
- row_merge_read_clustered_index:
- merge_buf[i] = row_merge_buf_create(index[i]): 각 인덱스마다 buffer 생성, innodb_sort_buffer_size로 읽기
- row_merge_insert_index_tuples():
- stage->begin_phase_insert: srv_stage_alter_table_insert 단계로 진입
- end_phase_read_pk:
--------------------------- 3번
- m_stage->begin_phase_flush
- row_merge_write_redo
- row_log_apply
- stage->begin_phase_log_index
- row_log_apply_ops: row log 적용
- if (error == DB_SUCCESS && ctx->online && ctx->need_rebuild())
- row_log_table_apply
- stage->begin_phase_log_table
--------------------------- 4번,5번
- wait_while_table_is_used
- MDL_context::upgrade_shared_lock
- MDL_context::acquire_lock : 커밋 전 MDL_EXCLUSIVE 잠금으로 업그레이드 대기
- THD_STAGE_INFO(thd, stage_alter_inplace_commit): DDL ccomit
- handler::ha_commit_inplace_alter_table
- ha_innobase::commit_inplace_alter_table
- commit_inplace_alter_table_impl
- log_ddl->write_drop_log: commit 완료 후 log drop (3번에서 쓰던 로그 같음)
- dd_commit_inplace_alter_table: data dictionary 업데이트
그동안 DBA들이 DDL 처리할 때 개발자 분들과 얘기하던 내용들이 조금은 이해되시길 바라며
DBA들의 이야기를 위 내용과 매칭해보면,,,,
- 이 테이블은 사이즈가 커서 좀 오래 걸릴 것 같아요 -> 2번에서 오래걸려서 그렇습니다
- 작업하는 동안 쓰기가 많으면 DDL이 좀 더 오래 걸릴 수 있습니다 -> 3번에서 처리할 양이 많아져서 그렇습니다
- 테이블 작업 마지막에 metadata lock을 걸 때 잠시 테이블 락이 있을 수 있습니다 -> 4번,5번에서 메타데이터락을 잡습니다
metadata lock 관련해서 부연설명을 하자면 ,,,,
위에서 metadata lock을 처음에 한번, 마지막에 한번 잡는데 종류와 동작 방식이 다릅니다.
- 첫번째 metadata lock은 MDL_SHARED_UPGRADABLE 입니다. 이것은 작업 테이블에 다른 DDL이 들어오지 않도록만 할 뿐 DML,select 는 가능합니다
table->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE);
(gdb) p *mdl_ticket
$3 = {<MDL_wait_for_subgraph> = {_vptr.MDL_wait_for_subgraph = 0x82fe7e0 <vtable for MDL_ticket+16>, static DEADLOCK_WEIGHT_CO = 0, static DEADLOCK_WEIGHT_DML = 25,
static DEADLOCK_WEIGHT_ULL = 50, static DEADLOCK_WEIGHT_DDL = 100}, next_in_context = 0xfffe54118710, prev_in_context = 0xfffe54118898, next_in_lock = 0x0,
prev_in_lock = 0xfffe540fe998, m_type = MDL_SHARED_UPGRADABLE, m_duration = MDL_TRANSACTION, m_ctx = 0xfffe54001130, m_lock = 0xfffe540fe780, m_is_fast_path = false,
m_hton_notified = false, m_psi = 0xffff87a41100}
- 마지막에 잡는 metadata lock은 EXCLUSIVE입니다. 모든 쿼리를 block 하는데 그동안 재빨리 바꿔치기를 하기 위함으로, DDL 마지막에 쿼리가 실패할 수 있다고 말하게 되는 범인입니다
#1 0x0000000003a080c0 in MDL_context::upgrade_shared_lock (this=0xfffe54001130, mdl_ticket=0xfffe54106170, new_type=MDL_EXCLUSIVE, lock_wait_timeout=60)
at /mysql_source/mysql-8.0.32/sql/mdl.cc:3756
mdl_new_lock_request = {type = MDL_EXCLUSIVE, duration = MDL_TRANSACTION, next_in_list = 0x0, prev_in_list = 0x0, ticket = 0x0, key = {m_length = 19, m_db_name_length = 4,
m_object_name_length = 12, m_ptr = "\004test\000instant_test", '\000' <repeats 368 times>, static m_namespace_to_wait_state_name = {{m_key = 102,
MDL_context::upgrade_shared_lock (this=0xfffe54001130, mdl_ticket=0xfffe54106170, new_type=MDL_EXCLUSIVE, lock_wait_timeout=60) at /mysql_source/mysql-8.0.32/sql/mdl.cc:3758
3758 is_new_ticket = !has_lock(mdl_svp, mdl_new_lock_request.ticket);
여기까지 기존에 활용되던 inplace 방식의 DDL을 살펴보았습니다.
사실 inplace의 의미를 곰곰이 생각해보면 Inplace 라면 테이블 교체없이 이뤄져야 하는 거 아니냐 싶은데요
위 과정을 좀 더 자세하게 살펴보면, 아래에서 볼 수 있듯이 기존 테이블인 0xfffe58ab2ce0 가 old_table이 되고 altered_table인 0xfffe58ac87c0 가 new_table이 되는 것을 볼 수 있습니다.
altered table 인 0xfffe58ac87c0 은 ‘test.#sql-1285_7’ 라는 이름으로 생성된 것 또한 확인할 수 있습니다.
17404 if (use_inplace) {
17405 if (mysql_inplace_alter_table(thd, *schema, *new_schema, old_table_def,
17406 table_def, table_list, table, altered_table,
17407 &ha_alter_info, inplace_supported,
17408 &alter_ctx, columns, fk_key_info,
17409 fk_key_count, &fk_invalidator)) {
(gdb) p table
$2 = (TABLE *) 0xfffe58ab2ce0
(gdb) p altered_table
$3 = (TABLE *) 0xfffe58ac87c0 ======> 기존 테이블과 altered table이 다름
altered_table = 0xfffe58ac87c0
use_inplace = true
inplace_supported = HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE
#0 row_create_table_for_mysql (table=@0xfffe58ac7f08: 0xfffe5817a128, compression=0x0, create_info=0xffff8714ec68, trx=0xffff8d7b4ff8, heap=0x0)
at /mysql_source/mysql-8.0.32/storage/innobase/row/row0mysql.cc:2768
#1 0x0000000004af20c4 in prepare_inplace_alter_table_dict<dd::Table> (ha_alter_info=0xffff8714c280, altered_table=0xfffe58ac87c0,
old_table=0xfffe58ab2ce0, old_dd_tab=0xfffe58ab42e0, new_dd_tab=0xfffe5817ac60, table_name=0xfffe58154a75 "instant_test", flags=33, flags2=16,
fts_doc_id_col=18446744073709551615, add_fts_doc_id=false, add_fts_doc_id_idx=false)
at /mysql_source/mysql-8.0.32/storage/innobase/handler/handler0alter.cc:4802
#2 0x0000000004b03d88 in ha_innobase::prepare_inplace_alter_table_impl<dd::Table> (this=0xfffe58abf560, altered_table=0xfffe58ac87c0,
ha_alter_info=0xffff8714c280, old_dd_tab=0xfffe58ab42e0, new_dd_tab=0xfffe5817ac60)
at /mysql_source/mysql-8.0.32/storage/innobase/handler/handler0alter.cc:6047
#3 0x0000000004ade7e4 in ha_innobase::prepare_inplace_alter_table (this=0xfffe58abf560, altered_table=0xfffe58ac87c0, ha_alter_info=0xffff8714c280,
old_dd_tab=0xfffe58ab42e0, new_dd_tab=0xfffe5817ac60) at /mysql_source/mysql-8.0.32/storage/innobase/handler/handler0alter.cc:1446
#4 0x00000000037bc924 in handler::ha_prepare_inplace_alter_table (this=0xfffe58abf560, altered_table=0xfffe58ac87c0, ha_alter_info=0xffff8714c280,
old_table_def=0xfffe58ab42e0, new_table_def=0xfffe5817ac60) at /mysql_source/mysql-8.0.32/sql/handler.cc:4846
#5 0x00000000034e14bc in mysql_inplace_alter_table (thd=0xfffe58000da0, schema=..., new_schema=..., table_def=0xfffe58ab42e0,
altered_table_def=0xfffe5817ac60, table_list=0xfffe58ac1a88, table=0xfffe58ab2ce0, altered_table=0xfffe58ac87c0, ha_alter_info=0xffff8714c280,
inplace_supported=HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE, alter_ctx=0xffff8714d170, columns=std::set with 0 elements,
fk_key_info=0xfffe58ac7350, fk_key_count=0, fk_invalidator=0xffff8714d0a8) at /mysql_source/mysql-8.0.32/sql/sql_table.cc:13408
T@7: dd_table_share.cc: | | | | | | | <open_table_def
T@7: table.cc: 2880: | | | | | | | >open_table_from_share
T@7: table.cc: 2881: | | | | | | | | enter: name: 'test.#sql-1285_7' form: 0xfffe58ac87c0 ======> 위에서 봤던 altered_table , 여기로 데이터를 복사한뒤 마지막에 바꿔치기를 한다
그리고 좀 더 확실하게 보기 위해 이 테이블에 대해 또 inplace ddl을 하면,,,,
위에선 altered_table 이자 new table이었던 0xfffe58ac87c0 테이블이 이제는 old_table이 된것을 볼 수 있습니다
(gdb) p table
$5 = (TABLE *) 0xfffe58ac87c0
(gdb) p altered_table
$6 = (TABLE *) 0xfffe58a9ecd0
(gdb) bt
#0 mysql_inplace_alter_table (thd=0xfffe58000da0, schema=..., new_schema=..., table_def=0xfffe58ae3780, altered_table_def=0xfffe58ac9430,
table_list=0xfffe58c5d3e8, table=0xfffe58ac87c0, altered_table=0xfffe58a9ecd0, ha_alter_info=0xffff8714c280,
inplace_supported=HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE, alter_ctx=0xffff8714d170, columns=std::set with 0 elements,
fk_key_info=0xfffe58c601d8, fk_key_count=0, fk_invalidator=0xffff8714d0a8) at /mysql_source/mysql-8.0.32/sql/sql_table.cc:13208
#0 row_create_table_for_mysql (table=@0xfffe58c3c018: 0xfffe5817aa18, compression=0x0, create_info=0xffff8714ec68, trx=0xffff8d7b4ff8, heap=0x0)
at /mysql_source/mysql-8.0.32/storage/innobase/row/row0mysql.cc:2734
#1 0x0000000004af20c4 in prepare_inplace_alter_table_dict<dd::Table> (ha_alter_info=0xffff8714c280, altered_table=0xfffe58a9ecd0,
old_table=0xfffe58ac87c0, old_dd_tab=0xfffe58ae3780, new_dd_tab=0xfffe58ac9430, table_name=0xfffe58154a75 "instant_test", flags=33, flags2=16,
fts_doc_id_col=18446744073709551615, add_fts_doc_id=false, add_fts_doc_id_idx=false)
at /mysql_source/mysql-8.0.32/storage/innobase/handler/handler0alter.cc:4802
#2 0x0000000004b03d88 in ha_innobase::prepare_inplace_alter_table_impl<dd::Table> (this=0xfffe58c60dd0, altered_table=0xfffe58a9ecd0,
ha_alter_info=0xffff8714c280, old_dd_tab=0xfffe58ae3780, new_dd_tab=0xfffe58ac9430)
at /mysql_source/mysql-8.0.32/storage/innobase/handler/handler0alter.cc:6047
#3 0x0000000004ade7e4 in ha_innobase::prepare_inplace_alter_table (this=0xfffe58c60dd0, altered_table=0xfffe58a9ecd0, ha_alter_info=0xffff8714c280,
old_dd_tab=0xfffe58ae3780, new_dd_tab=0xfffe58ac9430) at /mysql_source/mysql-8.0.32/storage/innobase/handler/handler0alter.cc:1446
#4 0x00000000037bc924 in handler::ha_prepare_inplace_alter_table (this=0xfffe58c60dd0, altered_table=0xfffe58a9ecd0, ha_alter_info=0xffff8714c280,
old_table_def=0xfffe58ae3780, new_table_def=0xfffe58ac9430) at /mysql_source/mysql-8.0.32/sql/handler.cc:4846
#5 0x00000000034e14bc in mysql_inplace_alter_table (thd=0xfffe58000da0, schema=..., new_schema=..., table_def=0xfffe58ae3780,
altered_table_def=0xfffe58ac9430, table_list=0xfffe58c5d3e8, table=0xfffe58ac87c0, altered_table=0xfffe58a9ecd0, ha_alter_info=0xffff8714c280,
inplace_supported=HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE, alter_ctx=0xffff8714d170, columns=std::set with 0 elements,
fk_key_info=0xfffe58c601d8, fk_key_count=0, fk_invalidator=0xffff8714d0a8) at /mysql_source/mysql-8.0.32/sql/sql_table.cc:13408
#6 0x00000000034ec82c in mysql_alter_table (thd=0xfffe58000da0, new_db=0xfffe58c5da38 "test", new_name=0x0, create_info=0xffff8714ec68,
table_list=0xfffe58c5d3e8, alter_info=0xffff8714eb00) at /mysql_source/mysql-8.0.32/sql/sql_table.cc:17405
T@7: dd_table_share.cc: | | | | | | | <open_table_def
T@7: table.cc: 2880: | | | | | | | >open_table_from_share
T@7: table.cc: 2881: | | | | | | | | enter: name: 'test.#sql-1285_7' form: 0xfffe58a9ecd0
algorithm=instant
instant 방식은 훨씬 간단합니다. (방식은 간단한데, 디버깅은 더 어려운 것 같습니다)
inplace 처럼 복잡한 과정 없이 INSTANT로 실행될 수 있는 DDL인지 확인 후 메타데이터 변경만 하기 때문에 기존데이터 복사 등의 과정이 없습니다.
(간단하지만 metadata 쪽 처리 관련해서 뭔가 큰것이 있는 것 같은데 내용이 너무 많아 생략했습니다)
--------------------------- 1번
mysql_alter_table
open_and_process_table
open_table
MDL_context::acquire_lock
open_table_get_mdl_lock
check_if_supported_inplace_alter :
mysql_inplace_alter_table
THD_STAGE_INFO(thd, stage_alter_inplace_prepare)
handler::ha_prepare_inplace_alter_table
ha_innobase::prepare_inplace_alter_table
ha_innobase::prepare_inplace_alter_table_impl
if (...is_instant(ha_alter_info)) : instant algorithm이라면 exit
THD_STAGE_INFO(thd, stage_alter_inplace)
handler::ha_inplace_alter_table
ha_innobase::inplace_alter_table
ha_innobase::inplace_alter_table_impl
if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA) ||is_instant(ha_alter_info)) : instant algorithm이라면 exit
--------------------------- 2번
wait_while_table_is_used
MDL_context::upgrade_shared_lock
MDL_context::acquire_lock
THD_STAGE_INFO(thd, stage_alter_inplace_commit)
handler::ha_commit_inplace_alter_table
ha_innobase::commit_inplace_alter_table
commit_inplace_alter_table_impl
if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA) ||is_instant(ha_alter_info)) : instant algorithm이라면 exit
dd_commit_inplace_instant // instance ddl
switch (type) //
dd_copy_private(*new_dd_tab, *old_dd_tab);
dd_commit_instant_table
dd_copy_table_columns
dd_add_instant_columns
dd_update_v_cols
inplace 방식과 비교하면 훨씬 간단해진 것을 확인할 수 있습니다.
특히 기존 데이터 복제, DDL 중 들어온 로그에 대해 sync하는 부분들이 모두 없어졌습니다.
아래처럼 instant 로 풀릴 수 있는 DDL인지를 확인한 뒤
1028 Instant_Type instant_type = innobase_support_instant(
1029 ha_alter_info, m_prebuilt->table, this->table, altered_table);
1030
1031 ha_alter_info->handler_trivial_ctx =
1032 instant_type_to_int(Instant_Type::INSTANT_IMPOSSIBLE);
1033
1034 if (!dict_table_is_partition(m_prebuilt->table)) {
1035 switch (instant_type) {
1036 case Instant_Type::INSTANT_IMPOSSIBLE:
(gdb) p alter_info->requested_algorithm
$2 = Alter_info::ALTER_TABLE_ALGORITHM_INSTANT
(gdb) p instant_type
$30 = INSTANT_ADD_DROP_COLUMN
중간 중간 아래와 같은 로직을 거치면서
inplace 에서는 해야할 처리들(기존 데이터 복제, DDL 중 들어온 데이터 싱크) 을 안하고 바로 ok_exit 해버리면서 과정이 훨씬 간소화되었습니다
template < typename Table>
bool ha_innobase::inplace_alter_table_impl(TABLE *altered_table,
Alter_inplace_info *ha_alter_info,
const Table *old_dd_tab,
표 *new_dd_tab) {
// ...
if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA) ||
is_instant (ha_alter_info)) {
ok_exit :
DEBUG_SYNC(m_user_thd, "innodb_after_inplace_alter_table" );
DBUG_RETURN ( false );
}
다만 한가지 꼭 유의할 점은, 위 1번 과정 중 check_if_supported_inplace_alter를 자세하게 확인해보면
#1 0x0000000004add930 in ha_innobase::check_if_supported_inplace_alter (this=0xfffe3ca9f260, altered_table=0xfffe3cb124c0,
ha_alter_info=0xffff682c8280) at /mysql_source/mysql-8.0.32/storage/innobase/handler/handler0alter.cc:1053
_db_trace = {m_stack_frame = {
func = 0x614fbbd "mysql_alter_table(THD*, const char*, const char*, HA_CREATE_INFO*, Table_ref*, Alter_info*)", func_len = 17,
file = 0x614ae90 "/mysql_source/mysql-8.0.32/sql/sql_table.cc", level = 2147483655, prev = 0xffff682ca168}}
__PRETTY_FUNCTION__ = "virtual enum_alter_inplace_result ha_innobase::check_if_supported_inplace_alter(TABLE*, Alter_inplace_info*)"
old_encryption = 0xfffe3c117990 "N"
new_encryption = 0xfffe3cb153b0 "N"
instant_type = INSTANT_ADD_DROP_COLUMN
add_drop_v_cols = false
online = false
cf_it = {<base_list_iterator> = {list = 0x48f0bac <native_rw_unlock(native_rw_lock_t*)+20>, el = 0xffff682cabe0, prev = 0xffff682c80e0,
current = 0xffff682c80a0}, <No data fields>}
.
.
.
/* INSTANT can't be done any more. Fall back to INPLACE. */
break;
} else if (!is_valid_row_version(
m_prebuilt->table->current_row_version + 1)) {
ut_ad(is_valid_row_version(m_prebuilt->table->current_row_version));
if (ha_alter_info->alter_info->requested_algorithm ==
Alter_info::ALTER_TABLE_ALGORITHM_INSTANT) {
my_error(ER_INNODB_MAX_ROW_VERSION, MYF(0),
m_prebuilt->table->name.m_name);
return HA_ALTER_ERROR;
}
is_valid_row_version 를 체크한다는 것인데
/* INSTANT can’t be done any more. Fall back to INPLACE. */ 구문이 얘기하는 것처럼
instant ddl 은 한 테이블에 대해 최대 64번까지 수행될 수 있고,
64번을 초과한 경우엔 위에서 본 것 처럼 INSTANT로 수행할 수 없습니다.
그리고 이 ROW_VERSION 64번을 체크하는 부분이 아래의 is_valid_row_version 함수입니다.
#0 is_valid_row_version (version=65 'A') at /mysql_source/mysql-8.0.32/storage/innobase/include/dict0mem.h:429
static inline bool is_valid_row_version(const uint8_t version) {
/* NOTE : 0 is also a valid row versions for rows which are inserted after
upgrading from earlier INSTANT implemenation */
if (version <= MAX_ROW_VERSION) {
return true;
}
return false;
(gdb) p version
$1 = 65
(gdb) p MAX_ROW_VERSION
$1 = 64 '@'
(gdb) s
433 return false;
mysql> ALTER TABLE instant_test ADD COLUMN t64 VARCHAR(10), ALGORITHM=INSTANT;
Query OK, 0 rows affected (0.22 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT NAME, TOTAL_ROW_VERSIONS
-> FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE 'test/instant_test';
+-------------------+--------------------+
| NAME | TOTAL_ROW_VERSIONS |
+-------------------+--------------------+
| test/instant_test | 64 |
+-------------------+--------------------+
1 row in set (0.03 sec)
mysql> ALTER TABLE instant_test ADD COLUMN t65 VARCHAR(10), ALGORITHM=INSTANT;
ERROR 4092 (HY000): Maximum row versions reached for table test/instant_test. No more columns can be added or dropped instantly. Please use COPY/INPLACE.
위와 같이 64번까지 instant ddl 을 한 뒤 ,65 번째 DDL에서는 실패하게 됩니다
단, 이때는 일부러 rebuild를 유발하는 algorithm=inplace DDL을 수행하면 ROW_VERSION 이 초기화가 되어 다시 INSTANT DDL을 64번까지 수행할 수 있습니다
mysql> alter table instant_test add column t65 varchar(10),algorithm=inplace;
Query OK, 0 rows affected (0.22 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT NAME, TOTAL_ROW_VERSIONS FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE 'test/instant_test';
+-------------------+--------------------+
| NAME | TOTAL_ROW_VERSIONS |
+-------------------+--------------------+
| test/instant_test | 0 |
+-------------------+--------------------+
1 row in set (0.19 sec)
초기화 된 이후 또 instant ddl 을 수행해보면, 이때는 is_valid_row_version 을 통과한 것을 확인할 수 있습니다
mysql> alter table instant_test add column t66 varchar(10),algorithm=instant;
Breakpoint 1, is_valid_row_version (version=1 '\001') at /mysql_source/mysql-8.0.32/storage/innobase/include/dict0mem.h:429
429 if (version <= MAX_ROW_VERSION) {
(gdb) p version
$1 = 1 '\001'
(gdb) s
430 return true;
보너스
handler0alter.cc 를 읽다보면 instant ddl 에 대해서 더 많이 알 수 있는데요
잘 모르지만 눈치껏 코드를 쭉 보면서 번외로 재밌는 내용들을 몇가지 가져와봤습니다.
- 현재 algorithm=instant로 지원하는 DDL
enum class INSTANT_OPERATION {
COLUMN_RENAME_ONLY, /*!< Only column RENAME */
VIRTUAL_ADD_DROP_ONLY, /*!< Only virtual column ADD AND DROP */
VIRTUAL_ADD_DROP_WITH_RENAME, /*!< Virtual column ADD/DROP with RENAME */
INSTANT_ADD, /*< INSTANT ADD possibly with virtual column ADD and
column RENAME */
INSTANT_DROP, /*|< INSTANT DROP possibly with virtual column ADD/DROP and
column RENAME */
NONE
};
- 무서운 fall back 로직 (중요!)
- instant 가 안되면 바로 inplace로 풀리도록 Fallback 로직이 있어서 주의해야합니다
- 이러한 inplace fallback을 원하지 않고 DDL이 instant 알고리즘으로 풀릴 수 없다면 바로 실패하길 원한다면 항상 DDL구문 뒤에 ,algorithm = ? 을 붙여주는 것이 건강에 이롭습니다
- ex) alter table tb_test add column tt int , algorithm=instant;
- 참고로 inplace가 안되는 것은 COPY로 fallback을 하는 로직이 있습니다.
/* INSTANT can't be done any more. Fall back to INPLACE. */
break;
} else if (!Instant_ddl_impl<dd::Table>::is_instant_add_drop_possible(
ha_alter_info, table, altered_table, m_prebuilt->table)) {
if (ha_alter_info->alter_info->requested_algorithm ==
Alter_info::ALTER_TABLE_ALGORITHM_INSTANT) {
/* Return error if either max possible row size already crosses max
permissible row size or may cross it after add. */
my_error(ER_INNODB_INSTANT_ADD_DROP_NOT_SUPPORTED_MAX_SIZE, MYF(0));
return HA_ALTER_ERROR;
}
- 일별 파티셔닝하는 테이블은 instant ddl 도 조심해서 수행해야합니다 (중요!)
- 아래와 같이 테이블의 파티션 여부를 체크하는데 아무리 instant여도 파티션이 많으면 이 부분에서 또 많은 시간을 소모합니다.
- 경험상 파티션이 천개 이상 쯤 되면 아래부분에서 몇초~수십초는 소요하는데 그 과정에서 쿼리가 모두 metadata lock으로 block되어 실패하게 됩니다.
dict_table_is_partition (table=0xfffe7ca72178) at /mysql_source/mysql-8.0.32/storage/innobase/include/dict0dict.ic:1383
1383 return (dict_name::is_partition(table->name.m_name));
(gdb)
dict_name::is_partition (dict_name="test/instant_test") at /mysql_source/mysql-8.0.32/storage/innobase/dict/dict0dd.cc:7284
7284 return check_partition(dict_name, false, position);
dict_name::check_partition (dict_name="test/instant_test", sub_part=false, position=@0xffff9834afd8: 281473235333136) at /mysql_source/mysql-8.0.32/storage/innobase/dict/dict0dd.cc:7200
7200 position = dict_name.find(part_sep);
dict_name::check_partition (dict_name="test/instant_test", sub_part=false, position=@0xffff9834afd8: 18446744073709551615) at /mysql_source/mysql-8.0.32/storage/innobase/dict/dict0dd.cc:7210
7210 position = dict_name.find(alt_sep);
- Fulltext index 를 사용하는 테이블은 instant DDL이 안됩니다!
- 사실 instant 뿐만 아니라 inplace도 안됩니다
- 이유는 fulltext index를 사용하는 테이블들은 FTS_DOC_ID 라는 히든 필드를 사용하는 것으로 보이는데, 이 동작 관련해서 아직 inplace, instasnt algorithm을 지원하지 않는것으로 보입니다. (뇌피셜)
static bool innobase_fulltext_exist(const TABLE *table) {
for (uint i = 0; i < table->s->keys; i++) {
if (table->key_info[i].flags & HA_FULLTEXT) {
return (true);
}
}
return (false);
}
if (key->flags & HA_FULLTEXT) {
assert(!(key->flags & HA_KEYFLAG_MASK &
~(HA_FULLTEXT | HA_PACK_KEY | HA_GENERATED_KEY |
HA_BINARY_PACK_KEY)));
ha_alter_info->unsupported_reason =
innobase_get_err_msg(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS);
# 정리
- mysql8 (aurora 3 ) 버전 들어오면서 DDL을 처리할 때 기존의 inplace 방식이 아닌 instant alogrithm이 추가 되었다
- instasnt 는 기존 inplace 와 달리 데이터 복제, 반영 중 들어온 로그 반영의 과정 없이 metadata 만 수정해버려서 엄청 빠르다
- 단 이렇게 엄청 좋은 instsant 도 아직은 컬럼 추가 쪽 밖에 지원하지 않고
- 한 테이블에 대해 최대 64번만 지원된다는 제약이 있으며 ( table rebuild로 초기화 가능)
- 파티션 테이블, 풀텍스트 인덱스를 사용하면 주의해야 한다
- 혹여나 작업이 잘못되어 instant 가 inplace 로 fallback 되지 않도록 alter table … , algorithm= ? ,lock=none 구문을 생활화하자