[2주차] Real MySQL 4장 발표자료

이민석's avatar
Nov 12, 2024
[2주차] Real MySQL 4장 발표자료

월간-CS, RealMySQL 토론스터디 2주차 (4장) 발표자료입니다. - [Ref]
11.11.월 ~ 11.12.화 까지 작성한 DIL을 기반으로 준비하였습니다. -
[Ref]

발표에 앞서

2주차 학습 진도는 MySQL 엔진InnoDB 엔진의 설명이 주를 이루고 있었습니다.
전체적으로 MySQL 엔진과 InnoDB 엔진의 핵심 설정값과 어떤 값들을 실제 운영환경에서 보면 좋을지 간단한 인사이트를 얻을 수 있었습니다.

4장 MySQL 아키텍처 (77~153p)

MySQL은 다음과 같은 아키텍처로 가동되고 있습니다.
이 중에서 핵심적인 부분만 꼽으면 MySQL 엔진스토리지 엔진인 것 같습니다.

ㅓMySQL 아키텍처

4.1. MySQL 엔진 (77~97p)

4.1.1. Handler API (79p)

Handler API는 MySQL 엔진과 스토리지 엔진이 소통하기 위해서 존재합니다.

위에서 말하는 Connection Handler와는 완전히 다른 존재인데,
구조적으로 보면 Proxy Pattern에 가깝고 그렇다면 Proxy Handler API 정도로 이름 짓는 것이 옳지 않았을까?
결과적으로 이 API 덕분에 다양한 스토리지 엔진을 연결하기 쉬운 것 같다.

SHOW GLOBAL STATUS LIKE 'Handler%';

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 575   |
...
| Handler_write              | 8     |
+----------------------------+-------+
18 rows in set (0.01 sec)

4.1.2. Threading Model (81~83p)

MySQL은 스레드 기반으로 작동하며 수십, 수백개의 스레드가 병렬적으로 처리됩니다.
이러한 스레드는 포어그라운드(Foreground)와 백그라운드(Background)로 구분됩니다.

당연히 스레드간 교착 문제도 발생할 수 있으나 당장 4.2.에서는 다루지 않습니다.

공식 문서에서도 포어그라운드와 백그라운드를 구분하는 차이를 다루지 않는데,
어떤 기준으로 스레드 타입을 구분하는지가 조금 궁급합니다. -
[Ref]

SELECT thread_id, name, type, processlist_user, processlist_host
FROM performance_schema.threads ORDER BY type, thread_id;

+-----------+---------------------------------------------+------------+------------------+------------------+
| thread_id | name                                        | type       | processlist_user | processlist_host |
+-----------+---------------------------------------------+------------+------------------+------------------+
|         1 | thread/sql/main                             | BACKGROUND | NULL             | NULL             |
...
|        46 | thread/mysqlx/acceptor_network              | BACKGROUND | NULL             | NULL             |
|        43 | thread/sql/event_scheduler                  | FOREGROUND | event_scheduler  | localhost        |
|        47 | thread/sql/compress_gtid_table              | FOREGROUND | NULL             | NULL             |
|        49 | thread/sql/one_connection                   | FOREGROUND | root             | localhost        |
+-----------+---------------------------------------------+------------+------------------+------------------+

유의미한 부분은 커넥션 1개당 1개의 포어그라운드 스레드를 생성하는 부분입니다.
후술할 메모리 할당 및 구조에 따르면 각 커넥션 스레드마다 개별 로컬 메모리 영역을 독립적으로 사용하게 될 것 같다는 생각이 듭니다.

따라서 max_connections 값을 올리면 시스템의 여유 메모리 용량이 줄어들고
전체적으로 innodb_buffer_pool_size를 추가 조정할 공간이 부족해지며
동시에 데드락 자동 조정 과정에서 트래픽 부하를 버티지 못하는 순간이 올 수도 있을 것 같다는 생각이 듭니다.

4.1.3. 메모리 할당 및 사용 구조 (84~85p)

MySQL에서 사용되는 메모리 영역은 글로벌 영역로컬 영역으로 구분됩니다.
글로벌 영역은 공유 메모리로서 사용되며 로컬 메모리는 커넥션 별 독립 메모리로서 사용되는 것 같습니다.

메모리 관점에서 중요한 사고방식은 메모리를 쓰는 “대상이 누가 있는가?”부터 시작되는 것 같습니다. 당장 간단하게 생각나는 주체는 OS, MySQL 프로세스, InnoDB 버퍼풀 등이 있을 것 같습니다.

4.1.4. 스토리지 엔진 (86~89p)

스토리지 엔진 리스트업을 할 수 있고 중요한 부분은 Support칼럼의 값이다.

mysql> SHOW ENGINES;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)

Support 항목의 값은 크게 Default, YES, NO, DISABLE로 구성됩니다.
각 속성의 뜻에 따라서 PoC를 해보면 테이블 생성 시 Default, YES는 선택할 수 있으나 그 외는 에러가 발생합니다.

-- Support=Default
CREATE TABLE test_table_1 (fd1 INT, fd2 INT) ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)

-- Support=YES
CREATE TABLE test_table_2 (fd1 INT, fd2 INT) ENGINE=MyISAM;
Query OK, 0 rows affected (0.00 sec)


-- Support=NO
CREATE TABLE test_table_3 (fd1 INT, fd2 INT) ENGINE=FEDERATED;
ERROR 1286 (42000): Unknown storage engine 'FEDERATED'

다만 Disable인 친구를 YES, NO로 변경하고 싶었는데,
SET, SET PERSIST, SET PERSIST_ONLY 중 무엇으로도 변경되지 않았습니다.

SET disabled_storage_engines='MyISAM';
SET PERSIST disabled_storage_engines='MyISAM';
SET PERSIST_ONLY disabled_storage_engines='MyISAM';

SHOW GLOBAL VARIABLES LIKE 'disabled_storage_engines'; -- 여전히 NULL
SHOW ENGINES;                                          -- 여전히 Support가 YES인 것을 알 수 있음

MySQL 5.7, 8.0에서는 각기 정해진 형식으로 my.cnf에 값을 추가하라고 되어 있었으나 그 부분도 작동하지 않는 것 같습니다. - [Ref]

4.1.5. 플러그인 (88p)

MySQL에서는 미리 구현된 기능이 담긴 플러그인을 설치해 사용 가능합니다.
다양한 플러그인들이 존재하며 일부는 MySQL 바이너리 설치 시점에 같이 옵니다.

mysql> SHOW PLUGINS; -- SHOW PLUGINS\G;
+---------------------------------+----------+--------------------+---------+---------+
| Name                            | Status   | Type               | Library | License |
+---------------------------------+----------+--------------------+---------+---------+
| binlog                          | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
...
| mysqlx                          | ACTIVE   | DAEMON             | NULL    | GPL     |
+---------------------------------+----------+--------------------+---------+---------+
45 rows in set (0.00 sec)

이런 플러그인은 3가지 제한사항을 가지고 있습니다.

  1. Plugin은 오직 MySQL 서버와 인터페이스할 수 있고, 플러그인끼리 통신할 수 없음

  2. Plugin은 MySQL 서버의 변수나 함수를 직접 호출하기 때문에 안전하지 않음 (캡슐화 안함)

  3. Plugin은 상호 의존 관계를 설정할 수 없어서 초기화가 어려움

사실 크게 공감이나 이해가 되는 부분은 아닌 것 같습니다.

4.1.6. 컴포넌트 (89~90p) - [Ref]

플러그인의 단점을 해소하기 위해 컴포넌트가 나왔다고 합니다.

SELECT * FROM mysql.component;

그런데 왜 기본값으로는 컴포넌트가 아닌 플러그인을 쓰는지 정확히 모르겠습니다.

4.7. 쿼리 실행 구조 - [Ref]

쿼리는 구체적으로 4단계로 진행됩니다.
이 중에서 옵티마이저가 제일 중요합니다.
이 4단계는 모두 후술하겠습니다.

쿼리 파서 → 전처리기 → 옵티마이저 → 쿼리 실행기

4.1.8. 캐시 쿼리 (93p) - [Ref]

MySQL 8.0에서 제거

4.1.8. 스레드 풀 (93~95p) - [Ref]

MySQL Community에서 미지원

4.1.9. 트랜잭션 지원 메타데이터 (95~97p) - [Ref]

MySQL 5.7은 테이블 구조와 스토어드 프로그램을 각자 특정 파일에 저장하였습니다.
따라서 트랜잭션을 지원하지 않아서 장애 상황에 대해서 매우 취약합니다.

MySQL 8.0은 두 데이터를 mysql.idb 파일에 기록하며 InnoDB에 위임합니다.
따라서 트랜잭션을 지원하며 장애 상황에 내결함성이 조금 생긴 상태입니다.

데이터 저장소 경로를 찾기 위해서 아래 쿼리를 사용할 수 있습니다.

mysql> SHOW VARIABLES LIKE 'datadir';
+---------------+--------------------------+
| Variable_name | Value                    |
+---------------+--------------------------+
| datadir       | /opt/homebrew/var/mysql/ |
+---------------+--------------------------+
1 row in set (0.01 sec)

이후 실제로 idb2sdi 유틸을 사용해서 파일을 읽을 수 있습니다.

ibd2sdi /opt/homebrew/var/mysql/mysql.ibd

배열이 너무 길어서 불편하면 yq를 사용할 수 있습니다.

ibd2sdi /opt/homebrew/var/mysql/mysql.ibd | yq 'keys'
ibd2sdi /opt/homebrew/var/mysql/mysql.ibd | yq '.[1]'

4.2. InnoDB 스토리지 엔진 아키텍처 (97~p)

MySQL 서버의 스토리지 엔진의 기본값인 InnoDB는 다음의 아키텍처를 그립니다.

4.2.1. PK에 의한 클러스터링 데이터 저장 (99p) - [Ref]

InnoDB의 모든 테이블은 기본적으로 PK에 의해서 클러스터링 되어 저장됩니다.

  1. 개발자가 Primary Key를 명시적으로 선언할 수 있습니다.

  2. 선언하지 않으면 열이 NOT NULL인 첫 번째 Unique Index가 PK로 할당됩니다.

  3. 1,2 가 없는 경우 6 Byte의 숫자가 Cluster Index로서 PK로 자동 할당됩니다.

Cluster Index란 말의 의미는 무엇일까?
모든 레코드들은 Primary Key가 유사한 레코드들끼리 묶여서 저장됩니다.
따라서 쓰기 성능은 저하되지만 Primary Key 기반의 읽기 성능은 개선됩니다.
Index로 조회를 할 경우에도 Index, Primary Key 순으로 타고들어가며 읽기 성능이 개선됩니다. - Ref

(자세하게는 "8.8절-클러스터링 인덱스" 에서 후술하겠지만)
모든 세컨더리 인덱스(Seconary Index)는 레코드의 주소가 아닌 PK를 주소로 합니다.
따라서 세컨더리 인덱스를 참조하는 쿼리보다 PK를 참조하는 범위 스캔이 더 빠르게 처리됩니다.

https://dev.mysql.com/doc/refman/8.4/en/innodb-index-types.html

4.2.2. 외래키 지원 (99p) - [Ref]

  1. 외래키는 InnoDB 스토리지 엔진에서 지원되는 기능으로 MyISAM, MEMORY에서는 사용 불가합니다.

  2. 외래키는 운영 상의 불편함 때문에 사용하지 않는 경우가 있다.

  3. 외래키는 Index Lock 전파와 이로 인한 Dead Lock이 밠애할 수 있다.

  4. foreign_key_checks를 비활성화 하여 외래키 관계에 의한 체크 작업을 일시적으로 중단할 수 있다.

  5. foreign_key_checks를 비활성화 하면 ON DELETE/UPDATE CASCADE 옵션도 무시된다.

  6. 특정 기능에서만 이 옵션을 끄고 싶다면 SET SESSION foreign_key_checks=OFF를 사용할 수 있다.

4.2.3. 트랜잭션 격리 레벨 - [Ref]

MySQL에서는 시스템 변수에 따라서 트랜잭션 격리 레벨이 정의됩니다.

mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+
1 row in set (0.00 sec)

트랜잭션 격리 레벨은 ACID 중 격리성(Isolation)을 보장해줍니다.
기본값에서 트랜잭션 격리 레벨을 조정하면 안정성, 일관성, 결과의 재현성 간의 균현ㅇ을 미세 조정할 수 있습니다.

하지만 이로 인해서 Dirty Read, Phantom Read 등의 현상이 발생할 수 있습니다.
이 중에서 Phantom Read는 실제로 R

Option

Description

READ_UNCOMMITED

트랜잭션이 커밋되지 않은 내용도 읽을 수 있음

READ_COMMITED

트랜잭션이 커밋된 내용만 읽을 수 있음

REPEATED_READ

동일한 트랜잭션 내에서 읽은 데이터는 다른 트렌잭션에 의해서 변경되지 않음.

SERIALIZABLE

모든 트랜잭션이 직렬화된 것처럼 처리되어 트랜잭션 간 상호 영향을 미치지 않음

4.2.4. MVCC (100~103p) - [Ref]

MySQL에서는 트랜잭션을 활성화 하면 한 레코드에 다양한 버전이 존재할 수 있습니다. 이 경우 조회시 어떤 값이 나오는지는 트랜잭션 격리 레벨에 따라 다릅니다.

CREATE TABLE member (
    m_id INT NOT NULL,
    m_name VARCHAR(20) NOT NULL,
    m_area VARCHAR(100) NOT NULL,
    PRIMARY KEY (m_id),
    INDEX ix_area (m_area)
);

INSERT INTO member (m_id, m_name, m_area) VALUES (12, '홍길동', '서울');
UPDATE member SET m_area='경기' WHERE m_id=12;
SELECT * FROM member WHERE m_id=12;

위와 같은 쿼리를 실행하면 아래와 같은 상태가 됩니다.

하지만 기본값인 REPEATED_READ에서는 항상 '서울’ 이 반환되지만
READ_UNCOMMITED에서는 ‘경기’가 반환될 것입니다.

다만 READ_UNCOMMITTED 방식을 사용하는 사례가 존재하긴 할지 의문입니다.

4.2.5. 자동 데드락 감지 (104~105p) - [Ref]

InnoDB는 내부적으로 잠금이 교착 상태에 빠지지 않는지 체크하기 위해 잠금 대기 목록을 그래프(Wait for List) 형태로 관리한다.

InnoDB의 데드락 감지 스레드는 주기적으로 잠금 대기 그래프(Wait for List)를 검사하여 교착 상태에 빠진 트랜잭션 들을 찾아서 그 중 하나를 제거합니다.

교착 상태인 트랜잭션 중 어떤 것을 지울것인가?
일반적으로 언두 로그를 가장 적게 가진 트랜잭션이 롤백의 대상이 됩니다.

InnoDB는 MySQL Engine에서 관리하는 테이블 잠금인 LOCK TABLES은 인식할 수 없습니다.
따라서 이를 위해서 @@innodb_table_locks 옵션을 활성화 해두는 것이 좋습니다.

mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_table_locks';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| innodb_table_locks | ON    |
+--------------------+-------+
1 row in set (0.01 sec)

다만 InnoDB에서 동시 처리량이 많아지거나 각 트랜잭션의 잠금의 양이 많다면 데드락 감지 스레드로 인해서 많은 양의 CPU가 소모될 것입니다.
이 경우에는 기본적으로 비활성화하고 timeout을 통하는 것이 좋습니다.

mysql> SHOW GLOBAL VARIABLES WHERE Variable_name REGEXP '^(innodb_deadlock_detect|innodb_lock_wait_timeout)$';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_deadlock_detect   | ON    |
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
2 rows in set (0.00 sec)

4.2.6. 자동화된 장애 복구 (105~108p) - [Ref]

InnoDB는 손실 및 장애로부터 데이터를 보호하기 위한 여러 가지 매커니즘이 탑재되어 있습니다.
이를 이용해 MySQL 서버가 시작할 때 완료되지 못한 트랜잭션 및 디스크에 일부만 기록된 데이터 페이지 등에 대한 일련의 복구 작업이 자동으로 진행됩니다.

하지만 일부 치명적인 문제나 자동 복구가 불가능하면 MySQL 서버가 시작되지 않습니다. 이런 경우에 @@innodb_force_recovery 시스템 변수를 설정해서 MySQL 서버를 시작해야 합니다.

4.2.7. InnoDB 버퍼풀 (108~120p) - [Ref]

InnoDB 버풀은 MySQL 서버가 디스크 입/출력을 최소화하기 위해 사용됩니다.
MySQL 8.0 부터는 SELECT 뿐만 아니라 INSERT, UPDATE, DELET 쿼리의 결과도 모두 InnoDB 버퍼풀을 활용하여 처리됩니다.

기본 크기는 @@innodb_buffer_pool_size로 확인 가능합니다. - [Ref]
출력 수치를 계산하면 약 128MB가 나오는데, 도서에서 버퍼풀의 크기 단위가 128MB 라고 한 부분이 기억납니다.

SELECT @@innodb_buffer_pool_size;

+---------------------------+
| @@innodb_buffer_pool_size |
+---------------------------+
|                 134217728 |
+---------------------------+

이런 버퍼풀은 크게 3가지로 구성되어 있습니다.

  1. LRU 리스트 : 읽기 작업으로 LRU, MRU으로 구성되어 있음

  2. 플러시 리스트 : 쓰기 작업

  3. 프리 리스트 : 안쓰는 작업

기본적으로 InnoDB 버퍼풀은 클린 페이지와 더티 페이지로 구성되어 있습니다.

  • 클린 페이지(Clean Page) : 디스크에서 읽은 상태로 전혀 변경되지 않은 페이지

  • 더티 페이지(Dirty Page) : INSERT, UPDATE, DELETE 등으로 변경된 페이지

이 중 더티 페이지의 데이터는 디스크에 반영되어 있지 않습니다.
다만 이 기간이 길어지지 않도록 일정 기준마다 더티 페이지가 싱크됩니다.
이 과정을 일반적으로 플러쉬(Flush)라고 부릅니다.

플러쉬가 되기 전까지는 리두 로그 시퀀스(RLS)와 함께 리두 로그(Redo Log)가 기록되면서 버퍼풀에 있는 데이터가 수정됩니다. 이후 디스크에 반영이 되는 순간을 체크 포인트(Checkpoint)라고 부르며 체크 포인트 이후의 새로운 리두 로그의 용량을 체크 포인트 에이지(Checkpoint Age)라고 합니다.

이 크기는 @@innodb_log_file_size로 조회할 수 있습니다. - [Ref]
제 컴퓨터에서는 48 MB 정도 되는 공간을 차지하고 있습니다.

mysql> SELECT @@innodb_log_file_size;
+------------------------+
| @@innodb_log_file_size |
+------------------------+
|               50331648 |
+------------------------+
1 row in set (0.00 sec)

이러한 과정을 요약하면
주기적으로 버퍼풀에 있는 최신 정보와 디스크의 구 정보를 싱크한다고 할 수 있고
플러시 리스트 플러시*라고 부를 수 있습니다.

또한 LRU 리스트에서 미사용 부분이 제거되는 LRU 리스트 플러시*도 존재합니다.

4.2.7.1. 버퍼풀 백업 및 복구 (117~119p) - [Ref]

백업은 순식간에 이루어지지만 복구는 수분이상이 걸릴 수 있습니다.

  • 백업 : Table, Column의 metadata만 ib_buffer_pool에 저장하기에 그 용량이 수십 MB로 매우 작습니다.

  • 복구 : 각 Table, Column에서 data를 가져와야하기에 그 용량은 매우 많습니다.

각 쿼리를 이용해서 백업, 복구 및 복구 진행도를 확인할 수 있습니다.

mysql> SET GLOBAL innodb_buffer_pool_dump_now=ON; -- 백업
mysql> SET GLOBAL innodb_buffer_pool_load_now=ON; -- 복구

mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status'\G -- 복구 진행도 학인
*************************** 1. row ***************************
Variable_name: Innodb_buffer_pool_dump_status
        Value: Dumping of buffer pool not started
1 row in set (0.00 sec)

4.2.7.2. 버퍼풀의 적재 내용 확인 (119~120p)

버퍼풀에 적재된 내용을 조회할 수 있습니다.
큰 실익이 있는 부분인지 정확히 모르겠습니다.

mysql> SELECT
        it.name table_name,
        ii.name index_name,
        ici.n_cached_pages n_cached_pages
    FROM information_schema.innodb_tables it
        INNER JOIN information_schema.innodb_indexes ii         ON ii.table_id = it.table_id
        INNER JOIN information_schema.innodb_cached_indexes ici ON ici.index_id = ii.index_id
    WHERE it.name=CONCAT('monthly_cs', '/', 'member');
Empty set (0.00 sec)

4.2.8. 이중 쓰기 버퍼 (120~122p) - [Ref]

내결함성을 높이기 위해서 더티 페이지가 플러시 될때에는
데이터 유실 방지를 위해서 이중 쓰기 버퍼(Double Write Buffer)가 사용됩니다.
이 기능은 기본적으로 활성화 되어 있으나 아래 쿼리로 확인 가능합니다.

SELECT @@innodb_doublewrite

+----------------------+
| @@innodb_doublewrite |
+----------------------+
| ON                   |
+----------------------+
1 row in set (0.00 sec)

모든 페이지에는 체크썸(Checksum)과 지정 알고리즘이 존재합니다.

SELECT @@innodb_checksum_algorithm;

+-----------------------------+
| @@innodb_checksum_algorithm |
+-----------------------------+
| crc32                       |
+-----------------------------+
1 row in set (0.00 sec)

더티 페이지의 백업본을 이중 버퍼(Doublewrite Buffer)에 먼저 기록하고
이후 실제 레코드를 디스크에 작성한 후 두 기록의 체크썸을 비교한다는 것 같습니다.
만약 더티 페이지 플러시가 중간에 끊기면 두 데이터의 체크썸이 다를 겁니다.
이 경우에는 트랜잭션 로그를 활용하여 데이터를 복구합니다.

4.2.9. 언두 로그 (122~129p) - [Ref]

내용이 너무 길고 복잡해서 본질을 조금 흐리는 것 같습니다.
사실 앞서 MVCC에서 언두 로그가 잠시 나왔기에 그 용도를 짐작하기 쉽습니다.
아직 커밋되지 않은 트랜잭션의 변경사항이 기록되어 있는 곳이라고 생각하면 좋습니다.

4.2.10. 체인지 버퍼 (129~130p) - [Ref]

MySQL에서 레코드가 INSERT, UPDATE 등이 될때에는 데이터 파일을 변경하는 작업 뿐만 아니라 테이블에 포함된 인덱스를 업데이트 하는 작업도 필요하다.

인덱스를 업데이트하는 작업은 랜덤하게 디스크를 읽는 작업이 필요하므로 테이블에 인덱스가 많다면 이 작업은 많은 자원을 솜화게 된다.

  1. 체인지 버퍼는 임시공간이다.

  2. 유니크 인덱스는 체인지 버퍼를 사용할 수 없다.

  3. 체인지 버퍼에 있는 모든 데이터는 체인지 버퍼 머지 스레드에 의해 병합된다.

  4. 체인지 버퍼의 대상이 되는 작업을 @@innodb_change_buffering 옵션으로 제어 가능하다.

SELECT @@innodb_change_buffering

+---------------------------+
| @@innodb_change_buffering |
+---------------------------+
| all                       |
+---------------------------+
1 row in set (0.00 sec)

체인지 버퍼는 InnoDB 버퍼풀의 일정 공간을 사용한다고 한다.
기본값 25%에서 최댓값 50%를 가진다고 문서에서 정의됩니다. - [Ref]

SELECT @@innodb_change_buffer_max_size;

+---------------------------------+
| @@innodb_change_buffer_max_size |
+---------------------------------+
|                              25 |
+---------------------------------+
1 row in set (0.00 sec)

체인지 버퍼의 공간이 부족한 경우에도 플러시가 일어난다.

앞서 체크포인트 에이지의 크기에 대해서 말할때 @@innodb_log_file_size를 말했습니다. 이외에도 @@innodb_max_dirty_pages_pct, @@innodb_io_capacity 등에 의해서 체크포인트 주기가 조절됩니다.

SELECT @@innodb_max_dirty_pages_pct;
+------------------------------+
| @@innodb_max_dirty_pages_pct |
+------------------------------+
|                    90.000000 |
+------------------------------+
1 row in set (0.00 sec)


SELECT @@innodb_io_capacity;
+----------------------+
| @@innodb_io_capacity |
+----------------------+
|                  200 |
+----------------------+
1 row in set (0.00 sec)

이 중에서도 코어가 되는 벨류인 @@innodb_max_dirty_pages_pct를 낮게 수정하면 더티 페이지 플러시가 너무 자주 일어나서 디스크 병목이 발생할 수 있다. - [Ref]

최종적으로 체인지 버퍼 중에서 실제로 쓰고 있는 부분을 볼 수도 있습니다.

SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED
FROM performance_schema.memory_summary_global_by_event_name
WHERE EVENT_NAME='memory/innodb/ibuf0ibuf';

+-------------------------+------------------------------+
| EVENT_NAME              | CURRENT_NUMBER_OF_BYTES_USED |
+-------------------------+------------------------------+
| memory/innodb/ibuf0ibuf |                          136 |
+-------------------------+------------------------------+
1 row in set (0.00 sec)

4.2.11 리도 로그 (130~137p)

리도 로그는 InnoDB의 트랜잭션 로그 2개 중 하나입니다. - [Ref]
전체적으로 MySQL 충돌 시 복구 및 테이블 스페이스에 대한 트랜잭션 백그라운드 플러시 등에서 쓰인다고 합니다.

리두 로그는 ACID 중 내구성(Durability)을 의미합니다.
내구성이 높다라는 뜻은 데이터가 손실(유실)될 가능성이 낮음을 의미합니다.

트랜잭션 격리 레벨이 ACID 중 격리성(Isolation)을 보장한다. - [Ref]

이 설정값은 @@innodb_flush_log_at_trx_commit으로 확인할 수 있습니다.

SELECT @@innodb_flush_log_at_trx_commit;

+----------------------------------+
| @@innodb_flush_log_at_trx_commit |
+----------------------------------+
|                                1 |
+----------------------------------+
1 row in set (0.00 sec)

설정값은 크게 0, 1, 2가 존재하며 1이 가장 내구성이 좋은 상태이다.
하지만 디스크 기록이 너무 빈번하게 일어나기 때문에 성능상의 문제가 발생하지 않을까 싶다.

Value

Transaction

0

1초에 한 번 리두 로그를 디스크로 기록하고 동기화
비정상 종료 시 최대 1초 동안의 트랜잭션은 커밋되더라도 변경 데이터가 손실될 수 있다.

1
(def)

매번 트랜잭션이 커밋될 때마다 디스크로 기록되고 동기화
트랜잭션이 커밋되면 해당 트랜잭션의 변경된 데이터는 사라진. (아마 기존 데이터를 말하는 것 같다...)

2

매번 트랜잭션이 커밋될 때마다 디스크로 기록, 1초마다 동기화
변경 데이터는 OS의 Change Buffer에 저장
MySQL 서버가 비정상 종료되더라도 OS Change Buffer로 이를 복구 가능
MySQL 서버와 OS가 비정상 종료되면 최대 1초 동안의 트랜잭션 커밋 데이터는 손실 가능

이런 리두 로그가 기록될 수 있는 용량과 수량은 다음의 @@innodb_log_file_size, @@innodb_log_files_in_group으로 정의됩니다. 기본값으로는 48MB, 2개 정도로 보입니다.

이 값은 InnoDB 버퍼풀이 쓰기 작업에 있어
체크 포인트 에이지(Checkpoint Age) 최대 용량에서 참조하는 값과 동일합니다.
두 내용이 모두 같은 리두 로그에 대해서 설명하고 있는 것 같습니다.

SHOW GLOBAL VARIABLES WHERE Variable_name REGEXP '^(innodb_log_file_size|innodb_log_files_in_group)$';

+---------------------------+----------+
| Variable_name             | Value    |
+---------------------------+----------+
| innodb_log_file_size      | 50331648 |
| innodb_log_files_in_group | 2        |
+---------------------------+----------+
2 rows in set (0.01 sec)

4.2.12. 어뎁티브 해시 인덱스 (137~141p)

InnoDB가 사용하는 B-Tree 인덱스로도 성능 부하를 견디기 어려운 순간에
자주 읽히는 페이지의 주소를 미리 저장해놓는 어뎁티브 해시 인덱스를 사용할 수 있습니다.

SELECT @@innodb_adaptive_hash_index;

+------------------------------+
| @@innodb_adaptive_hash_index |
+------------------------------+
|                            1 |
+------------------------------+
1 row in set (0.00 sec)

SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED
FROM performance_schema.memory_summary_global_by_event_name
WHERE EVENT_NAME='memory/innodb?adaptive hash index';

Empty set (0.00 sec);

Share article

Unchaptered