진행 중인 프로젝트에 채팅 기능이 있는데, 생성된 채팅을 MySQL에 저장하고 있었다.
그런데 채팅이라는 비정형 데이터에 MySQL 같은 관계형 데이터베이스를 사용하는 건 적합하지 않다고 느꼈다.
그래서 NoSQL인 MongoDB로 교체했다.
그런데 MySQL이 채팅 기능에 적합하지 않은 또 다른 이유가 있다고 한다. 바로 INSERT 작업이다.
채팅 기능은 채팅 전송을 할 때 마다 채팅 데이터를 DB에 저장해야 한다.
그렇기 때문에 채팅 전송 기능은 INSERT 작업이 동시에 수행되어야 한다.
하지만 MySQL은 채팅 기능 같이 반복적으로 INSERT 작업이 발생하는 기능에는 적합하지 않다고 한다.
이번 글에선 그 이유에 대해 정리해 보자.
🔍 MySQL이 쓰기에 약한 이유
MySQL의 엔진인 InnoDB은 디스크 기반 DB이다.
디스크 기반?? 그렇다면 당연히 인메모리 DB보단 데이터를 저장하고 조회하는 속도는 느릴 것이다.
CPU → 메모리 → 디스크 순으로 데이터가 오고 가기 때문이다.
그런데 개발자 커뮤니티를 보면 MySQL을 사용하지 않고 MongoDB를 사용하는 경우도 많다고 한다.
그런데 MongoDB도 디스크 기반 DB이지 않나..?
그렇다면 디스크 기반 DB라서 MySQL의 속도가 느린 것은 알겠는데, InnoDB엔진을 사용하는 MySQL이 MongoDB보다 INSERT 작업에 특별하게 느린 이유가 뭘까?
⚙️ 트랜잭션 처리
MySQL의 가장 중요한 특징이자 장점은 데이터 정합성이다.
데이터 정합성을 유지하기 위해선 자연스럽게 트랜잭션 개념으로 이어진다.
트랜잭션의 원칙 중 하나인 원자성의 의미는 ‘트랜잭션 내부의 작업은 모두 성공하거나, 모두 실패해야 한다’ 이다.
INSERT 작업 또한 DB 내부의 데이터 상태를 변경하는 작업이기 때문에 트랜잭션이 적용된다.
그렇다면 원자성 또한 적용되는 데, 이를 위해 Redo와 Undo 정보를 보관해야 한다.
그럼 OK. Redo Log과 Undo Log 보관을 위한 작업을 수행한다.
그리고 해당 변경 내용을 디스크에 반영하기 전에 메모리에 저장한다.
그리고 Commit이 정상적으로 수행된 경우 메모리에 있는 데이터 변경 내용이 디스크에 반영되고, DB에 반영된다.
음.. 아무리 봐도 하나의 데이터를 INSERT 하는 데, 많은 작업이 수행된다.
MySQL이 특별하게 느린 이유를 알겠다.
⚙️ Lock
InnoDB는 쓰기 작업을 수행할 때 레코드 수준의 잠금을 사용한다.
레코드 수준의 잠금이란 하나의 트랜잭션이 한 행에 대해 다른 트랜잭션이 접근하지 못하도록 잠금을 거는 것이다.
그렇기 때문에 이 과정에서 여러 트랜잭션이 채팅 데이터를 추가하고자 한다면 인덱스 추가를 위해 락 경합이 발생해 성능 저하가 발생한다.
→ 인덱스는 테이블의 고유한 값이어야 하기 때문에 다른 트랜잭션과 겹치지 않도록 락을 사용함
특히 채팅처럼 단일 테이블에 여러 스레드가 쓰기 요청을 할 경우 병목 현상이 심해진다.
🤔 결론
만약 속도 같은 성능을 고려한다면 Redis 같은 인메모리 DB가 적합할 듯싶다. Redis는 NoSQL이기도 하니까 괜찮을 듯?