Online DDL 모니터링
MySQL 클라이언트에서 Online DDL로 인덱스를 추가하다보면 작업이 생각외로 꽤 오랜 시간동안(상대적이지만) 수행되는 경우가 많다. 서비스 운영 중에는 언제 끝날지 모르는 인덱스 추가 작업으로 인해 심장이 덜컥 내려앉을 때도 있다.
다행히 MySQL 서버에서는 Online DDL 진행 상황을 모니터링 할 수 있는 기능을 제공한다. 아래 명령을 별도의 세션에서 수행하면 Online DDL 작업이 어느 정도까지 진행되었는지 확인할 수 있다.
(ref. https://dev.mysql.com/doc/refman/8.0/en/monitor-alter-table-performance-schema.html)
mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM performance_schema.events_stages_current; +------------------------------------------------------+----------------+----------------+ | EVENT_NAME | WORK_COMPLETED | WORK_ESTIMATED | +------------------------------------------------------+----------------+----------------+ | stage/innodb/alter table (read PK and internal sort) | 280 | 1245 | +------------------------------------------------------+----------------+----------------+ 1 row in set (0.01 sec)
하지만 Online DDL 작업과 동시에 한 화면에서 모니터링도 할 수 있는 것은 아니기 때문에 항상 다른 세션으로 접속하여 모니터링 해야 한다. 보통 끝나겠거니 생각하는 경우가 많고, 매번 진행 상황을 모니터링 하지는 않기 때문에 필요한 기능이면서도 사실상 잘 사용하지 않는 기능이기도 하다.
그래서 이 좋은 기능을 Online DDL 작업과 함께 한 눈에 확인할 수 있도록 MySQL 클라이언트에 일부 기능을 추가해보았다. 소스 코드는 간단하기 때문에 본 글의 설명 또한 간단하게 설명하였다.
<sql-common/client.cc>
코드 수정 및 추가 파일
https://github.com/godtechwak/mysql-server-custom/blob/8.0/sql-common/client.cc
코드 수정 및 추가 라인
Online DDL Progress 기능 추가
MySQL 클라이언트를 디버깅해보면 쿼리문을 수행할 때,
main() --> read_and_execute() --> readline() --> com_go() --> mysql_real_query_for_lazy() --> mysql_real_query() --> mysql_send_query() 순으로 함수가 실행되는 것을 확인할 수 있다.
Online DDL Progress 기능은 이 함수들 중에서 mysql_real_query() 함수가 정의되어 있는 sql-common/client.cc 파일에 추가해주면 된다.
아래 코드는 수정된 mysql_real_query() 함수이며, ALTER TABLE 명령이 수행될 때만 monitorDDLProgress() 함수를 실행한다.
int STDCALL mysql_real_query(MYSQL *mysql, const char *query, ulong length) { int retval; DBUG_TRACE; DBUG_PRINT("enter", ("handle: %p", mysql)); DBUG_PRINT("query", ("Query = '%-.*s'", (int)length, query)); DBUG_EXECUTE_IF("inject_ER_NET_READ_INTERRUPTED", { mysql->net.last_errno = ER_NET_READ_INTERRUPTED; DBUG_SET("-d,inject_ER_NET_READ_INTERRUPTED"); return 1; }); //by silver ConnInfoSub connInfoSub = { mysql->host, mysql->user, mysql->passwd, mysql->db, mysql->port, mysql->unix_socket, mysql->client_flag }; //ALTER TABLE문을 수행하는 경우에만 체크 by silver if (strcasestr(query, "alter table")) { std::thread queryThread(monitorDDLProgress, connInfoSub); if (mysql_send_query(mysql, query, length)) return 1; queryThread.join(); } else { if (mysql_send_query(mysql, query, length)) return 1; } retval = (int)(*mysql->methods->read_query_result)(mysql); mysql_extension_bind_free(MYSQL_EXTENSION_PTR(mysql)); return retval; }
아래는 monitorDDLProgress() 함수 정의이며, 이 함수는 Online DDL이 수행됨과 동시에 진행 상황을 MySQL 클라이언트에 출력한다.
//by silver void monitorDDLProgress(const ConnInfoSub& connInfoSub) { MYSQL *mysql = mysql_init(NULL); char ddl_percentage[10] = ""; char old_ddl_percentage[10] = ""; //시작 시간 std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); mysql_real_connect(mysql, connInfoSub.host, connInfoSub.user, connInfoSub.passwd, connInfoSub.db, connInfoSub.port, connInfoSub.unix_socket, connInfoSub.client_flag); while (true) { sleep(5); //DDL 수행을 보장하기 위해서 최소 5초간 대기한다. MYSQL_RES *result = nullptr; char cmd1[] = "SELECT ROUND(WORK_COMPLETED/WORK_ESTIMATED*100, 2) AS percentage FROM performance_schema.events_stages_current ORDER BY WORK_COMPLETED DESC LIMIT 1;"; mysql_query(mysql, cmd1); result = mysql_use_result(mysql); MYSQL_ROW row = mysql_fetch_row(result); if (row == NULL) { puts("DDL is done...\n"); mysql_free_result(result); mysql_thread_end(); //스레드 종료 break; } strcpy(ddl_percentage, row[0]); std::chrono::duration<double> elapsed_time = std::chrono::steady_clock::now() - start_time; int total_seconds = static_cast<int>(elapsed_time.count()); int milliseconds = static_cast<int>((elapsed_time.count() - total_seconds) * 1000); int minutes = total_seconds / 60; int seconds = total_seconds % 60; if (strcmp(ddl_percentage, old_ddl_percentage) != 0) { printf("Online DDL Progress: %s(%%) (%dmin %dsec %dms)\n", ddl_percentage, minutes, seconds, milliseconds); } else { printf("Online DDL Progress: 99.99(%%)..almost done (%dmin %dsec %dms)\n", minutes, seconds, milliseconds); } strcpy(old_ddl_percentage, ddl_percentage); mysql_free_result(result); } mysql_close(mysql); }
인덱스 추가하여 Online DDL 진행 상황 모니터링
1. 약 1GB 크기의 테이블을 준비한다.
mysql> SELECT A.table_name, CASE WHEN LENGTH(A.index_data_size) > 3 AND LENGTH(A.index_data_size) < 7 THEN CONCAT(ROUND(A.index_data_size/1024, 2), '(KB)') WHEN LENGTH(A.index_data_size) > 6 AND LENGTH(A.index_data_size) < 10 THEN CONCAT(ROUND(A.index_data_size/1024/1024, 2), '(MB)') WHEN LENGTH(A.index_data_size) > 9 THEN CONCAT(ROUND(A.index_data_size/1024/1024/1024, 2), '(GB)') ELSE A.index_data_size END AS index_data_size, concat(case when 10 - char_length(repeat('+', (A.index_data_size/total_size*100)/10)) != 0 then concat(repeat('+', (A.index_data_size/total_size*100)/10), repeat('_', (10 - char_length(repeat('+', (A.index_data_size/total_size*100)/10))))) else repeat('+', (A.index_data_size/total_size*100)/10) end, '(', round((A.index_data_size/total_size*100), 1), '%)') as 'Percentage(%)', A.table_rows FROM( SELECT table_name, table_schema, SUM(index_length + data_length) AS index_data_size, MAX(table_rows) AS table_rows, (SELECT SUM(index_length + data_length) FROM information_schema.tables WHERE table_schema = 'test' ) AS total_size FROM information_schema.tables WHERE table_name = 'test' GROUP BY table_name ORDER BY index_data_size) AS A +------------+-----------------+-------------------+------------+ | table_name | index_data_size | Percentage(%) | table_rows | +------------+-----------------+-------------------+------------+ | test | 0.96(GB) | +++++_____(54.7%) | 16045362 | +------------+-----------------+-------------------+------------+
2. 인덱스를 추가한다.
mysql> ALTER TABLE test ADD INDEX ix_userid(user_id), ALGORITHM=inplace, LOCK=none;
3. Online DDL 진행 상황을 모니터링 한다.


추가적으로 필요한 기능이 있다면(sleep 5초를 변경한다던지, 출력 포맷을 변경한다던지) client.cc 파일의 관련 부분을 수정하여 사용하면 되겠다. 한 가지 주의해야할 점이 있다면, make 명령은 /mysql-server 디렉터리에서 수행하지 않고, /mysql-server/client 디렉터리에서 수행해야 한다. 만일 /mysql-server에서 make 명령을 수행하면 아래와 같은 오류를 맞이하게 될 것이다.
Consolidate compiler generated dependencies of target sql_main
[ 43%] Building CXX object sql/CMakeFiles/sql_main.dir/__/sql-common/client.cc.o
[ 43%] Linking CXX static library ../archive_output_directory/libsql_main.a
[ 50%] Built target sql_main
[ 53%] Built target innobase
[ 53%] Built target minchassis
[ 53%] Linking CXX shared library library_output_directory/libserver_unittest_library.dylib
Undefined symbols for architecture arm64:
"_mysql_query", referenced from:
monitorDDLProgress(ConnInfoSub const&) in libsql_main.a(client.cc.o)
"_mysql_thread_end", referenced from:
monitorDDLProgress(ConnInfoSub const&) in libsql_main.a(client.cc.o)
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [library_output_directory/libserver_unittest_library.dylib] Error 1
make[1]: *** [CMakeFiles/server_unittest_library.dir/all] Error 2
make: *** [all] Error 2
mysql 클라이언트 프로그램은 실행 파일이 /mysql-server/client 디렉터리에서 만들어지기 때문에 빌드는 /mysql-server/client에서만 수행해주면 된다.
'MySQL' 카테고리의 다른 글
[MySQL] pt-osc를 활용한 online 테이블 파티션 변경 (1) | 2023.10.31 |
---|---|
MySQL Aborted connection error (1) | 2023.10.31 |
Aurora MySQL 3.05.0 스토리지 확장 테스트 (1) | 2023.10.29 |
MySQL 버그 리포트 찾는 방법(MySQL 8.0 Default Collation) (2) | 2023.10.28 |
MySQL RAND 함수의 난수 생성 원리 (0) | 2023.08.20 |