ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MySQL Buffer Pool 알아보기
    Database 2023. 10. 30. 10:05
    728x90
    반응형

    - 목차

     
     

    함께 보면 좋은 글

    https://westlife0615.tistory.com/5

     

    MySQL Redo Log 알아보기

    - 목차 소개. MySQL 의 Redo Log 는 Write Query 에 해당하는 데이터의 변경을 저장합니다. Insert, Update, Delete 요청의 타겟이 되는 Rows 들의 변경사항을 기록합니다. 이러한 하나하나의 기록들을 Redo Log Entr

    westlife0615.tistory.com

     
    https://westlife0615.tistory.com/16

     

    MySQL Page 알아보기

    - 목차 함께 읽으면 좋은 글 https://westlife0615.tistory.com/8 MySQL Undo Log (Undo Tablespace) 알아보기 - 목차 소개. MySQL 은 Undo Log 라는 데이터 저장 영역이 있습니다. Undo 란 revert, rollback 과 같이 직전에 수행

    westlife0615.tistory.com

    https://westlife0615.tistory.com/155

     

    LRU (Least Recently Used) algorithm 알아보기

    - 목차 소개. 이번 글에서는 LRU 알고리즘에 대해서 알아보려고 합니다. LRU 알고리즘은 Least Recently Used Algorithm 의 약자인데요. Cache Memory 를 구현하기 위해서 흔히 사용되는 알고리즘이자 자료구조

    westlife0615.tistory.com

     

    소개.

    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 PoolTablespace 의 모습입니다.
    Buffer PoolTablespace 모두 동일한 데이터를 가지고 있습니다.
     
    < 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

     

    MySQL Redo Log 알아보기

    - 목차 소개. MySQL 의 Redo Log 는 Write Query 에 해당하는 데이터의 변경을 저장합니다. Insert, Update, Delete 요청의 타겟이 되는 Rows 들의 변경사항을 기록합니다. 이러한 하나하나의 기록들을 Redo Log Entr

    westlife0615.tistory.com

     
    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 AlgorithmLeast 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
Designed by Tistory.