-
MySQL Buffer Pool 알아보기Database 2023. 10. 30. 10:05728x90반응형
- 목차
함께 보면 좋은 글
https://westlife0615.tistory.com/5
https://westlife0615.tistory.com/16https://westlife0615.tistory.com/155
소개.
Buffer Pool 이란 MySQL 의 Data Page 와 Index Page 가 캐쉬되는 영역입니다.
Page 는 MySQL 에서 다루는 데이터의 묶음 또는 단위인데요.
기본적으로 16KB 사이즈를 가지며,
16KB 만큼의 데이터들이 묶여져서 관리됩니다.
Page 중에서 Data Page 는 MySQL 테이블의 Row 들을 의미하구요.
Index Page 는 MySQL 의 Index 데이터들을 뜻합니다.
Buffer Pool 은 Data Page 와 Index Page 가 캐쉬되는 MySQL 의 In-Memory 구성요소입니다.
< Buffer Pool 로 데이터가 Cache 되는 모습 >
데이터들은 Page 단위로 캐시됩니다.
MySQL 은 가능한 많은 양의 Page 들을 Buffer Pool 에 로드해둡니다.
이는 Disk IO 를 최소화하여 퍼포먼스를 끌어올리기 위함입니다.
그래서 대부분의 Read/Write Query 들은 Buffer Pool 에서 수행되도록 하며,
해당하는 데이터가 없을 경우에 (Cache Miss) Disk IO 를 발생시키는 전략을 취합니다.
Write Query 와 Buffer Pool 의 관계.
Insert, Update, Delete 를 DML Query 또는 Write Query 라고 부르죠.
이러한 Write Query 들은 데이터를 변형할 수 있는 요청입니다.
만약에 Buffer Pool 같은 버퍼가 존재하지 않는다면,
하나의 Write Query 는 하나의 On-Disk 데이터를 변경해야합니다.
즉, 1:1 형식으로 Write Query 과 Write Operation 이 동기적으로 처리되어야합니다.
하지만 이러한 방식은 병목현상을 야기합니다.
MySQL 이 Client Connection 을 허용하는 범위 내에서 수 많은 Write 요청이 발생할 수 있고,
이러한 네트워크 요청은 Disk IO 가 감당하기는 쉽지 않습니다.
동시다발적인 네트워크 트래픽을 Disk IO 가 빠른 속도로 처리할 수 없기 때문이죠.
Buffer Pool 은 In-Memory 버퍼로써 이러한 Write Query 의 버퍼로써 동작합니다.
그리고 병목현상과 같은 퍼포먼스를 위한 용도 뿐만이 아니라 Buffer Pool 은 Cache 영역으로써 역할을 합니다.
많은 양의 데이터들을 (Row of Tabe 그리고 Index) Buffer Pool 에 로드합니다.
가능한 많은 양의 데이터를 미리 로드시켜놓는 것이죠.
그리고 클라이언트의 Write Query 에 대해서 Cache Hit 가 된다면 Disk IO 가 발생하지 않고
In-Memory 단계에서 Write Query 를 처리할 수 있습니다.
이렇게 되면 Buffer Pool 의 데이터가 변경되고,
그 다음의 Read Query 에 대한 응답도 Buffer Pool 의 변형된 데이터를 응답합니다.
예시를 들어보도록 하겠습니다.
< test_user table >
아래의 경우는 test_user 테이블이 생성되었고, Andy 하는 user 가 생성됩니다.create table test_user( id int not null auto_increment primary key, name varchar(32) ); insert into test_user(name) values('Andy');
아래는 "Andy" 라는 test_user 가 추가된 이후의 Buffer Pool 과 Tablespace 의 모습입니다.
Buffer Pool 과 Tablespace 모두 동일한 데이터를 가지고 있습니다.
< Memory and Disk State >
이후 test_user 를 업데이트합니다.
"Andy" 라는 사용자의 이름이 "Bob" 으로 수정됩니다.
< update test_user >update test_user set name = 'Bob' where name = 'Andy';
test_user 의 row 가 "Andy" 에서 "Bob" 수정되면,
Buffer Pool 에선 "Bob" 으로 변경되지만, Tablespace 에서는 여전히 "Andy" 입니다.
이러한 현상을 Dirty Page 라고 부르죠.
두 상태의 동기화는 주기적으로 동기화됩니다.
하지만 동기화가 이루어지지 않더라도, Buffer Pool 의 존재하는 데이터를 기준으로 Read/Write 쿼리들이 동작합니다.
그래서 데이터의 일관성을 유지할 수 있습니다.
하지만 이러한 상황에서 MySQL 이 Sudden Crash 로 죽게된다면 큰 문제가 발생합니다.
Buffer Pool 은 메모리에 존재하기에 반드시 휘발되기 때문이죠.
이러한 상황을 Failure 와 Recovery 를 위해서 Redo Log 가 사용됩니다.
Redo Log 와 Buffer Pool 의 관계.
https://westlife0615.tistory.com/5
Buffer Pool 은 캐시로써 메모리 영역에 존재합니다.
그리고 Write Query 의 데이터 변경 작업은 Buffer Pool 에서 수행됩니다.
그래서 Memory -> Disk Flush 되는 과정이 발생하기 이전이라면
Memory 와 Disk 의 데이터들은 동기화되지 않습니다.
이러한 Memory -> Disk Flush 를 Checkpoint 라고 합니다.
Checkpoint 는 주기적으로 발생하는 작업이라 Checkpoint 이전에 MySQL 이 죽는다면
필연적으로 데이터의 손실이 발생합니다.
이러한 시스템의 맹점을 보완하는 기능이 바로 Redo Log 입니다.
Redo Log 는 MySQL Recovery 과정에서 사용됩니다.
Redo Log 는 모든 데이터의 변경사항을 기록하며,
MySQL 의 Recovery 상황에서 Flush 되지 않은 Buffer Pool 의 변경 사항을 반영해줍니다.
예를 들어 보겠습니다.
< 1번째 Checkpoint 상황 >
아래는 첫번쨰 Checkpoint 상황입니다.
Memory 와 Disk 는 서로 동기화되어 있습니다.Buffer Pool (In-Memory) Row1 {id:1, name:Andy} Row2 {id:2, name:Robin} --------------------------- Tablespace (On-Disk) Row1 {id:1, name:Andy} Row2 {id:2, name:Robin}
< 2번째 Checkpoint 이전에 데이터가 변경됨 >
아래는 2번째 Checkpoint 이전에 Buffer Pool 의 데이터가 변경됨을 보여줍니다.
Memory 와 Disk 는 서로 동기화되지 않았습니다.
Dirty Page 상태입니다.Buffer Pool (In-Memory) Row1 {id:1, name:Bob} // updated Row2 {id:2, name:Kevin} // updated --------------------------- Tablespace (On-Disk) Row1 {id:1, name:Andy} Row2 {id:2, name:Robin}
< Redo Log >
Redo Log 를 간략하게 표현해보았습니다.
각 Checkpoint 과정의 데이터 변경 사항들이 기록됩니다.Redo Log Checkpoint 1 Entry1 {Insert Andy} Entry2 {Insert Robin} Checkpoint 2 Entry1 {Update from Andy to Bob} Entry2 {Update from Robin to Kevin}
결과적으로
Checkpoint 2 가 수행되기 전에 MySQL 이 죽더라도
Tablespace 의 데이터와
Redo Log 의 Checkpoint 2 의 기록들을 토대로
Failure 시점의 데이터 상태를 복원할 수 있습니다.
sql read query 는 어떻게 Buffer Pool 에서 데이터를 찾을까 ?
Buffer Pool 의 내부 구조를 파악하는 것이 복잡하여 정확한 프로세스는 찾지 못하였습니다.
그래서 대략적인 프로세스를 설명드리려고 합니다.
select * from test_table where id = 1;
위와 같은 Primary Index 를 활용한 Read Query 가 요청된 경우를 가정하겠습니다.
이때에 해당 Data Row 가 Buffer Pool 에 있다면,
Disk 의 Data Page 를 조회하지 않고 Buffer Pool 의 Data Row 를 Client 로 응답하게 됩니다.
이를 Cache Hit 라고 합니다.
반대의 경우도 있겠죠.
Cache Miss 가 되는 경우에는 Disk 의 Data Page 를 메모리로 로드해야합니다.
Buffer Pool 로 Query 하는 과정은 상당히 복잡해서 현재로썬 매커니즘을 알 수 없었습니다.
저의 추측을 적어보자면
Query Optimizer 에 의해서 해당 데이터가 어떤 Page 에 존재하는지 알 수 있습니다.
그리고 Buffer Pool 의 자체적인 Indexing 구조를 활용해서 효율적으로 해당 페이지가 Buffer Pool 에 존재하는지 아닌지를 파악합니다. (Cache Hit or Miss)
그리고 존재하게 된다면 Buffer Pool 의 데이터를 사용하고,
그렇지 않으면 On-Disk 의 IO 가 발생하게 되는 것이죠.
LRU Algorithm.
Buffer Pool 은 LRU Algorithm 으로 페이지들을 관리합니다.
LRU Algorithm 은 Least Recently Used 의 약자인데요.
캐시의 특성상 제한된 메모리에서 효율적인 Hit Rate 를 위해서 LRU 알고리즘을 사용합니다.
LRU Algorithm 는 시간순서상 최근에 Hit 된 Page 가 LRU Cache 의 최상단에 위치하게 되고,
조회 시도가 거의 발생하지 않은 Page 들은 LRU Cache 에서 방출되는 대상이 됩니다.
즉, 최근에 조회된 Page 일수록 LRU Cache 에 오래 머물 수 있고,
조회가 없는 Page 는 LRU Cache 에서 방출됩니다.
아래는 Chat-GPT 에서 물어본 간단한 LRU Algorithm 이구요.
사이즈가 3인 LRU Cache 에서 가장 오래된 데이터인 "D" 가 방출되는 실험을 하는 코드 예시입니다.
< 간단한 LRU Algorithm >class LRUCache { constructor(capacity) { this.capacity = capacity; this.cache = {}; // Stores key-value pairs this.usageOrder = []; // Keeps track of the order of usage } get(key) { if (this.cache[key] !== undefined) { // If the key exists in the cache, update the usage order this.updateUsage(key); return this.cache[key]; } else { return -1; // Key not found } } put(key, value) { if (this.cache[key] !== undefined) { // If the key exists, update its value and usage order this.cache[key] = value; this.updateUsage(key); } else { if (this.usageOrder.length >= this.capacity) { // If the cache is full, remove the least recently used item const lruKey = this.usageOrder.shift(); delete this.cache[lruKey]; } // Add the new key-value pair this.cache[key] = value; this.usageOrder.push(key); } } updateUsage(key) { // Move the used key to the end of the usage order (most recently used) const index = this.usageOrder.indexOf(key); if (index !== -1) { this.usageOrder.splice(index, 1); this.usageOrder.push(key); } } } // Example usage: const lruCache = new LRUCache(3); // Create an LRU cache with a capacity of 3 lruCache.put('A', 1); lruCache.put('B', 2); lruCache.put('C', 3); console.log(lruCache.get('A')); // Output: 1 (A is the most recently used) console.log(lruCache.get('B')); // Output: 2 (B is the second most recently used) console.log(lruCache.get('D')); // Output: -1 (D is not in the cache)
반응형'Database' 카테고리의 다른 글
MySQL Index 알아보기 (0) 2023.10.30 MySQL Page 알아보기 (0) 2023.10.30 MySQL Undo Log (Undo Tablespace) 알아보기 (2) 2023.10.30 MySQL Redo Log 알아보기 (2) 2023.10.30 MySQL Lock 이해하기 (0) 2023.09.20