2021년 1월 30일 토요일

MySQL 8.0.21 소스 컴파일로 설치하기 / CentOS 7

MySQL 8.0.21 소스를 컴파일 하여 설치하는 방법에 대해 알아보자.

설치 환경
CentOS 7.6(64 bit)

선행 작업
1. 유저 및 그룹 생성

# 그룹 생성
groupadd mysql

# 유저 생성
# options means
# -M: 홈 디렉토리를 생성하지 않는다.
# -s: 해당 유저로 로그인 할 수 없게 한다.
useradd -M -s /sbin/nologin -g mysql mysql

2. 패키지 설치

2-1. boost 1.72.0

# MySQL 8.0.21 컴파일 하기 위해서 boost 1.72.0 버전이 필요하다.
cd /opt & wget https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.gz
tar -xvf boost_1_72_0.tar.gz


2-2. gcc

# MySQL 8.0.21 컴파일 하기 위해서 gcc 5.3 이상의 버전이 필요하다.
# CentOS 7에서는 gcc 7을 간단하게 설치할 수 있다. yum -y install centos-release-scl make yum -y install devtoolset-7-gcc* scl enable devtoolset-7 bash

2-3. cmake 3.15.1
# MySQL 8.0.21 컴파일 하기 위해서 cmake 3.15.1 이상의 버전이 필요하다.
# cmake 설치하기 위해서는 make 패키지가 필요하다.
yum install -y make

cd /opt & wget https://github.com/Kitware/CMake/releases/download/v3.15.1/cmake-3.15.1.tar.gz
tar xvfz cmake-3.15.1.tar.gz
cd cmake-3.15.1
./bootstrap 
make
make install 


2-4. etc

# 그 외에 필요한 패키지 설치
yum install -y ncurses-devel openssl-devel cyrus-sasl-plain openldap-devel libtirpc-devel bison git


MySQL 설치

# MySQL 8.0.21 다운로드
wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.21.tar.gz
tar -xvf mysql-8.0.21.tar.gz
cd mysql-8.0.21
# cmake 실행 cmake \ -DCMAKE_INSTALL_PREFIX=/mysql\ -DMYSQL_DATADIR=/mysql/data \ -DSYSCONFDIR=/mysql/etc \ -DINSTALL_SBINDIR=/mysql/bin \ -DINSTALL_BINDIR=/mysql/bin \ -DINSTALL_SCRIPTDIR=/mysql/script \ -DMYSQL_USER=mysql \ -DMYSQL_GROUP=mysql \ -DWITH_INNOBASE_STORAGE_ENGINE=1 \ -DWITH_PARTITION_STORAGE_ENGINE=1 \ -DWITH_FEDERATED_STORAGE_ENGINE=1 \ -DWITH_BLACKHOLE_STORAGE_ENGINE=1 \ -DWITH_MEMORY_STORAGE_ENGINE=1 \ -DWITH_READLINE=1 \ -DMYSQL_UNIX_ADDR=/mysql/tmp/mysql.sock \ -DMYSOL_TCP_PORT=3306 \ -DENABLED_LOCAL_INFILE=1 \ -DENABLE_DOWNLOADS=1 \ -DWITH_EXTRA_CHARSETS=all \ -DDEFAULT_CHARSET=utf8mb4 \ -DDEFAULT_COLLATION=utf8mb4_general_ci \ -DWITH_DEBUG=0 \ -DMYSQL_MAINTAINER_MODE=0 \ -DDOWNLOAD_BOOST=1 \ -DWITH_BOOST=/opt/boost_1_72_0 \ -DCMAKE_C_COMPILER=/opt/rh/devtoolset-7/root/usr/bin/gcc \ -DCMAKE_CXX_COMPILER=/opt/rh/devtoolset-7/root/usr/bin/g++ \ -DFORCE_INSOURCE_BUILD=1 # 빌드 및 설치 # 빌드 하는 시간이 꽤 오래 걸린다. 2코어, 4G메모리 가상 서버에서 9시간 정도 걸렸다. make make install # data directory 초기화 /mysql/bin/mysqld --initialize --basedir=/mysql--datadir=/mysql/data --user=mysql # MySQL 기동 /mysql/bin/mysqld_safe --user=mysql & # MySQL 접속 및 버전 확인 /mysql/bin/mysql -uroot -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 8.0.21 Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>


# Refer
https://dev.mysql.com/doc/mysql-sourcebuild-excerpt/8.0/en/source-installation.html

2021년 1월 16일 토요일

MySQL 온라인 스키마 마이그레이션 툴 gh-ost에 대하여

GitHub의 OSS로 공개되어 있는 온라인 마이그레이션 툴 gh-ost에 대하여 알아보자.

먼저, MySQL 온라인 스키마 마이그레이션 이라고 하면 MySQL 5.6부터 온라인 DDL이 가능하다.
병렬로 DML이 실행되어도 락이 걸리지 않고 스키마 변경이 가능하다. 특히, MySQL 8.0부터 Instance Add Column 라고 테이블을 리빌드 하지 않고 컬럼을 추가할 수 있는 아주 좋은 기능이 추가되었다.

하지만, int -> bigint로 변경하는 ALTER 구문 등 몇 가지의 조작은 병렬 DML이 허용되지 않는다. 즉 테이블 전체가 락이 걸리게 되고 복제 지연이 발생할 가능성이 있다. 조작에 따라 Alter 중 가능한 동작이 다르므로 주의가 필요하다.
상세한 내용은 공식 사이트 참고.
https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html

gh-ost를 사용하면, 몇 가지 제약은 있지만 복제 지연이나 부하를 컨트롤 할 수 있고 병렬 DML도 허용하기 때문에 서비스 가동 중에 스키마 마이그레이션이 가능하다.

아키텍처
gh-ost는 기본적으로 아래와 같이 동작한다.
  1. 변경 하려고 하는 테이블을 복사하여 빈 테이블(고스트 테이블) 작성
  2. 고스트 테이블에 대하여 지정한 ALTER 구문 실행
  3. 기존 테이블의 데이터를 고스트 테이블에 복사
  4. 마이그레이션 실행 중, 기존 테이블의 신규 DML은 바이너리 로그로 부터 추출해서 고스트 테이블에 적용
    # 3, 4는 병렬로 가동
  5. 3, 4가 끝나면 기존 테이블과 고스트 테이블 교체
온라인 스키마 마이그레이션 툴로 유명한 Percona가 공개하고 있는 pt-online-schema-change(pt-osc) 가 있다. pt-osc와 gh-ost 기본 아키텍처는 비슷하지만, 다른점은 신규 DML을 적용하는 방법이다.
pt-osc는 트리거를 이용하지만, gh-ost는 바이너리 로그를 이용한다.

따라서, 트리거에 의해 오버헤드가 없는 만큼 gh-ost가 유연하게 부하를 억제할 수 있다.
gh-ost는 최대한 마스터의 부하를 억제하지 위해서 슬레이브를 활용하도록 설계되어 있다. 즉, 기본적으로 바이너리 로그를 습득하는 곳은 슬레이브이다.
1, 2, 3, 5는 마스터에서 실행되고 4번의 바이너리 로그 습득은 슬레이브로 부터, 추출한 DML 적용은 마스터로 실행하게 된다.
물론, 설정에 따라 바이너리 로그 습득을 마스터에서 실행할 수도 있다.

필수조건
gh-ost를 실행하기 위해서는 아래의 권한이 필요하다.
  1. ALTER, CREATE, DELETE, DROP, INDEX, INSERT, LOCK, TABLES, SELECT, TRIGGER, UPDATE 권한을 대상 데이터베이스로 작성
  2. SUPER, REPLICATION CLIENT, REPLICATION SLAVE on *.*이 필요한 경우 있음
2번의 권한은 --switch-to-rbr 옵션을 사용할 때 필요하다. 이 옵션은 슬레이브의 binlog_format이 ROW가 아닌 경우, binlog_format을 ROW로 변경하고 STOP SLAVE와 START SLAVE를 실행한다. 
binlog_format이 ROW인 경우에는 --assume-rbr 옵션을 사옹하여 STOP SLAVE, START SLAVE를 실행안하는 게 좋다.

복제 설정
gh-ost가 슬레이브에서 바이너리 로그를 습득하기 위해 필요한 MySQL 설정은 아래와 같다.
  1. log_bin
  2. log_slave_updated=ON
  3. binlog_format=ROW
  4. binlog_row_image=FULL
마스터가 binlog_format=STATEMENT or MIXED로 설정되어 있어도 문제 없다. 슬레이브만 위와 같이 설정하면 gh-ost는 실행 가능하다.
마스터와 슬레이브 간 설정이 다른 게 불안하다면, gh-ost실행 후 설정을 되돌리면 된다.

제한 항목
아래의 조건에 해당하는 게 하나라도 있으면 에러가 발생한다.
  • 대상 테이블에 Primary key 또는 NOT NULL 제약이 붙은 유니크 키가 없는 경우
  • 대상 테이블에 외래 키 제약이 있는 경우
  • 대상 테이블에 트리거가 있는 경우
  • 대상 테이블의 Primary key에 JSON 컬럼이 포함되어 있는 경우
테스트
이제부터 gh-ost를 실제로 사용해보자.

설치
# wget https://github.com/github/gh-ost/releases/download/v1.1.0/gh-ost-1.1.0-1.x86_64.rpm
# rpm -i gh-ost-1.1.0-1.x86_64.rpm

실행
CREATE TABLE `sbtest1` (
  `id` int NOT NULL AUTO_INCREMENT,
  `k` int NOT NULL DEFAULT '0',
  `c` char(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '',
  `pad` char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

sbtest1 테이블에 col1 컬럼을 추가하는 gh-ost를 실행해보자.
gh-ost \
--host=127.0.0.1 \
--port=3306 \
--user="ghost_user" \
--password="xxxxxx" \
--database="test_db" \
--alter="ALTER TABLE sbtest1 ADD col1 int;" \
--execute

--host: 슬레이브 호스트명. 마스터의 정보는 gh-ost가 내부적으로 검색한다.
--alter: 실행하려고 하는 Alter 구문
--execute: 실제로 실행. 지정하지 않으면 테스트만 실행한다.

결과
sbtest1 테이블에 col1 컬럼이 추가되어 있는 것을 확인할 수 있다.
CREATE TABLE `sbtest1` (
  `id` int NOT NULL AUTO_INCREMENT,
  `k` int NOT NULL DEFAULT '0',
  `c` char(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '',
  `pad` char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '',
  `col1` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

기존 테이블은 아래와 같이 확인할 수 있다.
mysql> show tables like '_sbtest1_del';
+-------------------------------------+
| Tables_in_sysbenchdb (_sbtest1_del) |
+-------------------------------------+
| _sbtest1_del                        |
+-------------------------------------+

기본적으로 _TABLENAME_del 과 같은 네이밍으로 변경된다. DROP은 cost 높은 처리이므로, 기존 테이블을 바로 DROP 하지 않는다. --ok-to-drop-table 옵션을 사용하면 실행 종료 타이밍에 기존 테이블을 DROP시킬 수 있다.

gh-ost 실행은 아래와 같은 순서로 하는 게 좋을 거 같다.
  1. --execute 옵션 없이 실행 후, 에러가 발생하는 지 확인
  2. --execute 옵션 적용 후, 실제로 실행

2021년 1월 10일 일요일

MySQL 8.0 옵티마이저 힌트에 대하여

MySQL 8.0 부터 추가된 옵티마이저 힌트에 대하여 알아보자.
먼저 옵티마이저 힌트를 어떻게 사용하는 지 살펴보도록 하자.

옵티마이저 힌트 구문
옵티마이저 힌트는 /*+...*/ 를 statement 안에 기술한다.
SELECT, UPDATE, DELETE 등의 DML 키워드 뒤에 힌트를 기술하면 서버가 인식하여 처리하게 된다.
mysql> SELECT /*+ hint */ ...
mysql> UPDATE /*+ hint */ ...

지정한 힌트가 유효한 지 확인하려면, EXPLAIN 후에 SHOW WARNINGS 를 실행하면 된다.
예를 들어, 아래는 NO_RANGE_OPTIMIZATION 힌트를 추가하여 실시한 결과이다. 이 힌트는 지정한 테이블 및 인덱스를 사용한 레인지 스캔을 하지 않도록 제어하였다.
힌트가 유효하면, SHOW WARNINGS 에서 사용된 힌트를 확인할 수 있다.
mysql> EXPLAIN SELECT /*+ NO_RANGE_OPTIMIZATION(t PRIMARY) */ * FROM t WHERE id BETWEEN 1 AND 10;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t     | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL | 39528 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+

mysql> SHOW WARNINGS;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                           |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select /*+ NO_RANGE_OPTIMIZATION(`t`@`select#1` `PRIMARY`) */ `d`.`t`.`id` AS `id`,`d`.`t`.`k` AS `k`,`d`.`t`.`c` AS `c`,`d`.`t`.`pad` AS `pad` from `d`.`t` where (`d`.`t`.`id` between 1 and 10) |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

무효한 힌트를 지정한 경우는 아래와 같이 표시된다.
mysql> EXPLAIN SELECT /*+ NO_RANGE_OPTIMIZATION(t idx_t) */ * FROM t WHERE id BETWEEN 1 AND 10;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t     | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |   10 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+

mysql> SHOW WARNINGS;
+---------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                                                    |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 3128 | Unresolved name `t`@`select#1` `idx_t` for NO_RANGE_OPTIMIZATION hint                                                                                      |
| Note    | 1003 | /* select#1 */ select `d`.`t`.`id` AS `id`,`d`.`t`.`k` AS `k`,`d`.`t`.`c` AS `c`,`d`.`t`.`pad` AS `pad` from `d`.`t` where (`d`.`t`.`id` between 1 and 10) |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------+

MySQL 8.0 옵티마이저 힌트
이제부터 MySQL 8.0 에서 새롭게 추가된 옵티마이저 힌트에 대하여 알아보도록 하자.

JOIN 힌트
조인의 순서를 컨트롤 할 수 있게 되었다.
  • JOIN_FIXED_ORDER
    : 강제적으로 조인하는 순서를 FROM 절의 지정 순서대로 실시하도록 한다
  • JOIN_ORDER
    : 지정한 순서대로 표를 조인하도록 옵티마이저에게 알려준다
  • JOIN_PREFIX
    : 지정한 테이블을 먼저 조인하도록 옵티마이저에게 알려준다
  • JOIN_SUFFIX
    : 지정한 테이블을 나중에 조인하도록 옵티마이저에게 알려준다
Join 힌트를 이용하는 경우는, 조인하는 SELECT 뒤에 기술해야 한다.
예를 들어, t1, t2 테이블에 대하여 JOIN_PREFIX 를 이용하는 경우는 아래와 같이 기술해야 한다.
SELECT /*+ JOIN_PREFIX(t1) */ t2.id FROM t2 LEFT JOIN t1 on t1.id = t2.id;

기존의 STRAIGHT_JOIN 은 강제적으로 조인하는 순서를 지정했지만 JOIN_ORDER, JOIN_PREFIX, JOIN_SUFFIX 은 강제로 하지않고, 옵티마이저가 판단하여 cost가 높을 경우 선택되지 않을 가능성이 있다.

SET_VAR 힌트
statement 안에 시스템 변수의 세션 값을 일시적으로 설정할 수 있게 되었다.
구문은 SET_VAR(var_name = value) 와 같이 기술한다. 예를 들어, index_condition_pushdown 을 비활성화 하는 경우는 아래와 같이 기술해야 한다.
mysql> SELECT /*+ SET_VAR(optimizer_switch='index_condition_pushdown = off') */ ...

INDEX_MERGE, NO_INDEX_MERGE 힌트
지정된 테이블 또는 인덱스의 인덱스 머지를 활성화 또는 비활성화 할 수 있게 되었다.
예를 들어, t1 테이블의 i_a, i_b, i_c 인덱스를 머지하는 경우는 아래와 같이 기술해야 한다.
SELECT /*+ INDEX_MERGE(t1 i_a, i_b, i_c)*/ * FROM t1
  WHERE a = 1 AND b = 2 AND c = 3 AND d = 4;

HASH_JOIN, NO_HASH_JOIN 힌트
MySQL 8.0.18 에서 추가된 HASH_JOIN 을 활성화 또는 비활성화 할 수 있게 되었다.
이 힌트는 MySQL 8.0.18 에서만 유효하고, MySQL 8.0.19 부터는 효과가 없기 때문에 주의가 필요하다.
참고로 MySQL 8.0.19 에서 HASH_JOIN 을 비활성화 시키려면 NO_BNL 힌트로 비활성화 되는 걸 확인할 수 있었다.
mysql> EXPLAIN FORMAT=tree SELECT /*+ NO_HASH_JOIN(t,t2) */ * FROM t JOIN t2 USING(c);
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t.c = t2.c)  (cost=4105.41 rows=3953)
    -> Table scan on t  (cost=547.54 rows=39528)
    -> Hash
        -> Table scan on t2  (cost=0.35 rows=1)
 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> EXPLAIN FORMAT=tree SELECT /*+ NO_BNL(t,t2) */ * FROM t JOIN t2 USING(c);
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                    |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join  (cost=4105.40 rows=3953)
    -> Table scan on t2  (cost=0.35 rows=1)
    -> Filter: (t.c = t2.c)  (cost=547.53 rows=3953)
        -> Table scan on t  (cost=547.53 rows=39528)
 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

이외 에도 MERGE 힌트, SKIP_SCAN 힌트 등이 추가 되었으므로, 상세한 내용은 공식 사이트를 참고하면 좋을 거 같다.
https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html

결혼이민비자 신청방법(F-6-1 국민의 배우자)

 제가 일본인 여자친구와 결혼 후, 한국에 귀국하기 위해 신청한 결혼이민비자에 대하여 작성해보도록 하겠습니다. 필자는 일본에서 근무하고 있었으며, 한국에서의 소득은 없었습니다. 결혼이민비자를 신청한 날짜는 2021-04-21 이며, 사증이 발급된 날짜...