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

15293 단어 Real MySQLReal MySQL

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

이번에는 MySQL의 스토리지 엔진 가운데 가장 많이 사용되는 InnoDB 스토리지 엔진을 간단히 살펴보자. InnoDB는 MySQL에서 사용할 수 있는 스토리지 엔진 중에서 거의 유일하게 레코드 기반의 잠금을 제공하고 있으며, 때문에 높은 동시성 처리가 가능하고 또한 안정적이며 성능이 뛰어나다. 간단히 InnoDB의 구조를 그림으로 살펴보자.

1. InnoDB 스토리지 엔진의 특성

프라이머리 키에 의한 클러스터링

InnoDB의 모든 테이블은 기본적으로 프라이머리 키를 기준으로 클러스터링되어 저장된다. 즉, 프라이머리 키 값의 순서대로 디스크에 저장된다는 뜻이며, 이로 인해 프라이머리 키에 의한 레인지 스캔은 상당히 빨리 처리될 수 있다. 결과적으로 쿼리의 실행 계획에서 프라이머리 키는 기본적으로 다른 보조 인덱스에 비해 비중이 높게 설정(쿼리의 실행 계획에서 다른 보조 인덱스보다 프라이머리 키가 선택될 확률이 높음)된다. 오라클 DBMS의 IOT(Index organized table)와 동일한 구조가 InnoDB에서는 일반적인 테이블의 구조가 되는 것이다.

잠금이 필요 없는 일관된 읽기(Non-locking consistent read)

InnoDB 스토리지 엔진은 MVCC(Multi Version Concurrency Control)라는 기술을 이용해 락을 걸지 않고 읽기 작업을 수행한다. 락을 걸지 않기 때문에 InnoDB에서 읽기 작업은 다른 트랜잭션이 가지고 있는 락을 기다리지도 않는다. 읽기 작업이 가능하다(SERIALIZABLE 격리 수준은제외).

외래 키 지원

외래 키에 대한 지원은 InnoDB 스토리지 엔진 레벨이서 지원하는 기능으로 MyISAM이나 MEMORY테이블에서는 사용할 수 없다. 외래 키는 여러 가지 제약사항 탓에 실무에서는 잘 사용하지 않기 때문에 그렇게 필수적이지는 않지만 개발 환경의 데이터베이스에서는 좋은 가이드 역할을 할 수 있다. InnoDB에서 외래 키는 부모 테이블과 자식 테이블 모두 해당 칼럼에 인덱스 생성이 필요하고, 변경 시에는 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고, 그로 인해 데드락이 발생할 때가 많다.

자동 데드락 감지

InnoDB는 그래프 기반의 데드락 체크 방식을 사용하기 때문에 데드락이 발생함과 동시에 바로 감지되고, 감지된 데드락은 관련 트랜잭션 중에서 ROLLBACK이 가장 용이한 트랜잭션(ROLLBACK을 했을 때 복구 작업이 가장 작은 트랜잭션, 즉 레코드를 가장 적게 변경한 트랜잭션)을 자동적으로 강제 종료해 버린다. 따라서 데드락 때문에 쿼리가 제한시간(Timeout)에 도달하거나 슬로우 쿼리로 기록되는 경우는 많지 않다.

자동화된 장애 복구

InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 여러 가지 메커니즘이 탑재돼 있다. 그러한 메커니즘을 이용해 MySQL 서버가 시작될 때, 완료되지 못한 트랜잭션이나 디스크에 일부만 기록된 데이터 페이지(Partial write) 등에 대한 일련의 복구 작업이 자동으로 진행된다.

오라클의 아키텍처 내용

InnoDB 스토리지 엔진의 기능은 오라클 DBMS의 기능과 상당히 비슷한 부분이 많다. 대표적으로 MVCC 기능이 제공된다는 것과 언두(Undo) 데이터가 시스템 테이블 스페이스에 관리된다는 것, 그리고 테이블 스페이스의 개념 등이 있으며 이 외에도 상당히 흡사한 부분이 많아서 오라클에 익숙한 사용자에게는 InnoDB의 많은 부분들이 상당히 친숙할 것이다.

2. InnoDB 버퍼 풀

InnoDB 스토리지 엔진에서 가장 핵심적인 부분으로, 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다. 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할도 같이 한다. 일반적인 애플리케이션에서는 INSERT나 UPDATE 그리고 DELETE와 같이 데이터를 변경하는 쿼리는 데이터 파일의 이곳저곳에 위치한 레코드를 변경하기 때문에 랜덤한 디스크 작업을 발생시킨다. 하지만 버퍼 풀이 이러한 변경된 데이터를 모아서 처리하게 되면 랜덤한 디스크 작업의 횟수를 줄일 수 있다.

MyISAM 키 캐시가 인덱스의 캐시만을 주로 처리하는 데 비해 InnoDB의 버퍼 풀은 데이터와 인덱스 모두 캐시하고 쓰기 버퍼링의 역할까지 모두 처리하고 있는 것이다. 그 밖에도 InnoDB의 버퍼 풀은 많은 백그라운드 작업의 기반이 되는 메모리 공간이다. 따라서 InnoDB의 버퍼 풀 크기를 설정하는 파라미터(innodb_buffer_pool_size)는 신중하게 설정하는 것이 좋다.일반적으로 전체 물리적인 메모리의 80% 정도를 InnoDB의 버퍼 풀로 설정하라는 내용의 게시물도 있는데, 그렇게 단순하게 설정해서 되는 값은 아니며 운영체제와 각 클라이언트 스레드가 사용할 메모리도 충분히 고려해서 설정해야 한다. 일반적으로 전체 장착된 물리 메모리의 50~80% 수준에서 버퍼 풀의 메모리 크기를 결정한다.

InnoDB 버퍼 풀은 아직 디스크에 기록되지 않은 변경된 데이터를 가지고 있다(이러한 데이터를 가지고 있는 페이지를 더티 페이지(Dirty page)라고 한다. 이러한 더티 페이지는 InnoDB에서 주기적으로 또는 어떤 조건이 되면 체크포인트 이벤트가 발생하는데, 이때 Write 스레드가 필요한 만큼의 더티 페이지만 디스크로 기록한다. 체크포인트가 발생한다고 해서 버퍼 풀의 모든 더티 페이지를 디스크로 기록하는 것은 아니다.

3. 언두(Undo) 로그

언두 영역은 UPDATE 문장이나 DELETE와 같은 문장으로 데이터를 변경했을 때 변경되기 전의 데이터(이전 데이터)를 보관하는 곳이다. 예를 들어 다음과 같은 업데이트 문장을 실행했다고 해보자.

mysql > UPDATE member SET name='홍길동' WHERE member_id='1';

위 문장이 실행되면 트랜잭션을 커밋하지 않아도 실제 데이터 파일(데이터/인덱스 버퍼) 내용은 "홍길동"으로 변경된다. 그리고 변경되기 전의 값이 "벽계수"였다면, 언두 영역에는 "벽계수"라는 값이 백업되는 것이다. 이 상태에서 만약 사용자가 커밋하게 되면 현재 사용자가 그대로 유지되고, 롤백하게 되면 언두 영역의 백업된 데이터를 다시 데이터 파일(데이터/인덱스 버퍼)로 복구한다.

언두의 데이터는 크게 두 가지 용도로 사용되는데, 첫 번째 용도가 바로 위에서 언급한 트랜잭션의 롤백 대비용이다. 두 번째 용도는 트랜잭션의 격리 수준을 유지하면서 높은 동시성을 제공하는 데 사용된다. 트랜잭션의 격리 수준이라는 개념이 있는데, 이는 동시에 여러 트랜잭션이 데이터를 변경하거나 조회할 때, 한 트랜잭션의 작업 내용이 다른 트랜잭션에 어떻게 보여질지를 결정하는 기준이다.

4. 인서트 버퍼(Insert Buffer)

RDBMS에서 레코드가 INSERT되거나 UPATE될 때는 데이터 파일을 변경하는 작업뿐 아니라 해당 테이블에 포함된 인덱스를 업데이트하는 작업도 필요하다. 그런데 인덱스를 업데이트하는 작업은 랜덤하게 디스크를 읽는 작업이 필요하므로 테이블에 인덱스가 많다면 이 작업은 상당히 많은 자원을 소모하게 된다. 그래서 InnoB는 변경해야 할 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트를 수행하지만, 그렇지 않고 디스크로부터 읽어와서 업데이트해야 한다면 이를 즉시 실행하지 않고 임시 공간에 저장해 두고 바로 사용자에게 결과를 반환하는 형태로 성능을 향상시키게 되는데, 이때 사용하는 임시 메모리 공간을 인서트 버퍼(Insert Buffer)라고 한다.

사용자에게 결과를 전달하기 전에 반드시 중복 여부를 체크해야 하는 유니크 인덱스는 인서트 버퍼를 사용할 수 없다. 인서트 버퍼에 임시로 저장돼 있는 인덱스 레코드 조각은 이후 백그라운드 스레드에 의해 병합되는데, 이 스레드는 인서트 버퍼 머지 스레드(Merge thread)라고 한다. MySQL 5.5 이전 버전까지는 INSERT 작업에 대해서만 이러한 버퍼링이 가능했는데, MySQL 5.5부터는 INSERT나 DELETE로 인해 키를 추가하거나 삭제하는 작업데 대해서도 버퍼링이 될 수 있게 개선됐다. 또 MySQL 5.5 이전 버전에서는 별도의 파라미터 설정 없이 기본적으로 기능이 활성화됐지만 MySQL 5.5부터는 innodb_change_buffering이라는 설정 파라미터가 새로 도입되어 작업의 종류별로 인서트 버퍼를 활성화할 수 있으며, 인서트 버퍼가 비효율적일 때는 인서트 버퍼를 사용하지 않게 설정할 수 있도록 개선됐다.

5. 리두(Redo) 로그 및 로그 버퍼

쿼리 문장으로 데이터를 변경하고 커밋하면 DBMS는 데이터의 ACID를 보장하기 위해 즉시 변경된 데이터의 내용을 데이터 파일로 기록해야 한다. 하지만 이러한 데이터 파일의 변경 작업은 순차적으로 많은 데이터를 한꺼번에 변경하는 것이 아니고 랜덤하게 디스크에 기록해야 하기 때문에 디스크를 상당히 바쁘게 만드는 작업이다. 그래서 이러한 부하를 줄이기 위해 데이터의 DBMS에는 변경된 데이터를 버퍼링해 두기 위해 InnoDB 버퍼 풀과 같은 장치가 포함돼 있다. 하지만 이 장치만으로는 ACID를 보장할 수 없는데 이를 위해 변경된 내용을 순차적으로 디스크에 기록하는 로그 파일을 가지고 있다. 더 정확한 명칭은 리두 로그이며, 일반적으로 DBMS에서 로그라 하면 이 리두 로그를 지칭하는 경우가 많다.

MySQL 서버 자체가 사용하는 로그 파일은 사람들의 눈으로 확인할 수 있는 내용이 아니라서 편집기로 열어볼 수 없으며, 열어볼 필요도 없다. 리두 로드 덕분에 DBMS 데이터는 버퍼링을 통해 한꺼번에 디스크에 변경된 내용을 처리할 수 있고 그로 인해 상당한 성능 향상을 기대할 수 있게 된다. 하지만 사용량(특히 변경 작업)이 매우 많은 DBMS 서버의 경우에는 이 리두 로그의 로드 작업이 큰 문제가 되는데, 이러한 부분을 보완하기 위해 최대한 ACID 속성을 보장하는 수준에서 버퍼링하게 된다. 이러한 리두 로그 버퍼링에서 사용되는 공간이 로그 버퍼다.

로그 버퍼의 크기는 일반적으로 1~8MB 수준에서 설정하는 것이 적합한데, 만약 BLOB이나 TEXT와 같이 큰 데이터를 자주 변경하거나 하는 경우에는 더 크게 설정하는 것이 좋다.

ACID는 데이터베이스에서 트랜잭션의 무결성을 보장하기 위해 반드시 필요한 4가지 요소(기능)를 의미한다.
"A"는 Atomic의 첫 글자로, 트랜잭션은 원자성 작업이어야 함을 의미한다.
"C"는 Consistent의 첫 글자로, 일관성을 의미한다.
"I"는 Isolated에서 온 첫 글자로, 격리성을 의미한다.
"D"는 Durable의 첫 글자이며, 한번 저장된 데이터는 지속적으로 유지돼야 함을 의미한다.
일관성과 격리성은 쉽게 정의하기는 힘들지만, 이 두 가지 속성은 서로 다른 두 개의 트랜잭션에서 동일 데이터를 조회하고 변경하는 경우에도 상호 간섭이 없어야 한다는 것을 의미한다.

6. MVCC(Multi Version Concurrency Control)

일반적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS가 지원하는 기능이며, MVCC의 가장 큰 목적은 잠금을 사용하지 않는 일관된 읽기를 제공하는데 있다. InnoDB는 언두 로그를 이용해 이 기능을 구현한다. 여기서 멀티 버전이라 함은 하나의 레코드에 대해 여러 개의 버전이 동시에 관리도니다는 의미다. 이해를 위해 격리 수준(Isolation level)이 READ_COMMITTED인 MySQL 서버에서 InnoDB 스토리지 엔진을 사용하는 테이블의 데이터 변경을 어떻게 처리하는지 그림으로 한번 살펴보자.

우선 다음과 같은 테이블에 한 건의 레코드를 UPDATE해서 발생하는 변경 작업 및 절차를 확인해 보자.

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)
);

mysql > INSERT INTO member (m_id, m_name, m_area) VALUES (12, '홍길동', '서울');
mysql > COMMIT;

INSERT 문이 실행되면, 데이터베이스의 상태는 아래 그림과 같은 상태로 바뀔 것이다.

아래 그림은 MEMBER 테이블에 UPDATE 문장이 실행될 때의 처리 절차다.

mysql > UPDATE member SET m_area='경기' WHERE m_id=12;

UPDATE 문장이 실행되면 커밋 실행 여부와 관계 없이, InnoDB의 버퍼 풀은 새로운 값인 "경기"로 업데이트된다. 그리고 디스크의 데이터 파일에는 체크포인트나 InnoDB의 Write 스레드에 의해 새로운 값으로 업데이트돼 있을 수도 있고 아닐 수도 있다(InnoDB가 ACID를 보장하기 때문에 일반적으로는 InnoDB의 버퍼 풀과 데이터 파일은 동일한 상태라고 가정해도 무방하다).

아직 COMMIT이나 ROLLBACK이 되지 않은 상태에서 다른 사용자가 다음 같은 쿼리로 작업 중인 레코드를 조회하면 어디에 있는 데이터를 조회할까?

mysql > SELECT * FROM member WHERE m_id=12;

이 질문에 대한 답은 MySQL 초기화 파라미터에 설정된 격리 수준(Isolation level)에 따라 다르다. 만약 격리 수준이 READ_UNCOMMITTED인 경우에는 InnoDB 버퍼 풀이나 데이터 파일로부터 변경되지 않은 데이터를 읽어서 반환한다. 즉, 데이터가 커밋됐든 아니든 변경된 상태의 데이터를 반환한다. 그렇지 않고 READ_COMMITTED나 그 이상의 격리 수준(REPEATABLE_READ, SERIALIZABLE)인 경우에는 아직 커밋되지 않았기 때문에 InnoDB 버퍼 풀이나 데이터 파일에 있는 내용 대신 변경되기 이전의 내용을 보관하고 있는 언두 영역의 데이터를 반환한다. 이러한 과정을 DBMS에서는 MVCC라고 표현한다. 즉, 하나의 레코드(회원번호가 12인 레코드)에 대해 2개의 버전이 유지되고, 필요에 따라 어느 데이터가 보여지는지 여러 가지 상황에 따라 달라지는 구조다. 여기서는 한 개의 데이터만 가지고 설명했지만 관리해야 하는 예전 버전의 데이터는 무한히 많아질 수 잇다(트랜잭션이 길어지면 언두에서 관리하는 예전 데이터가 삭제되지 못하고 오랫동안 관리돼야 하며, 자연히 언두 영역이 저장되는 시스템 테이블 스페이스의 공간이 많이 늘어나야 하는 상황이 발생할 수도 있다).

지금까지 UPDATE 쿼리가 실행되면 InnoDB 버퍼 풀은 즉시 새로운 데이터로 변경되며 기존의 데이터는 언두(Undo)로 복사하는 과정까지 살펴봤는데, 이 상태에서 COMMIT 명령을 실행하면 InnoDB는 더 이상의 변경 작업 없이 지금의 상태를 영구적인 데이터로 만들어 버린다. 하지만 만약 롤백을 실행하면 InnoDB는 언두 영역에 있는 백업된 데이터를 InnoDB 버퍼 풀로 다시 복구하고, 언두 영역의 내용을 삭제해 버린다. 커밋이 된다고 언두 영역의 백업 데이터가 항상 바로 삭제되는 것은 아니다. 이 언두 영역을 필요로 하는 트랜잭션이 더는 없을 때 비로소 삭제된다.

7. 잠금 없는 일관된 읽기(Non-locking consistent read)

InnodB에서 격리 수준이 SERIALIZABLE이 아닌 READ-UNCOMMITTED나 READ-COMMITTED 그리고 REPEATABLE-READ 수준인 경우 INSERT와 연결되지 않은 순수한 읽기(SELECT) 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다. 아래 그림에서 특정 사용자가 레코드를 변경하고 아직 커밋을 수행하지 않았다 하더라도 이 변경 트랜잭션이 다른 사용자의 SELECT 작업을 방해하지 않는다. 이를 "잠금 없는 일관된 읽기"라고 표현하며, InnoDB에서는 변경되기 전의 데이터를 읽기 위해 언두(Undo) 로그를 사용한다.

오랜 시간 동안 활성 상태인 트랜잭션으로 인해 MySQL 서버가 느려지거나 문제가 발생할 때가 가끔 있는데, 바로 이러한 일관된 읽기를 위해 언두 로그를 삭제하지 못하고 계속 유지해야 하기 때문에 발생하는 문제다. 따라서 트랜잭션이 시작됐다면 가능한 빨리 롤백이나 커밋을 통해 트랙잭션을 완료하는 것이 좋다.

8. InnoDB와 MyISAM 스토리지 엔진 비교

지금까지는 MyISAM이 기본 스토리지 엔진으로 사용되는 경우가 많았다. 하지만 MySQL 5.5부터는 InnoDB 스토리지 엔진이 기본 스토리지 엔진으로 채택됐다. 기본 스토리지 엔진이 MyISAM이었기 때문인지는 모르겠지만 MySQL을 사용하는 많은 서비스가 별다른 고민 없이 MyISAM을 기본 스토리지 엔진으로 선택했다. 하지만 InnoDB 스토리지 엔진은 MyISAM과 비교할 수준이 아닐 정도로 많은 특징과 기능을 가지고 있으며 안정성 또한 MyISAM에 비할 바가 못 된다.

MyISAM 스토리지 엔진이 인덱스를 위한 키 캐시를 가지고 있지만 데이터 자체는 운영체제의 캐시에 의존하는 반면 InnoDB 스토리지 엔진은 자체적인 버퍼 풀을 가지고 좀 더 업무 특성에 맞는 캐싱이나 버퍼링을 수행한다. 트랜잭션 관리는 언급할 필요도 없고, 레코드 수준의 잠금 관리로 인해 동시성도 MyISAM을 훨씬 능가한다. 그나마 MyISAM의 전문 검색 기능이 MyISAM을 선택할 이유를 만들어 주기는 하겠지만 사실 검색 기능 또한 제약이 심하다. 이미 전문 검색을 위해서는 스핑크스(Sphinx)나 트리튼과 같은 서드파티 소프트웨어를 많이 사용하는 편이다.

sysbench 도구를 이용해 복잡한 형태의 쿼리가 포함된 트랜잭션으로 벤치마킹해 본 결과는 다음과 같다. 대체로 MyISAM에 비해 InnoDB가 월등한 성능을 보여준다는 것을 확인할 수 있다. 여기서 수치는 초당 트랜잭션의 처리 수이며, 평균적으로 하나의 트랜잭션에는 대략 10개 정도의 INSERT, UPDATE 그리고 DELETE와 SELECT 등의 다양한 쿼리가 포함돼 있다.

MyISAM과 InnoDB의 성능 비교는 무의미해 보일 정도로 차이가 난다. 단 한 가지 InnoDB의 단점이라면 MyISAM보다 MySQL 서버의 설정 튜닝이 아주 조금 까다롭다는 것이다.

읽기 방법 성능 비교
프라이머리 키
(데이터 파일 읽기 포함)
InnoDB가 6~9% 정도 빠름
벤치마킹 사용 쿼리
SELECT name FROM $tableName WHERE id = %d
보조 인덱스
(데이터 파일 읽기 포함)
64개 이하의 동시 스레드에서는 비슷함
그 이상의 동시 스레드에서는 InnoDB가 400~500% 빠름
벤치마킹 사용 쿼리
SELECT name FROM $tableName WHERE country_id = %d
보조 인덱스
(데이터 파일 읽기 포함 + LIMIT 사용)
256개 동시 스레드 이하에서는 InnoDB가 25~50% 빠름
그 이상에서는 거의 비슷함
벤치마킹 사용 쿼리
SELECT name FROM $tableName WHERE country_id = %d LIMIT 5
보조 인덱스
(커버링 인덱스)
InnoDB가 평균 30% 정도 빠름
벤치마킹 사용 쿼리
SELECT state_id FROM $tableName WHERE country_id = %d
보조 인덱스
(커버링 인덱스 + LIMIT 사용)
256개 동시 스레드 이하에서는 InnoDB가 80% 정도 빠름
그 이상에서는 거의 비슷함
벤치마킹 사용 쿼리
SELECT state_id FROM $tableName WHERE country_id = %d LIMIT 5
프라이머리 키
(커버링 인덱스)
성능 차이 없음
벤치마킹 사용 쿼리
SELECT id FROM $tableName WHERE id = %d
프라이머리 키 레인지 스캔
(일부 범위)
InnoDB가 200~250% 빠름
벤치마킹 사용 쿼리
SELECT min(dob) FROM $tableName WHERE id between %d and %d
프라이머리 키 레인지 스캔
(전체 범위)
256개 동시 스레드 이하에서는 InnodB가 30% 정도 빠름
그 이상에서는 MyISAM이 5% 정도 빠름
보조 인덱스 레인지 스캔
(데이터 읽기 포함)
동시 처리 스레드가 128개 이하에서는 성능 차이 없음
그 이상에서는 InnoDB가 600% 정도 빠름
벤치마킹 사용 쿼리
SELECT name FROM $tableName WHERE country_id = %d and %d
보조 인덱스 레인지 스캔
(데이터 읽기 포함 + LIMIT)
InnoDB가 평균 10% 정도 빠름
벤치마킹 사용 쿼리
SELECT name FROM $tableName WHERE country_id = %d and state_id between %d and %d LIMIT 50
보조 인덱스 레인지 스캔
(커버링 인덱스)
InnoDB가 20~30% 정도 빠름
벤치마킹 사용 쿼리
SELECT city FROM $tableName WHERE country_id = %d and state_id between %d and %d
보조 인덱스 레인지 스캔
(커버링 인덱스 + LIMIT)
InnoDB가 20~30% 정도 빠름
벤치마킹 사용 쿼리
SELECT city FROM $tableName WHERE country_id = %d and state_id between %d and %d LIMIT 50
풀 테이블 스캔 InnoDB가 20% 정도 빠름
벤치마킹 사용 쿼리
SELECT min(dob) FROM $tableName

9. InnoDB와 MEMORY(HEAP) 스토리지 엔진 비교

MEMORY 스토리지 엔진의 가장 큰 장점은 데이터와 인덱스를 모두 메모리에 저장하기 때문에 저장 작업이나 읽기 작업이 매우 빠르다는 것이다. 하지만 이 내용을 동시성 높은 것으로 오해하는 사용자가 많다. MEMORY 스토리지 엔진을 사용하는 테이블은 레코드 수준의 잠금이 아니라 테이블 수준의 잠금을 이용하게 된다. 이는 동시에 두 개 이상의 클라이언트가 테이블을 변경할 수 없음을 의미한다.

이 벤치마킹 결과에 의하면 MEMORY 스토리지 엔진은 동시 스레드 10개일 때 가장 빠른 성능을 내며, 이때도 초당 744번 정도의 트랜잭션을 처리한다. 대략 100만 건의 레코드가 저장된 MEMORY 테이블의 크기는 500MB로, 크진 않았지만 생각보다 낮은 성능을 보여줬다. MEMORY 스토리지 엔진 테이블에 1000만 건의 데이터를 입력하고 테스트했을 때는 초당 평균 250번 정도의 트랜직션을 처리하는 것으로 결과가 나왔다. 벤치마킹이 실행되는 동안 "SHOW PROCESSLIST" 명령으로 MySQL 서버의 프로세스 리스트를 확인해본 결과 대부분의 프로세스가 변경을 위해 테이블 잠금을 기다리고 있는 것으로 나타났다. 다음 결과를 보면 30번 프로세스가 현재 sbtest라는 테이블을 변경하고 있기(State='Updating') 때문에 다른 클라이언트의 UPDATE 명령은 처리되지 못하고 모두 대기하고 있음을 알 수 있다.

root@localhost:sb_memory 16:04:16> SHOW PROCESSLIST;
+----+---------+-----------+-----------+----------+------------------------------------------+
| Id | User    | db        | Command   | State    | Info
+----+---------+-----------+-----------+----------+------------------------------------------+
| 1  | root    | sb_memory | Query     | Null     | show processlist                         |
| 24 | sysuser | sb_memory | Execute   | Locked   | UPDATE sbtest set k=k+1 where id=500282  |
| 25 | sysuser | sb_memory | Execute   | Locked   | UPDATE sbtest set k=k+1 where id=299040  |
| 26 | sysuser | sb_memory | Execute   | Locked   | UPDATE sbtest set k=k+1 where id=501978  |
| 27 | sysuser | sb_memory | Execute   | Locked   | UPDATE sbtest set k=k+1 where id=502489  |
| 28 | sysuser | sb_memory | Execute   | Locked   | UPDATE sbtest set k=k+1 where id=503675  |
| 29 | sysuser | sb_memory | Execute   | Locked   | UPDATE sbtest set k=k+1 where id=503262  |
| 30 | sysuser | sb_memory | Execute   | Updating | UPDATE sbtest set k=k+1 where id=536736  |
| 31 | sysuser | sb_memory | Execute   | Locked   | UPDATE sbtest set k=k+1 where id=495684  |
| 32 | sysuser | sb_memory | Execute   | Locked   | UPDATE sbtest set k=k+1 where id=503203  |
'''
+----+---------+-----------+-----------+----------+------------------------------------------+

다음으로 동일한 쿼리를 이용한 InnoDB 스토리지 엔진의 벤치마크 결과를 확인해 보자. 아래 그림에 나와있는 결과를 보면 알 수 있듯이 InnoDB 스토리지 엔진은 동시 스레드가 32개인 경우 최대 성능을 보여줬으며 이때 처리된 초당 트랜잭션 수도 MEMORY 스토리지 엔진의 15배가 넘는 수준을 보여 준다. InnoDB 스토리지 엔진의 테이블은 일부러 MEMORY 테이블보다 10배인 1000만 건을 대상으로 테스트했지만 훨씬 더 높은 성능을 보여준다.

벤치마크 결과에서도 알 수 있듯이 메모리가 충분하다면 테이블 수준의 잠금을 사용하는 MEMORY 스토리지 엔진보다 레코드 수준의 잠금을 사용하는 InnoDB 스토리지 엔진이 훨씬 빠른 트랜잭션 처리를 보장해 준다. MEMORY 테이블은 여러 커넥션에 의해 읽기 위주로 사용되는 경우 또는 단일 커넥션으로 사용될 때는 적합하지만 동시에 많은 커넥션이 트랜잭션을 유발하는 OLTP 환경에서는 적합하지 않다.

참고

  • Real MySQL

좋은 웹페이지 즐겨찾기