Redis vs DB — 트랜잭션, ACID, Lock?
먼저 ACID, Lock 이 뭐냐 -> (사용할 기술 선택 시 알아야하는 개념임.)
1. ACID(트랜잭션이 반드시 보장해야 할 4가지 원칙)
1-1. Atomicity (원자성)
-
트랜잭션 내 작업은 전부 성공 or 전부 실패, 중간 상태 없음
-
내부 구현: Undo Log — 작업 전 상태를 로그에 기록, 실패 시 해당 로그로 롤백
1-2. Consistency (일관성)
- 트랜잭션 전후로 DB가 정의된 규칙(제약 조건, 외래키, 유니크 등)을 항상 만족
- DB 제약 조건 + 애플리케이션 로직이 함께 보장하는 속성
- 제약을 깨는 결과를 만드는 트랜잭션은 아예 거부됨
1-3. Isolation (격리성)
- 동시 실행 중인 트랜잭션이 서로의 중간 상태를 볼 수 없어야 함
- 격리성이 없을 때 발생하는 이상 현상
- Dirty Read: 커밋되지 않은 중간값을 다른 트랜잭션이 읽음
- Non-Repeatable Read: 같은 쿼리를 두 번 실행했을 때 결과가 다름 (중간에 다른 트랜잭션이 수정/커밋)
- Phantom Read: 같은 범위 쿼리를 두 번 실행했을 때 행의 수가 다름 (중간에 다른 트랜잭션이 삽입/삭제)
- 격리 수준 4단계 (높을수록 안전, 느림)
- READ UNCOMMITTED: 커밋 안 된 데이터도 읽음. Dirty Read 발생 가능
- READ COMMITTED: 커밋된 데이터만 읽음. Dirty Read 방지. 대부분의
DB 기본값
- REPEATABLE READ: 같은 트랜잭션 내 동일 쿼리는 항상 같은 결과.
MySQL InnoDB 기본값
- SERIALIZABLE: 트랜잭션을 완전히 순차 실행처럼 처리. 가장 강한 격리, 가장 느림
- 격리 수준이 높을수록 Lock을 더 많이, 더 오래 점유 → 성능 저하
1-4. Durability (지속성)
2. Lock
2-1. Lock이 필요한 이유
- Isolation(격리성)을 실제로 구현하는 메커니즘
- 동시에 여러 트랜잭션이 같은 데이터에 접근할 때 충돌 방지
2-2. 비관적 락 vs 낙관적 락
- 비관적 락 (Pessimistic Lock)
- "충돌이 날 거라 가정하고 미리 잠근다"
- 대표:
SELECT FOR UPDATE — 읽는 시점에 X-Lock 선점
- 안전하나 대기 시간 증가 가능
- 낙관적 락 (Optimistic Lock)
- "충돌이 드물다고 가정하고, 충돌 감지 시 처리한다"
- 버전 컬럼을 두고 UPDATE 시 버전 일치 여부 확인
- 버전이 다르면(중간에 다른 트랜잭션이 수정) 실패 처리 후 재시도
- 충돌이 드문 환경에서 성능 유리
2-3. Lock 범위
- Row-level Lock: 특정 행만 잠금. 동시성 높으나 관리 비용 큼
- Table-level Lock: 테이블 전체 잠금. 관리 단순하나 동시성 저하
- Gap Lock: 존재하지 않는 범위에 대한 잠금. Phantom Read 방지 목적
2-4. Deadlock (교착 상태)
- 두 트랜잭션(여러 작업을 하나로 묶어서 전부 성공 or 실패)이 서로 상대방의 락이 풀리길 기다리며 영원히 대기하는 상태
- ex: 트랜잭션 A가 row1 잠금 + row2 대기, 트랜잭션 B가 row2 잠금 + row1 대기 → 서로가 서로를 기다려 무한 대기
- DB가 주기적으로 Deadlock 감지 후 한쪽 트랜잭션을 강제 롤백시켜 해결
2-5. 비관적 락의 세분화
- Shared Lock (공유 락, S-Lock)
- 읽기 작업에 사용
- 여러 트랜잭션이 동시에 S-Lock 보유 가능
- S-Lock이 걸린 데이터에 X-Lock(쓰기)은 불가
- Exclusive Lock (배타 락, X-Lock)
- 쓰기 작업에 사용
- 단 하나의 트랜잭션만 보유 가능
- 다른 트랜잭션의 읽기/쓰기 모두 차단
DB vs MySQL vs InnoDB
계층 구조
DB (개념)
└── MySQL (DB 구현체 중 하나)
└── InnoDB (MySQL의 스토리지 엔진 중 하나)
각각 뭔지
DB (Database)
그냥 "데이터를 저장하는 시스템"이라는 개념
Oracle, MySQL, PostgreSQL, MongoDB 전부 DB
MySQL
DB를 구현한 소프트웨어 (RDBMS)
쿼리 파싱, 최적화, 캐시 등 담당
스토리지 엔진을 골라서 쓸 수 있음
InnoDB
MySQL 안에서 실제로 데이터를 읽고 쓰는 엔진
트랜잭션, 락, Undo Log, Redo Log 전부 여기서 처리
3. DB가 느린 이유
- 디스크 I/O: WAL 로그 기록 + 실제 데이터 파일 수정 모두 디스크 접근 발생. RAM 대비 HDD는 수천 배, SSD도 수백 배 느림
- Lock 경합: 동일 데이터에 다수 트랜잭션이 몰리면 Lock 대기 줄이 생기고, 대기 시간이 쌓이면 전체 처리량 급감
- 버퍼 풀 관리: DB는 자주 쓰는 데이터를 메모리(버퍼 풀)에 캐싱. 버퍼 풀에 없는 데이터 접근 시 디스크 I/O 발생 (Page Fault)
- 컨텍스트 스위칭: 멀티스레드 환경에서 스레드 간 전환에 CPU 비용 발생
4. Redis 구조와 속도
4-1. 인메모리
- 모든 데이터를 RAM에 저장 → 디스크 접근 없음
- RAM은 HDD 대비 수천 배, SSD 대비 수백 배 빠름
- 단점: 메모리는 휘발성 → 서버 꺼지면 데이터 소멸
- 보완 방법
- RDB: 특정 시점의 스냅샷을 디스크에 저장
- AOF: 모든 명령을 로그로 기록
- 단, 두 방법 모두 활성화하면 디스크 I/O 발생 → 속도 저하 트레이드오프
4-2. 싱글 스레드 이벤트 루프
- 명령을 하나씩 순서대로 처리
- 동시에 두 개의 명령이 실행되지 않으므로 Lock 자체가 불필요
- Lock 획득/해제 연산 없음, Deadlock 없음, Lock 경합 없음
- Redis의 병목은 CPU가 아니라 네트워크 I/O → 싱글 스레드로도 충분한 처리량
- Redis 6.0부터 네트워크 I/O 처리에 한해 멀티스레드 도입. 명령 실행 자체는 여전히 싱글 스레드
5. Redis 트랜잭션 — MULTI/EXEC
MULTI/EXEC -> Redis에서 트랜잭션을 구현하는 명령어
기본 사용법
MULTI ← 트랜잭션 시작
SET a 100
SET b 200
INCR a
EXEC ← 한꺼번에 실행DB

왜 쓰냐 ->
Redis는 싱글 스레드(일을 한 가지 주체가 순차적으로 실행)
→ MULTI/EXEC 사이 명령어들은 원자적으로 실행
→ 다른 클라이언트 명령이 중간에 끼어들지 못함
정리: Redis의 MULTI/EXEC는 완전한 트랜잭션이 아니라 명령어 묶음 실행 수준이고, ACID 중 완전한 보장은 못함.
즉, Redis는 싱글 스레드라 비관적 락(블로킹) 자체가 필요 없고, WATCH로 낙관적 락을 구현.
5-1. 동작 방식
- MULTI 선언 후 명령들은 즉시 실행되지 않고 큐에 쌓임
- EXEC 호출 시 큐의 명령들이 한 번에, 다른 클라이언트 개입 없이 순서대로 실행
5-2. DB 트랜잭션과의 차이
- Atomicity (불완전)
- EXEC 실행 중 특정 명령이 런타임 오류로 실패해도 나머지 명령은 계속 실행
- 자동 롤백 없음
- 단, 큐에 쌓는 단계(MULTI 이후)에서 문법 오류 발생 시 EXEC 자체가 실패하고 전체 취소
- Isolation (완전 보장)
- EXEC 실행 중 다른 클라이언트 명령 개입 불가
- 싱글 스레드 구조 덕분에 자연스럽게 보장
- Durability
- AOF
always 모드 설정 시 매 명령마다 디스크 기록 → 지속성 높아지나 속도 저하
6. Redis WATCH = 낙관적 락
6-1. 필요한 이유
- MULTI/EXEC만으로는 Race Condition 방지 불가
- EXEC 직전 다른 클라이언트가 내가 읽은 값을 변경하는 상황이 생길 수 있음
6-2. 동작 방식
WATCH balance -- 이 키 감시 시작
GET balance -- 현재값 읽기
-- 애플리케이션에서 로직 계산
MULTI
SET balance 500
EXEC -- WATCH 이후 balance가 변경됐으면 nil 반환 (트랜잭션 취소)
- WATCH 이후 해당 키가 변경되면 EXEC가 nil 반환 → 트랜잭션 전체 취소
- 재시도 로직은 개발자가 직접 구현
6-3. <-> 비관적 락과의 차이
- 비관적 락: 선점 후 차단 → 다른 트랜잭션이 아예 접근 못함
- WATCH: 충돌 감지 후 취소 → 충돌이 드문 환경에서 성능 유리, 충돌이 잦으면 재시도 반복으로 비효율
7. 전체 비교표
| 항목 | 관계형 DB | Redis MULTI/EXEC | Redis WATCH |
|---|
| 동시성 제어 | Lock (비관적) | 싱글 스레드 직렬화 | 낙관적 락 |
| Atomicity | 완전 보장, 자동 롤백 | 런타임 오류 시 롤백 없음 | 트랜잭션 취소만 (롤백 없음) |
| Consistency | 완전 보장 | 제한적 | 제한적 |
| Isolation | 격리 수준 설정 가능 | 완전 격리 | 완전 격리 |
| Durability | WAL로 완전 보장 | 설정에 따라 다름 | 설정에 따라 다름 |
| 충돌 시 처리 | 대기 or 롤백 | 해당 명령만 실패, 나머지 진행 | 트랜잭션 전체 취소 |
| 속도 | 느림 | 빠름 | 빠름 (충돌 없을 때) |
| 디스크 I/O | 항상 발생 | 기본 없음 (설정 시 발생) | 기본 없음 (설정 시 발생) |
8. 그래서
transanction acid 가 db 랑 어떻게 다른지-->
DB는 ACID를 완전하게 보장.
롤백도 되고, 격리 수준도 설정 가능하고, 디스크에 영구 저장.
이와 다르게
Redis는 ACID를 완전히 지원하지 않음.
MULTI/EXEC로 트랜잭션 비슷한 걸 제공하지만 런타임 오류 시 롤백이 없고, Durability도 설정에 따라 상이.
왜 빠른지 -->
DB가 느린 이유가 디스크 I/O + Lock 경합인데, Redis는 그 두 가지를 구조적으로 없앰.
인메모리 → 디스크 I/O 없음
싱글 스레드 → Lock 자체가 필요 없음
즉, ACID를 완전히 보장하는 비용을 포기한 대신 속도를 얻은 것이 Redis라고 할 수 있음.