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 |