안녕하세요. 기술적 겸손함으로 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 리플리카 서버를 적용하는 일련의 과정을 시도해 보는 글을 작성하고자 합니다.

저는 ubuntu22.04, docker 24.0.2,  MySql lastest 버전을 사용하였습니다.

 

 

1. 리플리케이션

 

출처: https://tgyun615.com/118

 

데이터베이스의 리플리케이션은 마스터-슬레이브 구조를 가집니다. 마스터 데이터베이스는 쓰기 작업, 슬레이브 데이터베이스는 읽기 작업을 처리하는 방식입니다.

 

리플리케이션을 적용하면 다음의 장점을 가질 수 있습니다.

- 부하 분산: 리플리케이션은 슬레이브 서버가 읽기 요청, 마스터 서버가 쓰기 요청을 수행하므로 데이터베이스 부하를 줄일 수 있습니다.

- 고가용성: 마스터 데이터베이스에 문제가 생긴 경우, 슬레이브 서버는 상태를 유지하고 있으므로 데이터 손실의 위험성을 줄일 수 있습니다.

- 읽기 성능 향상: 슬레이브 데이터베이스는 독립적인 읽기 작업을 수행하므로, 읽기 성능을 향상할 수 있습니다.

 

하지만 고려해야 하는 상황은 다음과 같습니다.

- 쓰기 성능 이슈: 마스터 데이터베이스에 쓰기 요청을 수행한 후, 슬레이브 서버로 데이터를 업데이트하는 과정이 수반되므로, 쓰기 이슈가 발생할 수 있습니다.

 

 

구현 목적

 

1. 스케일 아웃:

수직적으로 서버의 사양을 높이는 것을 스케일 업이라고 한다면, 수평적으로 서버를 증설하는 방법을 스케일 아웃이라고 표현합니다. 스케일 아웃을 적용한다면 스케일 업 방식보다 갑자기 늘어나는 트래픽을 대웅하는데 유연한 구조를 가질 수 있습니다.

 

2. 데이터 백업:

DB서버에는 다양한 종류의 데이터가 저장이 됩니다. 이 과정에서 DB에 저장된 데이터들을 주기적으로 백업하는 것이 필수적입니다. 따라서, 데이터 백업의 절차등을 리플리케이션 서버에서 진행합니다.

 

3. 데이터 분석:

특정 데이터 집단에 대해 인사이트를 얻기 위해 분석용 쿼리등을 실행하기도 합니다. 이러한 분석용 쿼리는 대량의 데이터를 조회하는 경우가 많습니다. 집계 연산 등의 복잡하고 무거운 연산을 진행할 수 있으므로 복제를 사용해 여분의 레플리카 서버를 구축하기 위한 용도로도 사용합니다.

 

4. 데이터의 지리적 분산

 

서비스에서 사용되는 에플리케이션 서버와 DB는 지리적으로 근접하거나 멀 수 있습니다. DB 서버와 에플리케이션 서버의 거리에 따라 통신 시간이 달라질 수 있으므로 다양한 지점에 대한 DB 서버를 위치함으로써 응답 속도를 개선시킬 수 있습니다.

 

 

2. 리플리케이션 서버의 작동 원리

 

 

1) Binary Log, Replay Log 두 가지 메커니즘

 

MYSQL에서 데이터 복제는 Binary Log, Replay Log 두 가지 메커니즘으로 동작합니다.

 

Binary Log: 마스터 서버에 있는 바이너리 로그는 데이터 변경에 대한 정보를 기록합니다. 테이블에 새로운 데이터가 삽입되거나, 기존 데이터가 수정 또는 삭제될 때 정보를 기록합니다. 이 로그를 바탕으로 슬레이브 서버의 데이터를 업데이트합니다.

 

Relay Log: 슬레이브 서버는 마스터 서버에서 보낸 Binary Log를 기반으로 자신의 Relay Log에 저장합니다. 이 로그를 바탕으로 데이터를 업데이트합니다.

 

 

2) I/O 스레드, SQL 스레드

데이터 복제는 I/O 스레드와 SQL 스레드가 비동기적으로 작동하여 처리됩니다.

 

I/O 스레드(Slave I/O Thread): I/O 스레드는 슬레이브에서 실행되며 미스터 서버로부터 binary Log Events를 읽어오는 역할을 합니다. 마스터 서버의 Binary Log를 슬레이브의 Relay Log로 복사합니다.

 

SQL 스레드(Slave SQL Thread): SQL 스레드는 슬레이브에서 실행되며, I/O 스레드가 relay Log에 기록한 이벤트를 읽고 실행하는 역할을 수행합니다. 

 

 

3) 특정 데이터 값 변경에 대한 Binary Log와 읽기 작업

 

MYSQL 복제에서 마스터의 Binary Log에서 슬레이브의 Relay Log로 데이터를 복제하는 I/O 스레드와 이를 실제 슬레이브 DB에서 적용하는 SQL 스레드는 비동기적으로 동작합니다.

I/O 스레드가 마스터의 Binary Log를 읽어 Relay Log에 기록하는 동안 Relay Log는 이전 이벤트를 읽어서 데이터를 업데이트할 수 있습니다. 이 과정에서 데이터 업데이트 간 지연이 발생한다면 최신 변경 사항이 적용되지 않은 데이터를 읽을 수 있습니다.

 

따라서, 만약 금전과 관련된 정보가 마스터 서버에는 업데이트되어 잔액 부족이 발생할 수 있지만, 지연 문제로 슬레이브 서버에는 잔액이 있다고 나올 수 있습니다. 따라서, 이러한 리플리카 서버의 형태는 마스터 서버에서 쓰기 작업을 수행하고, 리플리카 서버는 읽기 작업의 용도로 활용할 수 있습니다.

 

 

4) 슬레이브 서버는 커밋된 트랜잭션을 받아서 복제 처리

 

슬레이브 서버는 마스터 서버에서 커밋 혹은 롤백된 결과를 바탕으로 복제를 진행합니다. 즉, 마스터 서버에서는 커밋되지 않은 결과를 슬레이브 서버에 보내지 않음으로써 데이터의 원자성을 보존할 수 있습니다.

 

 

 

3. 마스터 서버와 슬레이브 서버 생성하기 

 

마스터 서버와 슬레이브 서버를 서로 분리하기 위해 도커를 활용하여 마스터 서버, 슬레이브 서버를 분리하였습니다.

 

절차는 다음과 같습니다.

1) 도커로 마스터 서버 역할을 하는 mysql 컨테이너 생성

2) 마스터 서버에 database 생성 및 table 생성 후 dump.sql 생성

3) 마스터 서버에 연결할 슬레이브 mysql 서버 생성

4) 슬레이브 서버에 dump.sql 파일을 복제한 후, 마스터 서버에 슬레이브 연결

5) 마스터 서버에서 insert 한 결과가 슬레이브에 저장되는지 파악하기

 

 

1) 마스터 서버 도커파일 - mysql-master.dockerfile

 

FROM mysql:latest

ENV MYSQL_ROOT_PASSWORD root
ENV MYSQL_DATABASE sample
ENV MYSQL_USER user
ENV MYSQL_PASSWORD password

COPY my.cnf /etc/mysql/my.cnf

EXPOSE 3306

CMD ["mysqld"]

 

도커파일이 있는 폴더 내부에 my.cnf 파일을 추가하였습니다.

 

[mysqld]
log-bin=mysql-bin
server-id=1

도커파일을 빌드하면 다음의 절차를 따르게 됩니다.

docker build -t mysql_master -f mysql-master.dockerfile .

 

도커 이미지가 생성되면, 도커 내부의 mysql 포트와 호스트 os의 포트를 연결하기 위해 다음의 커멘드를 입력합니다.

docker run -p 3307:3306 --name order_master -e MYSQL_ROOT_PASSWORD=1234 -d mysql-master

 

이후 도커 내부 쉘에 접속하기 위해 다음의 코드를 작성합니다.

docker exec -it order_master /bin/bash

# bash 이동 후
mysql -u root -p
password : 1234

# mysql 내부 

CREATE USER 'orderroot'@'%' IDENTIFIED BY '1234';
ALTER USER 'orderroot'@'%' IDENTIFIED WITH mysql_native_password BY '1234';
GRANT REPLICATION SLAVE ON *.* TO 'orderroot'@'%';
FLUSH PRIVILEGES;

 

grant replcation slave on *.* to 'orderrot'@'%'; 은 

사용자에게 복제(slave) 권한을 부여하는 구문입니다. 복제 권한은 마스터 서버에서 변경 사항을 읽을 수 있도록 허용하는 권한입니다. 이는 슬레이브 서버가 마스터 서버의 데이터 변경을 추적할 수 있게 합니다.

 

 

2) 데이터베이스 및 테이블 생성 후 dump.sql 생성

 

마스터 서버에서 데이터베이스 및 테이블을 생성하면 다음과 같습니다.

# mysql 쉘 
create database orders;

CREATE TABLE member (
    member_id BIGINT PRIMARY KEY,
    name VARCHAR(255)
);

insert into member value (1, 'kose');
insert into member value (2, 'gose');

exit

# bash 쉘
mysqldump -u root -p orders < dump.sql;

ls // dump.sql이 있어야 함
exit

 

이어진 로컬의 쉘에서 다음의 명령어를 입력합니다.

docker cp order_master:dump.sql .
>> Successfully copied 3.58kB to /home/gosekose/.

 

 

 

3) 슬레이브 서버 생성하기

 

마스터 서버와 마찬가지로 도커파일을 생성하되(위에 있던 마스터 도커 파일과 동일합니다.)

my.cnf를 수정해서 빌드하여야 합니다.

[mysqld]
log-bin=mysql-bin
server-id=2

 

마스터 서버와 분리되는 고유한 id를 설정하기 위해 slave 서버는 server-id=2로 한 후 도커 빌드를 진행합니다.

 

docker build -t mysql_slave -f mysql-slave.dockerfile .

 

이어서 빌드가 완료되면 마스터 서버와 연결하기 위해 --link를 추가하여 다음의 명령어를 실행합니다.

--link는 서로 다른 컨테이너 간 연결을 돕는 도커 명령어입니다.

docker run -p 3308:3306 --name order_slave -e MYSQL_ROOT_PASSWORD=1234 --link order_master -d mysql-slave

 

 

4) 슬레이브 서버에 덤프 파일 적용

 

이전에 생성한 덤프파일을 도커로 복사한 후, (cp) 데이터 베이스를 생성하여 dump.sql을 복사합니다.

docker cp dump.sql order_slave:.
docker exec -it order_slave /bin/bash

mysql -u root -p

mysql> CREATE DATABASE orders;

mysql> exit

mysql -u root -p orders < dump.sql

기존에 있는 데이가 슬레이브에 덤프 되었습니다.

 

5) 슬레이브 서버에 마스터 서버 연결하기

 

order_master의 mysql로 이동한 후 현재 master 서버의 status를 확인합니다.

 

mysql> SHOW MASTER STATUS\G

여기서 기억할 부분은 mysql-bin.000003, position 2193입니다. 

 

이어서 슬레이브 서버의 order_slave의 mysql로 이동하여 마스터 서버를 연결합니다.

 

CHANGE MASTER TO MASTER_HOST='order_master', MASTER_USER='orderroot', MASTER_PASSWORD='1234', MASTER_LOG_FILE='mysql-bin.000003', MASTER_LOG_POS=2193;

 

마스터 서버가 연결된 것을 확인하기 위해 하단의 명령어를 입력합니다.

SHOW SLAVE STATUS\G

 

Master_LOG_FILE 및 Read_Master_Log_Pos가 모두 마스터 서버의 값이 잘 기입되었습니다.

하단에 SlaveI_IO_Running, Slave_SQL_Running이 모두 Yes가 떠야 성공입니다.

이는 위에서 설명했던 슬레이브 서버에서 작용하는 스레드 두 가지가 모두 완료되어야 하는 것을 의미합니다.

 

 

5) 마스터 서버에 쓰기 진행

 

마스터 서버에 새로운 member를 저장합니다.

insert into member value(3, 'gosekose');

 

마스터 서버

 

슬레이브 서버

 

값이 잘 저장된 것을 확인할 수 있습니다.

 

 

이번 포스팅은 MySQL 리플리카 서버를 적용하는 과정에 대한 글을 작성하였습니다. 다음 글은 리플리카 서버를 바탕으로 스프링 부트 서버를 기동하여 리플리카 서버를 활용하는 글을 작성하고자 합니다. 이상으로 포스팅을 마치도록 하겠습니다. 감사합니다!

 

 

사진 출처: https://tgyun615.com/118

참고 자료: https://escapefromcoding.tistory.com/710

 

'DB' 카테고리의 다른 글

[DB] 락(잠금)  (0) 2023.04.24
[DB] MySQL8.0 Index  (0) 2023.02.06
[DB] MySQL 엔진 아키텍처 (Real MySQL 8.0)  (0) 2023.01.22
[DB] MySQL 계정 생성 및 권한 부여 (Real MySQL 8.0)  (0) 2023.01.07
[DB] 칼럼형 DBMS VS 로우형 DBMS  (0) 2022.12.28

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 RealMySQL8.0의 트랜잭션과 잠금에 대해서 정리하는 글을 작성하도록 하겠습니다.!

 

 

1. MySQL 엔진의 잠금

  • MySQL에서 사용되는 잠금은 스토리지 엔진 레벨과 MySQL 엔진 레벨로 나뉩니다.
  • MySQL 엔진 레벨의 잠금은 스토리지 엔진 레벨 잠금에 영향을 줄 수 있습니다.
  • MySQL 엔젠 레벨은 클라이언트로부터 쿼리를 받아 처리하고 결과를 반환하는 등 클라이언트에게 직접적인 엔진 레벨이지만, 스토리지 엔진 레벨은 DB 저장 및 검색 관리 방식 등을 결정하는 엔진으로 디스크에 가까운 역할을 수행하고 있습니다.

 

 

2. 글로벌 락 

글로벌락은 다음과 같은 명령으로 획득할 수 있습니다. MySQL에서 제공하는 잠금 중 가장 큰 범위로 select를 제외한 대부분의 DDL 혹은 DML 문장을 실행하는 경우 글로벌 락이 해제될 때까지 해당 문장이 대기 상태로 남습니다.

 

FLUSH TABLES WITH READ LOCK

 

FLUSH TABLES WITH READ LOCK은 테이블에 읽기 잠금을 걸기 전에 먼저 테이블을 플러시 해야 하므로 테이블에 실행 중인 모든 종류의 쿼리가 완료되어야 합니다.

 

 

3. 백업 락

 

MySQL8.0 버전부터는 XtraBackup 혹은 Enterprise Backup과 같은 백업 툴들의 안정적인 실행을 위해 백업 락이 도입되었습니다. 

  • XtraBackup: 오픈 소스로 도입되는 InnoDB 스토리지 엔진 전용의 무료 백업 도구로 물리적인 백업 방식을 사용합니다.
    XtraBackup은 인크리멘탈 백업 파일을 사용하는데, 이는 마지막 전체 백업 이후 변경된 데이터만 백업하는 방법을 의미합니다. 따라서 백업 파일의 크기를 줄이고 백업 속도를 향상할 수 있습니다.
  • Enterprise Backup은 기업용 백업 솔루션으로 별도의 백업 서버에서 실행됩니다. 

백업 락의 실행 과정이 잘 이해가 안되는 부분이 있어서 제가 이해한 바를 하단에 정리하였는데 이 부분은 실제 내용과 다를 수 있습니다.!

  • 소스 서버에 있는 데이터를 백업하기 위해 레플리카 서버가 활용되는데, 백업을 위해 소스 서버 전체를 글로벌 락으로 잠그는 데에는 효율적이지 않습니다.
  • 일반적으로 백업하는 과정에서 DDL 요청이 수행되면 백업에 실패하게 됩니다.
  • 이러한 문제를 해결하기 위해  레플리카 서버에 백업 락을 설정하여 소스 서버는 DDL이 처리될 때 레플리카 서버는 잠시 DDL 처리가 완료되기까지 락을 해제하고 대기합니다. DDL이 종료되면 백업을 진행하게 됩니다.

 

 

4. 테이블 락

 

테이블 락은 명시적 또는 묵시적 락으로 특정 테이블의 락을 획득할 수 있습니다.

명시적으로 획득한 잠금은 잠금을 반납할 수 잇는데, 특별한 상황이 아니라면 사용할 일이 많이 없습니다.

반면, 묵시적 테이블 락은 MyISAM 혹은 MEMORY 테이블에 데이터를 변경하는 쿼리를 실행하면 발생합니다.

InnoDB 엔진에서는 테이블 락이 설정되기는 하지만 DML 쿼리에서는 레코드 락이 설정되고 DDL의 경우 영향을 미칩니다.

 

 

5. 네임드 락

 

네임드락은 GET_LOCK() 함수를 이용해 임의의 문자열에 대한 잠금을 설정하비다.

네임드 락은 단순히 사용자가 지정한 문자열에 대해 획득하고 반납하는 잠금입니다. 이러한 잠금으로 동일한 요청에 대한 반복 수행 등을 방지하는 역할을 수행할 수 있습니다. 

 

네임드 락은 분산락과 비슷하지만 다른 용도로 사용됩니다.

네임드 락 분산 락
문자열(키)로 잠금 문자열(키)로 잠금
공유 자원 접근을 관리하되, 단일 데이터베이스에서 상호 동기화 처리에 용이 여러 데이터베이스 클러스터간 공유 자원 접근을 제어

 

 

6. InnoDB 스토리지 엔진 잠금

 

InnoDB 스토리지 엔진은 레코드 기반의 잠금 기능을 제공하며, 잠금 정보가 상당히 작은 공간으로 관리되기 때문에 레코드 락이 페이지 락으로 또는 테이블 락으로 레벨업 되는 경우는 없습니다.

 

InnoDB 스토리지 엔진은 레코드 락과 갭 락, 넥스트 키 락을 제공합니다.

사실 이 부분은 명확하게 이해하기가 너무 어려웠습니다. 따라서 먼저 이론적인 내용을 정리한 후, 실습을 통해 제가 이해한 바를 정리하도록 하겠습니다 (이 부분 역시 사실과 다를 수 있는 점 양해 부탁드립니다.)

 

<레코드 락>

  • 레코드 자체를 잠그는 것을 레코드 락이라고 합니다. InnoDB 스토리지 엔진은 레코드 자체가 아니라 인덱스의 레코드를 잠급니다. 
  • 인덱스가 없는 경우 자동 생성된 클러스터 인덱스를 이용해 잠금을 처리합니다.

 

<갭 락>

  • 갭 락은 레코드 자체가 아니라 레코드와 바로 인접한 레코드 사이의 간격만을 잠그는 것을 의미합니다.
  • 갭 락의 역할은 레코드와 레코드 사이의 간격에 새로운 레코드가 생성되는 것을 제어합니다.

 

<넥스트 키 락>

  • 레코드 락과 갭 락을 합쳐 놓은 형태로 innodb_locks_unsafe_for_binlog 시스템 변수가 비활성화되면 변경을 위해 검색하는 레코드에는 넥스트 키 락 방식으로 잠금이 걸립니다.

 

 

<실습>

 

먼저, 실습 환경은 MySQL8.25 REPEATABLE READ 격리 수준입니다.

주어진 테이블의 인덱스를 확인하기 위해 아래의 명령어를 작성하여 프라이머리 키와 보조 인덱스를 확인하였습니다.

 

show index from employees;

 

 

이후, insert into 구문으로 새로운 데이터 2 개를 삽입하였습니다.

 

 

보조 인덱스로 검색할 경우, 보조 인덱스가 잘 적용되는지 확인하기 위해 실행 계획을 실행한 결과 올바르게 적용되었습니다.

 

 

먼저 1번 터미널에 start transaction을 작성하고, 2번 터미널에서도 start transaction을 적용하여 서로 격리된 트랜잭션을 적용하였습니다.

 

이 상태로, 1번 터미널에서 성별이 M이고 1997-01-01과 1997-01-03 사이에 업데이트를 위한 조회문을 작성하였습니다.

select * from employees where gender = 'M' and birth_date between '1997-01-01' and '1997-01-03' for update;

 

 

2번 터미널에서는  1997-01-02과 1997-01-03 사이인 1997-01-02일 값을 가지는 인서트문을 작성하였습니다.

 

insert into employees (emp_no, birth_date, first_name, last_name, gender, hire_date) values (1000003, '1997-01-02', 'Gose', 'Kose', 'M', '2023-09-01');

 

그 결과 갭 락이 적용되는 것을 확인할 수 있었습니다. 

 

 

사이값 뿐만 아니라, 시작점인 1997-01-01의 전날인 1996-12-31, 당일, 이후인 1997-01-04도 역시 갭 락이 적용되었습니다.

 

그렇다면 보조 인덱스가 아닌 프라이머리키를 적용할 때는 어떤 결과가 나올까요?

 

먼저 실행 계획으로 프라이머리키가 인덱스로 활용되는지 파악하였습니다.

 

<1번 터미널>

 

 

1000001 ~ 1000006번 사이에는 1000001, 1000002, 1000006번이 있습니다.

 

 

2번 터미널에서는 1000005를 pk로 가지는 임의의 값을 대입하였습니다 

insert into employees (emp_no, birth_date, first_name, last_name, gender, hire_date) values (1000005, '1996-12-31', 'Gose', 'Kose', 'M', '2023-09-01');

 

그 결과, 프라이머리키로 범위를 설정하였을 때도 갭 락이 적용되는 것을 확인할 수 있었습니다.

교재나 다른 글에서는 프라이머리 키 또는 유니크 인덱스에 의한 변경 작업에서 갭에 대해서는 잠그지 않고 레코드 자체에 대해서만 잠근다고 설명되어 있습니다. 

 

현재, LOCK_MODE에 GAP이 있지만 이 것이 어떠한 이유로 적용되었는지는 파악하기가 어려웠습니다. 제가 이해한 바로는 프라이머리 키 또는 유니크 인덱스에 의한 변경 작업은 직접적으로 pk를 이용하여 변경하므로 갭을 사용하지 않지만 위의 예시와 같이 범위를 설정하는 경우에는 갭락이 적용되는 것이라고 생각하게 되었습니다.

 

 

 

데이터베이스를 얕게 알 때는 몰랐지만, 공부를 하면 할수록 정말 어려운 것 같습니다.

락에 대해서 전 보다 깊게 배울 수 있었고 어떤 상황에는 어떠한 락이 적용되는지 파악할 수 있었습니다.

이상으로 포스팅을 마치겠습니다 감사합니다.!

 

 

참고자료: RealMySQL8.0 백은빈님 이성욱님 지음

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 RealMySQL8.0 (백은빈님 지음) 책을 읽고, 가상의 데이터를 생성하여 인덱스 쿼리 성능 비교를 하는 글을 작성하도록 하겠습니다.

 

Spring의 JPA나 QueryDsl을 사용할 때, index에 대한 개념을 명확하게 정리하지 않는 체 구현에만 급급하여 프로젝트를 진행했었습니다. RealMySQL8.0을 읽으면서 인덱스의 구조와 실행 계획을 확인하니, Like의 '%'의 위치에 따라 인덱스로 데이터 접근이 가능하거나 불가능하다는 사실을 확인할 수 있었습니다. 먼저 인덱스에 대한 개념과 B-Tree에 대한 설명, 그리고 쿼리를 통한 성능 분석을 진행하도록 하겠습니다.

 

1. 인덱스란?

DBMS는 테이블의 모든 데이터를 검색해서 원하는 정보를 가져오려면 시간이 오래 걸립니다. 그래서, 컬럼의 값과 해당 레코드가 저장된 주소를 키와 값의 쌍으로 삼아 인덱스를 생성하면, 시간을 단축할 수 있습니다. 또한, 정렬 기능이 추가된다면 사전을 찾을 때처럼 단어를 찾는 속도를 향상할 수 있는데, DBMS도 마찬가지로 컬럼의 값을 주어진 순서로 미리 정렬해서 보관합니다.

 

인덱스의 역할로 구분하기

- 프라이머리 키: 프라이머리키는 레코드를 대표하는 컬럼의 값으로 만들어진 인덱스입니다. 이 컬럼은 테이블에서 해당 레코드를 식별할 수 있는 기준값이 되기 때문에, 이를 식별자라고 부릅니다. 프라이머리 키는 NULL 값과 중복 값을 허용하지 않는 특징이 있습니다.

- 세컨더리 키: 프라이머리 키를 제외한 모든 인덱스를 세컨더리 인덱스라 부릅니다. 유니크 인덱스는 프라이머리 키와 성격이 비슷하고 프라이머리 키를 대체하여 사용할 수 있어 대체키로 부르기도 합니다.

 

 

2. B-Tree 인덱스

B-Tree는 Balanced Tree의 약자로 데이터베이스 인덱싱 알고리즘 가운데 가장 일반적으로 사용되고, 가장 먼저 도입된 알고리즘입니다. B-Tree의 변형된 알고리즘 형태는 B+-Tree와 B*-Tree가 있습니다.

RealMySQL8.0

- 구조 :

B-Tree는 트리구조의 최상위에 하나의 "루트 노드(Root node)"가 존재하고 그 하위에 자식 노드가 붙어 있는 행태입니다.  트리 구조의 가장 하위에 있는 노드를 "리프 노드(Leaf node)"라 하고, 트리 구조에서 루트 노드와 리프 노드가 아닌 중간 노드를 브랜치(Branch node)라 부릅니다. 리프 노드는 실제 데이터 레코드를 찾아가기 위한 주소값을 가지고 있습니다.

대부분 RDBMS의 데이터 파일에서 레코드는 특정 기준으로 정렬되지 않고 임의의 순서로 저장됩니다. 하지만 InnoDB 테이블에서 레코드는 클러스터 되어 디스크에 저장되므로 기본적으로 프라이머리 키 순서로 정렬되어 저장됩니다,!

 

 

3. 인덱스 사용과 성능

인덱스는 사용 방법에 따라, 성능이 달라집니다. 인덱스 테이블을 사용하더라도, 잘못 사용하면 인덱스의 주요 기능을 활용할 수 없습니다. 인덱스 성능 테스트를 위해 인텔리제이를 MySQL와 연동하여 약 1000만 개의 데이터를 생성하여 쿼리를 파악하는 시간을 가져보았습니다.

 

1) 인텔리제이 Mysql 콘솔 활용하기

 

오른쪽 상단에 데이터베이스를 클릭하고 생기는 데이터 소스 프로퍼티를 클릭하면, 인텔리제이와 MySQL을 연동할 수 있습니다.

호스트, 사용자, 비밀번호, URL을 입력하면 현재 root 계정에 있는 MySQL database에 연결할 수 있습니다.

 

 

 

2) 테이블 생성과 더미 데이터 저장

member의 테이블 명과 member_id (primary_key),  age, gender, user_id, username의 컬럼을 가진 테이블을 생성하였습니다.

다른 컬럼과 비교하기 위해 인덱스 컬럼은 user_id로 설정하였습니다.

# 인덱스 생성하기
CREATE INDEX member_user_id_idx ON member(user_id);

 

더미 데이터는 insert into table(컬럼) values(값)으로 일부 저장한 후,  아래 콘솔을 여러 번 반복하여 데이터를 추가하였습니다. insert into table 이후, select 절을 활용하면 select 된 데이터를 다시 insert 하는 데 사용할 수 있습니다. 이러한 방법을 활용하면 현재 있는 데이터 *2 만큼 데이터를 추가할 수 있습니다.

INSERT INTO member SELECT NULL, age, gender, user_id, username FROM member;

 

 

3) 성능 비교하기 인덱스 비교

 

먼저 user_id라는 인덱스를 활용하여 질의한 실행계획은 다음과 같습니다. type에 ref로 동등 조건이 실행되었고, possible_keys로 실행 가능한 인덱스 키를 확인할 수 있습니다. 즉, member_user_id_idx라는 인덱스로 ref(동등 조건) 비교를 실행할 수 있다는 의미입니다.

결과를 파악하면, 80ms를 기록하였습니다.

 

 

반면, 인덱스를 활용하지 않은 경우는 실행계획과 결과가 다음과 같습니다. type은 All 처리, pssible_keys는 null이 기록되었습니다. 인덱스로 활용 가능하지 않으므로 해당 값이 기록되지 않은 것입니다.

추가로, SQL_NO_CACHE는 실행되는 쿼리가 반복되더라도 캐시를 활용하지 않겠다는 의미입니다. 이는 여러 번 반복되는 행위에 MySQL이 캐시화하는 과정이 숨어있는데 이를 제거하지 않는다면 명확한 비교를 하기 어렵기 때문입니다. 결과를 확인하면 128ms로 인덱스보다 훨씬 느린 것을 볼 수 있었습니다.

 

5) 성능 비교하기 Like '단어%'

인덱스는 Like를 매우 효율적으로 활용할 수 있습니다. 하지만, 사용하는 방법에 따라, 인덱스를 활용하지 못할 수도 있습니다. 

전자는 '%'를 뒤에 적용하여 질의하는 것입니다. 실행계획을 살펴보면 member_user_id_idx로 range 질의가 실행되고 있습니다.

쿼리 실행 결과 90ms입니다.

 

 

인덱스를 활용하지 않은 경우는 다음과 같습니다. 적용가능한 possible_keys가 없으므로 all 질의가 진행되었고 성능은 3s가 기록되었습니다.

 

6) 성능 비교 Like '%단어'

'단어%' 형태로 질의한 경우, 많은 성능 차이를 보였지만, %가 앞에 위치하게 된다면 인덱스를 활용할 수 없습니다. 인덱스는 기본적으로 왼쪽 값을 기준으로 오른쪽 값이 정렬되어 있습니다. 따라서 '단어%'의 경우, 해당 단어가 기록되기 시작 한 곳부터 범위 계산을 할 수 있지만, '%단어' 형태가 된다면, 앞에서부터 해당 단어가 포함되는지 확인해야 하므로, 인덱스를 사용하는 의미가 없어지는 것입니다. 예시를 보면 다음처럼, 인덱스를 활용한 결과와 활용하지 않은 결과가 동일한 것을 보실 수 있습니다.

 

 

 

7) 함수를 이용한 인덱스 검색

MySQL8.0 버전은 함수형 인덱스를 제공한다는 장점이 있습니다. 데이터가 많을 때, 인덱스 컬럼을 추가한다면 비효율적일 수 있습니다. 이때, 자주 질의되는 형태를 함수형으로 구현한다면 인덱스를 활용할 수 있습니다. 

 

CONCAT(username, ' ',gender

 

member_username_gender_full_idx라는 인덱스로 ref(동등비교) 질의가 실행되는 것을 확인할 수 있었습니다.

 

 

하지만, 중요한 점은 같은 결과를 반환할지라도, 반드시 정의한 함수형 원형을 사용해야 합니다. 

저는 gender를 M, WM으로 대문자로 선언했기에 upper를 쓰더라도 결과는 같은 결과를 반환합니다.

하지만, 원형에 변형을 준다면 인덱스를 활용할 수 없습니다.

 

 

 

이상으로 인덱스를 활용한 예제 몇 가지를 실습하고 정리해 보았습니다.

이외에도 많은 index 활용 전략이 있습니다. 이는 추후 2편으로 나눠서 진행하도록 하겠습니다.

활용소스는 깃허브 링크 하단에 추가하도록 하겠습니다.

 

읽어주셔서 감사드립니다.!

 

참고자료: RealMySQL8.0 (백은빈님 지음)

활용 소스: https://github.com/gosekose/MySQL-Study/blob/main/issue6/Issue6.sql

 

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

오늘은 MySQL의 엔진 아키텍처에 대해서 정리하는 글을 작성하고자 합니다.

 

1. MySQL 전체 구조도

MySQL은 MySQL 엔진스토리지 엔진로 구성되어 있으며, 이를 MySQL 서버라고 부릅니다.

 

2. 구조도 요약

- MySQL 엔진 구성요소

  • 커넥션 핸들러
  • SQL 파서
  • 전처리기
  • 옵티마이저

- 스토리지 엔진

(실제 데이터르 디스크 스토리지에 저장하거나 디스크 스토리지로부터 데이터를 읽어오는 부분은 스토리지 엔진이 전담합니다.)

  • 키 캐시 (MyISAM 스토리지 엔진)
  • InnoDB 버퍼 풀

 

3. MySQL 스레딩 구조

 

MySQL 서버는 멀티스레드 프로그램입니다.

MySQL은 클라이언트 요청에 따라 스레드를 계속 생산하지 않고 정해진 개수를 상황에 따라 위임하여 처리합니다.

(과도한 멀티스레딩은 오히려 컨텍스트 스위칭 성능이 떨어뜨립니다 참고자료:  https://www.crocus.co.kr/1364

따라서, 멀티스레드 프로그램들은 스레드 풀(thread pool)을 이용해서  스레드의 개수를 제한합니다.

 

스레드 풀은 내부적으로 사용자의 요청을 처리하는 스레드의 개수를 줄여서 동시 처리되는 요청이 많더라도, MySQL 서버의 CPU가 제한된 개수의 스레드 처리에만 집중할 수 있게 합니다.

서버는 요청이 들어올 때마다 새 스레드를 생성하는 대신, 스레드 풀에게 작업을 위임하게 되고

작업은 내부적으로 큐에 저장하고 스레드들은 큐에서 작업을 위임받아 처리합니다.

- MySQL은 스레드 기반으로 작동하며, 크게 포그라운드백그라운드 스레드로 구분할 수 있습니다.

 

 

포그라운드 스레드 

- 클라이언트 사용자가 요청하는 쿼리 문장을 처리

- InnoDB 테이블은 데이터 버퍼나 캐시까지만 포그라운드 스레드가 처리, 나머지 버퍼로부터 디스크까지 기록하는 작업은 백그라운드 스레드가 처리

 

InnoDB의 백그라운드 스레드

- 인서트 버퍼를 병합하는 스레드

- 로그를 디스크로 기록하는 스레드

- InnoDB 버퍼 풀의 데이터를 디스크에 기록하는 스레드

- 데이터를 버퍼로 읽어 오는 스레드

- 잠금이나 데드락을 모니터링 하는 스레드 

 

백그라운드 스레드가 디스크에 기록하는 역할을 수행하는 이유는, 사용자의 'select' 요청은 지연될 수가 없지만, insert, update, delete는 해당 요청을 처리하되 바로 데이터 파일에 접근하여 수정하지 않고 일정 시간의 지연을 가지며 일괄적으로 처리를 수행하도록 할 수 있습니다. (비정상적인 종료를 방지한 언두, 로그 기록 등 필수)

 

쓰기 스레드는 아주 많은 작업을 백그라운드로 처리하기 때문에 읽기 스레드는 많이 설정할 필요가 없지만 쓰기 스레드는 아주 많은 작업을 백그라운드로 처리하기 때문에 일반적인 내장 디스크를 사용할 때는 2 ~ 4 정도로 설정하는 것이 좋습니다.

 

4. 쿼리 실행 구조

 

- 쿼리 파서

쿼리 파서는 사용자 요청으로 들어온 쿼리 문장을 토큰이 인식할 수 있는 최소 단위의 어휘나 기호로 분리해 트리 형태의 구조로 만들어 내는 작업을 의미합니다. 쿼리 문장의 기본적인 문법 오류는 이 과정에서 발견됩니다.

selecr * from member;
-> select 에러

 

- 전처리기

파서 과정에ㅓ 만들어진 파서 트리를 기반으로 쿼리 문장에 구조적인 문제점이 있는지 확인합니다.

각 토큰을 테이블 이름이나 칼럼 이름, 또는 내장함수와 같은 개체를 매핑해 해당 객체에 존재 여부와 객체의 접근 권한 등을 확인하는 과정을 이 단계에서 수행, 실제 존재하지 않거나 권한상 사용할 수 없는 개체의 토큰은 이 단계에서 걸러집니다.

select * from member;
-> SELECT command denied to user 'kose'@'localhosts' for table 'member'

 

- 옵티마이저

옵티마이저랑 사용자의 요청으로 들어온 쿼리 문장을 저렴한 비용으로 가장 빠르게 처리할지를 결정하는 역할을 수행

옵티마이저 튜닝에 따라 성능 차이가 발생할 수 있습니다.

 

- 실행 엔진

실행 엔진은 옵티마이저의 결과에 따라 만들어진 계획을 핸들러에게 요청, 요청 후 처리된 결과를 다른 핸들러의 요청의 입력으로 처리하는 중간 처리 단계 역할 수행합니다.

 

- 핸들러

핸들러는 MySQL 서버의 가장 밑단에서 MySQL 실행 엔진의 요청에 따라 데이터를 디스크로 저장하고 디스크로부터 읽어 오는 역할을 담당합니다. 

 

- 쿼리 캐시

MySQL 서버에서 쿼리 캐시는 빠른 응답을 필요로 하는 웹 기반의 응용 프로그램에서 매우 중요한 역할을 담당했지만, 쿼리 캐시는 테이블 데이터가 변경되면 캐시에 저장된 결과 중에서 변경된 테이블과 관련된 것들을 모두 삭제해야 합니다.

따라서, 심각한 동시 처리 성능 저하, 버그의 원인되어 쿼리 캐시는 MySQL 8.0에서 완전히 제거 

 

 

5. InnoDB 스토리지 엔진 아키텍처

프라이머리 키

- InnoDB의 모든 테이블은 기본적으로 프라이머리 키를 기준으로 클러스터링되어 저장됩니다.

- 프라이머리 키가 클러스터링 인덱스이기 때문에 프라이머리 키를 이용한 레인지 스캔은 상당히 빨리 처리될 수 있습니다.

- 쿼리의 실행 계획에서 프라이머리 키는 기본적으로 다른 보조 인덱스에 비해 비중이 높게 설정된다.

 

MVCC

- 레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능 MVCC의 가장 큰 목적은 잠금을 사용하지 않는 일관된 읽기를 제공하는데 있습니다.

- InnoDB는 언두 로그를 이용해 이 기능을 구현하고, 하나의 레코드에 대해 여러 개의 버전이 동시에 관리됩니다. 

- Row 수준의 락을 제공하므로, 다중 동시성 제어를 효율적으로 수행할 수 있습니다.

- 격리 수준 READ_UNCOMMITED -> InnoDB 버퍼 풀이 현재 가지고 있는 변경된 데이터를 읽어서 반환하고

  READ_COMMITED -> 변경되기 이전의 내용을 가지고 있는 언두 영역의 데이터 /반환

 

어댑티브 해시 인덱스

- 어댑티브 해시 인덱스는 사용자가 수동으로 생성하는 인덱스가 아니라 InnoDB 스토리지 엔진에서 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스입니다.

- InnoDB 스토리지 엔진은 자주 읽히는 데이터 페이지의 키 값을 이용해 해시 인덱스를 만들고, 필요할 때마다 어댑티브 해시 인덱스를 검색해서 레코드가 저장된 데이터 페이지를 즉시 찾아갈 수 있습니다.

- 해시 인덱스는 인덱스 키 값과 해당 인덱스 키 값이 저장된 데이터 페이지 주소의 쌍으로 관리합니다.

- 인덱스 키 값은 B-Tree 인덱스의 고유번호와 B-Tree 인덱스의 실제 키 값 조합으로 생성합니다.

 

언두로그

- InnoDB 스토리지 엔진은 트랜잭션 격리 수준을 보장하기 위해 DML 변경되기 이전 버전의 데이터를 별도로 백업합니다.

- 트랜잭션 롤백 대비용, 트랜잭션의 격리 수준을 유지하면서 높은 동시성을 제공합니다.

 

 

체인지 버퍼

- RDMBS에서 레코드가 insert 되거나 update 될 때, 데이터 파일을 변경하는 작업과 해당 테이블에 포함된 인덱스를 업데이트하는 작업도 필요합니다.

- 랜덤하게 디스크를 읽는 작업이 필요하므로 테이블에 인덱스가 많다면 이 작업은 상당히 많은 자원을 소모합니다.

- 버퍼 풀에 있다면 바로 업데이트를 수행하지만, 임시 공간에 값을 저장해 두고 사용자에게 결과를 반환합니다.

- 반드시 중복 여부를 체크해야하는 유니크 제약 조건 인덱스는 사용 불가합니다.

 

 

리두 로그

- 리두 로그는 하드웨어나 소프트웨어 등 여러 가지 문제점으로 인해 MySQL 서버가 비정상적으로 종료됐을 때 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전장치입니다.

- MySQL 서버를 포함한 대부분 데이터베이스 서버는 데이터 변경 내용을 로그에 먼저 기록하고, 쓰기 비용이 낮은 자료 구조를 가진 리두 로그, 비정상 종료가 발생하면 리두 로그의 내용을 이용해 데이터 파일을 다시 서버가 종료되기 직전 상태로 복구합니다.

 

6. MyISAM 아키텍처

정적인 데이터를 저장하고 자주 읽기 작업이 일어나는 테이블에 적합한 엔진입니다

 

- 구조가 단순하고 속도가 빠르고 데이터 저장에 실제적인 제한이 없고 매우 효율적으로 저장합니다.

- 테이블 작업시 특정 행을 수정하려고 하면 테이블 전체에 락이 걸려서  다른 사람이 작업할 수 없습니다.

- 트랜잭션에 대한 지원이 없기때문에 작업 도중의 에러 발생 시, 데이터 무결성이 깨질 수 있습니다.

 

7. MyISAM vs InnoDB 차이점 표 비교

 

 

참조: https://velog.io/@sweet_sumin/DB-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EC%97%94%EC%A7%84-%EB%B3%84-%EC%B0%A8%EC%9D%B4

'DB' 카테고리의 다른 글

[DB] MySQL8.x 리플리카 서버 적용하기(1)  (0) 2023.06.10
[DB] 락(잠금)  (0) 2023.04.24
[DB] MySQL8.0 Index  (0) 2023.02.06
[DB] MySQL 계정 생성 및 권한 부여 (Real MySQL 8.0)  (0) 2023.01.07
[DB] 칼럼형 DBMS VS 로우형 DBMS  (0) 2022.12.28

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 MySQL의 계정 생성 및 권한 부여에 관한 내용을 정리하고자 합니다.

내용 및 예제 소스는 백은빈 님, 이성욱 님의 Real MySQL 8.0를 기반으로 구성하였습니다.

 

제 운영체제 환경은 ubuntu 20.04이고, MySQL 버전은 8.0이므로 맥 OS, Windows 사용자분들이나, MySQL이 8.0대 버전이 아니신 분들은 명령어가 다를 수 있습니다.

 

1. MySQL 계정 생성하기

 

계정을 생성하기 전에 먼저 MySQL이 활성화되어 있지 않다면 아래 명령어를 통해 MySQL을 실행시켜주어야 합니다.

sudo systemctl start mysql

 

MySQL이 설치되어 있다고 가정하고, root로 로그인을 하기 위한 명령어는 다음과 같습니다.

mysql -u root -p

 

root 계정으로 이동 후, db에 접근 가능한 권한을 분리하여 적용하기 위해 계정을 생성합니다.

create user 'kose1'@'%'
identified with 'mysql_native_password' by 'password'
require none
password expire interval 30 day
account unlock
password history default
password reuse interval default
password require current default;

이때, 저는 다음과 같은 에러가 발생하였습니다.

--> ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

에러를 분석하면, 현재 작성한 'password'라는 패스워드가 mysql에 설정되어 있는 비밀번호 정책에 맞지 않다는 것입니다.

 

MySQL 서버의 비밀번호는 유효기간이나 이력관리를 통한 재사용 금지 기능뿐만 아니라, 비밀번호를 쉽게 유추할 수 있는 단어들이 사용되지 않도록 금칙어를 설정할 수 있습니다.

MySQL 서버에서 비밀번호의 유효성 체크 규칙을 적용하려면 validate_password 컴포넌트를 사용해야 합니다.

 

비밀번호 정책은 크게 3가지가 있으며, 기본은 MEDIUM입니다.

 

- LOW: 비밀번호의 길이만 검증
- MEDIUM: 비밀번호의 길이를 검증, 숫자와 대소문자, 특수문자의 배합을 검증
- STRONG: MEDIUM 레벨의 검증을 모두 수행, 금칙어의 포함 여부 검증 

 

현재, Mysql에서 medium으로 설정되어 있기 때문에 low로 바꾸고 다시 유저를 생성하면, 에러를 해결할 수 있습니다.

 

show variables like 'validate_password%';

//출력

+--------------------------------------+--------+
| Variable_name                        | Value  |
+--------------------------------------+--------+
| validate_password.check_user_name    | ON     |
| validate_password.dictionary_file    |        |
| validate_password.length             | 8      |
| validate_password.mixed_case_count   | 1      |
| validate_password.number_count       | 1      |
| validate_password.policy             | MEDIUM |
| validate_password.special_char_count | 1      |
| validate_password_check_user_name    | ON     |
| validate_password_dictionary_file    |        |
| validate_password_length             | 8      |
| validate_password_mixed_case_count   | 1      |
| validate_password_number_count       | 1      |
| validate_password_policy             | MEDIUM |
| validate_password_special_char_count | 1      |
+--------------------------------------+--------+
//mysql8 기준
set global validate_password.policy=LOW;
set variables like 'validate_password%';

+--------------------------------------+--------+
| Variable_name                        | Value  |
+--------------------------------------+--------+
| validate_password.check_user_name    | ON     |
| validate_password.dictionary_file    |        |
| validate_password.length             | 8      |
| validate_password.mixed_case_count   | 1      |
| validate_password.number_count       | 1      |
| validate_password.policy             | LOW    |
| validate_password.special_char_count | 1      |
| validate_password_check_user_name    | ON     |
| validate_password_dictionary_file    |        |
| validate_password_length             | 8      |
| validate_password_mixed_case_count   | 1      |
| validate_password_number_count       | 1      |
| validate_password_policy             | MEDIUM |
| validate_password_special_char_count | 1      |
+--------------------------------------+--------+

이후 다시 root 계정으로 계정 등록을 하면, 계정이 정상적으로 등록됩니다.

mysql> create user 'kose1'@'%'
    -> identified with 'mysql_native_password' by 'password'
    -> require none
    -> password expire interval 30 day
    -> account unlock
    -> password history default
    -> password reuse interval default
    -> password require current default;
Query OK, 0 rows affected (0.04 sec)

 

--> 계정 생성에 사용한 설정 정보는 다음과 같은 역할을 수행합니다.

 

● IDENTIFIED WITH

 

- 사용자 인증 방식과 비밀번호를 설정

- mysql 서버의 기존 인증 방식을 사용하고자 한다면, identified by 'password'형식으로 명시

 

 

 REQUIRE

 

- mysql 서버에 접속할 때, 암호화된 SSL/TLS 채널을 사용할지 여부를 설정

- 만약 require 옵션을 ssl로 설정하지 않았더라도, identified with에서 사용하는 사용자 인증방식을 Caching SHA-2 Authentication(mysql8 default) 인증 방식을 사용하면 암호화된 채널만으로 MySql 접속이 가능

 

 

 PASSWORD EXPIRE

 

- 비밀번호의 유효 기간을 설정하는 옵션

- 별도로 명시하지 않으면 defaulr_password_lifetime 시스템 변수에 저장된 기간으로 유효 기간이 설정

 

 

 PASSWORD HISTORY

 

- 한 번 사용했던 비밀번호를 재사용하지 못하게 설정하는 옵션

- password history default: password_history 시스템 변수에 저장된 개수만큼 비밀번호의 이력을 저장

- password history n: 비밀번호의 이력을 최근 n개까지만 저장

 

 PASSWORD REUSE INTERVAL

 

- 한 번 사용했던 비밀번호의 재사용 금지 기간을 설정

- 별도로 명시하지 않으면 password_reuse_interval 시스템 변수에 저장된 기간으로 설정

- password reuse interval default: 시스템 변수에 기입된 기간으로 설정

- password reuse interval n day: n일자 이후에 비밀번호를 재사용할 수 있도록 설정

 

 PASSWORD REQUIRE

 

- 비밀번호가 만료되어 새로운 비밀번호로 변경할 때, 현재 비밀번호를 필요로 할지 말지를 결정하는 옵션

- 별도로 명시되지 않으면 password_require_current 시스템 변수값으로 설정

- password require current: 비밀번호를 변경할 때, 현재 비밀번호를 먼저 입력하도록 설정

- password require optional: 비밀번호를 변경할 때, 현재 비밀번호를 입력하지 않아도 되도록 설정

- passowrd require default: password_require_current: 시스템 변수의 값으로 사용

 

 ACCOUNT ROCK / UNLOCK

 

- 계정 새성 시 또는 alter user 명령을 사용해 계정 정보를 변경할 때 계정을 사용하지 못하게 잠글지 여부를 결정

- account rock: 계정을 사용하지 못하도록 잠금

- account unlock: 잠긴 계정을 다시 사용 가능한 상태로 잠금 해제

 

 

2. DB 권한 부여하기

현재, 저의 MySQL에는 Employees라는 테이블이 구성되어 있습니다. 새로 생성한 계정에 select, insert, update 권한을 부여하기 위해  다음과 같은 명령어를 수행할 수 있습니다.

mysql> grant select, insert, update on employees.* to 'kose1'@'%';
Query OK, 0 rows affected (0.02 sec)

각 계정에 따라 다른 권한을 부여할 수 있는 것을 확인하기 위해 새로운 계정을 생성하고 권한을 부여하면 다음과 같습니다.

mysql> create user 'kose2'@'%'
    -> identified with 'mysql_native_password' by 'password'
    -> require none
    -> password expire interval 30 day
    -> account unlock
    -> password history default
    -> password reuse interval default
    -> password require current default;

mysql> grant select on employees.* to 'kose2'@'%';
Query OK, 0 rows affected (0.02 sec)

 

 

--> 생성한 계정으로 로그인하여 권한 적용 확인하기

mysql -u kose1 -p
Enter password:
//(아까 생성할 때 사용한 password 입력)

root 계정에서, kose1에 접근 가능한 권한을 select, insert, update만 부여했으므로,

delete 명령어는 실행되지 않는 것을 확인할 수 있습니다.

 

 

3. 역할(ROLE) 부여하기

MySQL에서는 '역할'을 담당하는 계정을 생성할 수 있는데, 이는 사용자 계정에 권한 부여의 목적으로 사용이 됩니다.

mysql> create ROLE
    -> role_emp_read,
    -> role_emp_write;
Query OK, 0 rows affected (0.04 sec)

mysql> grant select on employees.* to role_emp_read;
Query OK, 0 rows affected (0.04 sec)

mysql> grant insert, update, delete on employees.* to role_emp_write;
Query OK, 0 rows affected (0.02 sec)

 

생성한 kose1, kose2의 계정에 각각 다른 권한을 부여하면 다음과 같습니다.

mysql> grant role_emp_read, role_emp_write to 'kose1'@'%';
Query OK, 0 rows affected (0.03 sec)

mysql> grant role_emp_read to 'kose2'@'%';
Query OK, 0 rows affected (0.04 sec)

이때, 특정 계정을 로그아웃하면, root 계정에서 설정한 권한이 수행되지 않는 상황이 발생하였습니다.

이는 MySQL에서 설정한 권한 정보가 로그인 상태에서만 유효하도록 설정되어 있기 때문입니다.

이를 로그아웃되더라도 유지하도록 설정하려면 다음과 같은 명령어를 작성하면 됩니다.

-> root 계정
mysql> set global activate_all_roles_on_login=on;
Query OK, 0 rows affected (0.00 sec)

 

--> 권한 적용 확인하기

mysql> delete from departments where dept_no = 'd001';
ERROR 1142 (42000): DELETE command denied to user 'kose2'@'localhost' for table 'departments'

-> kose1 계정
mysql> delete from departments where dept_no = 'd001';
Query OK, 1 row affected (0.02 sec)

 

 

read (select) 권한만 부여 받은 kose2 계정은 delete 명령어를 수행하면 명령이 거부된 것을 확인할 수 있습니다.

반면, kose1 계정은 CRUD가 모두 적용된 기능을 수행할 수 있는 것을 확인할 수 있습니다.

 

역할 테이블을 생성하는 이유는 무엇일까?
역할과 계정은 내외부적으로 동일한 객체인데, CREATE ROLE 명령과 CREATE USER 명령을 구분하는 이유는
계정 생성과 권한 부여가 모두 가능한 사용자와 권한 부여만 가능한 사용저를 분리하여, 보안을 높이기 위함입니다.

 

이상으로, MySQL에서 계정을 생성하고 권한을 부여하는 과정을 정리하는 글을 마치도록 하겠습니다.

자세한 사항은 Real MySQL에서 확인하실 수 있습니다.

감사합니다.!

 

출처: Real MySQL (백은빈님, 이성욱 님 지음)

'DB' 카테고리의 다른 글

[DB] MySQL8.x 리플리카 서버 적용하기(1)  (0) 2023.06.10
[DB] 락(잠금)  (0) 2023.04.24
[DB] MySQL8.0 Index  (0) 2023.02.06
[DB] MySQL 엔진 아키텍처 (Real MySQL 8.0)  (0) 2023.01.22
[DB] 칼럼형 DBMS VS 로우형 DBMS  (0) 2022.12.28

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 칼럼형 DBMS와 로그형 DBMS를 비교하는 글을 작성하고자 합니다.

 

데이터베이스의 필드는 행과 열의 교차점이며 특정 자료형의 단일 값 입니다.

http://wiki.hash.kr/index.php/%ED%95%84%EB%93%9C_%28%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%29

'김모씨'는 첫 번째 행의 '이름'이라는 varchar(varchar2) 특정 자료형을 가진 값의 필드가 되는 것입니다. 데이터베이스는 디스크에 저장하는 방식에 따라, 칼럼형 DBMS와 로우형 DBMS로 나눌 수 있습니다.

https://dataonair.or.kr/db-tech-reference/d-lounge/expert-column/?mod=document&uid=52606

1. 로우형 데이터베이스

 

001 1001 KOSE 123-123
002 1002 GOSE 123-123

로우형 데이터베이스는 전통적인 DBMS 구조로 여러 필드의 값은 고유 식별 키로 구분할 수 있는 레코드 형식에 적합합니다. 특정 사용자의 요청이 여러 컬럼을 포함하는 데이터를 요구한다면 이러한 로우형 데이터베이스가 적합할 수 있습니다. 

 

로우형 데이터베이스에 적합한 상황은 무엇일까?

특정 인덱스로 학교 정보를 가져와야 한다고 생각해 보겠습니다.

select * from Schools where school_id = 10;

 이 경우, 인덱스 10인 학교 로우 데이터를 읽게 됩니다. 이때 발생하는 이점이 공간 지역성입니다. 공간 지역성이란 a[0], a[1] 처럼 같은 데이터 배열에 연속적으로 접근할 때 참조된 데이터 근처에 있는 데이터가 잠시 후 사용될 가능성이 높은 것입니다. 하나의 로우에 데이터를 저장하므로, 행 단위의 데이터 요청이 많은 상황이라면 로우형 데이터가 유리할 수 있습니다.

 

 

2. 칼럼형 데이터베이스

칼럼형 데이터베이스는 데이터를 로우 단위가 아니라 수직 분할하여 저장합니다. 로우를 연속해 저장하는 방식과 달리 같은 칼럼끼리 디스크에 연속해 저장하는 방식입니다. 장점은, 특정 컬럼만 따로 읽는 경우가 많은 경우, 해당 데이터를 로우 단위로 읽지 않으므로 칼럼별로 파일을 저장하거나 세그먼트 단위로 저장하면 효율성을 높일 수 있습니다.

 

효율성이 증가하는 이유는 무엇일까?

 

사번: 1000:001,003,2000:002,3000:004

 

  • 같은 칼럼의 여러 값을 한 번에 읽으면 캐시 활용도와 처리 효율성이 높아집니다. 최근 CPU는 벡터 연산을 통해 한 번의 CPU 명령으로 많은 데이터를 처리할 수 있습니다.
  • 자료형 별로 저장하면 압축률도 증가합니다. 각 컬럼마다 타입에 맞는 적절한 압축 알고리즘을 적용할 수 있습니다.
  • 한 컬럼의 데이터를 직렬화하여 저장하여 로우 방식과 다르게 데이터를 PK로 설정하여 데이터에 매칭되는 값을 포인터로 설정하여 데이터를 처리합니다.

 

 

3.  데이터 베이스 효율적인 저장 방식

로그를 남겨야 하는 시스템과 (WAL, Write ahead logging)과 다중 동시성 제어(Multiversion concurrency control)가 필요한 온라인 트랜잭션 처리 시스템(OLTP) 성 업무는 로우 기반의 시스템으로 구현하는 것이 적합합니다.

성능을 높이기 위해서는 파티셔닝과 인덱스, 캐싱을 활용해야 하고 별도의 온라인 분석 처리 데이터베이스 (OLAP)를 적용하는 방법을 강구해야 합니다.

 

[다중 동시성 제어 시스템이 로우형 데이터 베이스가 적합한 이유는 무엇일까?]

MVCC는 동시 접근을 허용하는 데이터베이스에서 동시성을 제어하기 위해 사용하는 방법 중 하나입니다.
MVCC 모델에서 데이터에 접근하는 사용자는 접근한 시점에서 데이터베이스의 SnapShot을 읽습니다. Snapshot에 대한 데이터 변경이 완료될 때까지 만들어진 변경사항은 다른 데이터 베이스 사용자가 볼 수 없습니다. 사용자가 데이터를 업데이트하면 이전의 데이터를 덮어 씌우는 것이 아니라 새로운 버전의 데이터를 UNDO 영역에 생성합니다. 이후, 이전 데이터와 비교하여 변경된 내용을 기록합니다. 사용자는 마지막 버전의 데이터를 읽게 됩니다.

로우형 데이터베이스는 하나의 행에 대한 데이터를 연속된 시퀀스로 기록하기 때문에 다중 동시성 제어 시스템에서는 특정 값이 바뀔 경우 해당 로우 전체를 최신 버전의 데이터를 UNDO 영역에 생성하는 것입니다. 따라서, 컬럼형에 비해 속도가 빠를 수 있습니다.

 

<추가>
PostGreSqL은 주기적으로 VACUUM 하여 기존 데이터에 대한 처리를 진행
Oracle은 Rollback segment 방식을 활용하여 업데이트된 데이터를 새롭게 변경하고
이전 데이터는 Rollback segment에 보관하는 방식을 따릅니다. 

https://scorpio-mercury.tistory.com/32

 

참조: https://dataonair.or.kr/db-tech-reference/d-lounge/expert-column/?mod=document&uid=52606

+ Recent posts