채널톡 면접 D-3 — OS 동시성 + DB 트랜잭션 깊이파기
---
학습 순서 (3시간 블록 가이드)
| 블록 | 시간 | 주제 | 산출물 |
|---|---|---|---|
| 1 | 12:00–13:00 | OS 동시성 메커니즘 | 뮤텍스/세마포어/데드락 답변 라인 |
| 2 | 13:00–14:00 | DB 트랜잭션 ACID + 격리수준 | 4가지 격리수준 부정합 현상 매트릭스 암기 |
| 3 | 14:00–15:00 | MVCC + 낙관/비관 락 DB 구현 | 5/22 미해결 답변 라인 보강 |
| — | — | (휴식) | — |
| 4 | 16:00–18:00 | 모의 면접 Q&A | Raphael 채널에서 6문제 |
---
1. OS 동시성 메커니즘
1.1 뮤텍스 vs 세마포어 — 면접관이 자주 헷갈리게 묻는 포인트
| 항목 | 뮤텍스 (Mutex) | 세마포어 (Semaphore) |
|---|---|---|
| 본질 | 락 (소유 개념 O) | 카운터 (소유 개념 X) |
| 자원 수 | 1개 | N개 |
| 해제 권한 | **락을 획득한 스레드만** | 어떤 스레드든 가능 |
| 용도 | 임계 구역 보호 | 자원 풀 관리, 시그널링 |
| Java 대응 | `synchronized`, `ReentrantLock` | `Semaphore` (자바 표준) |
핵심 트랩: "Binary Semaphore (count=1)는 뮤텍스와 같은가?" → 다르다. 소유 개념이 없으므로 다른 스레드가 release 가능. 뮤텍스는 락 보유자만 해제.
1.2 데드락 4가지 조건 (Coffman conditions)
1. Mutual Exclusion (상호 배제) — 자원이 한 번에 한 스레드만 점유 가능
2. Hold and Wait (점유 대기) — 자원 보유한 채로 다른 자원 대기
3. No Preemption (비선점) — 강제로 자원 회수 불가
4. Circular Wait (순환 대기) — 대기 그래프에 사이클
예방 전략 (실무):
- 자원 획득 순서 고정 (Lock Ordering) — *순환 대기 제거*
tryLock(timeout)사용 — *점유 대기 + 비선점 완화*- 락 보유 시간 최소화
실 케이스 답변 라인 (사전 인터뷰 출제됨)
> "JPA에서 A 엔티티 → B 엔티티 순으로 락 거는 코드와, 다른 트랜잭션이 B → A 순으로 락 거는 코드가 같이 돌아가면 데드락이 발생합니다. 해결책은 두 가지였는데, 첫째는 락 획득 순서를 ID 오름차순으로 강제해서 순환 대기를 제거했고, 둘째는 락이 필요한 트랜잭션을 짧게 쪼개서 보유 시간을 줄였습니다. MySQL은 데드락 감지가 자동이라 한쪽이 자동 롤백되긴 하지만, 빈번하게 발생하면 모니터링 알람으로 잡고 코드 레벨에서 순서를 다시 점검했습니다."
1.3 프로세스 vs 스레드 — Spring/Tomcat 맥락
면접관이 "Java/Spring 7년차"라면 일반론보다 JVM 맥락으로 답해야 가산점:
> "Tomcat의 기본 스레드 풀(max-threads=200)이 요청당 스레드 모델이고, 스레드는 JVM 프로세스 내에서 힙을 공유하므로 객체 참조를 직접 넘길 수 있는 게 장점입니다. 대신 공유 변수 접근 시 synchronized나 ConcurrentHashMap 같은 동기화 장치가 필요했고, 이 부분이 늘 버그의 원천이었습니다. Java 21부터 Virtual Thread가 도입되면서 I/O 바운드 작업의 컨텍스트 스위칭 비용을 줄일 수 있게 됐는데, 저희는 아직 도입 전입니다."
1.4 Context Switching 비용 — 자주 놓치는 디테일
- **저장 대상**: 레지스터, PC, 스택 포인터, 메모리 맵
- **숨은 비용**: TLB 무효화 (프로세스 스위치 시), L1/L2 캐시 미스
- **스레드 vs 프로세스 스위치**: 스레드는 같은 주소 공간 → TLB 보존 → 더 가벼움
- **수치 감각**: 1회 컨텍스트 스위치 ≈ 1~10μs (캐시 미스 포함 시 더)
---
2. DB 트랜잭션 — ACID + 격리수준
2.1 격리수준 4가지 × 부정합 현상 매트릭스 (필수 암기)
| 격리수준 | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| READ UNCOMMITTED | ⭕ | ⭕ | ⭕ |
| READ COMMITTED | ❌ | ⭕ | ⭕ |
| REPEATABLE READ | ❌ | ❌ | ⭕ (MySQL InnoDB는 MVCC로 거의 방지) |
| SERIALIZABLE | ❌ | ❌ | ❌ |
2.2 부정합 현상 — 실 시나리오로 외우기
Dirty Read (커밋 안 된 데이터 읽음)
```
T1: UPDATE balance SET amount = 0 WHERE id = 1; -- 커밋 X
T2: SELECT amount FROM balance WHERE id = 1; -- 0 읽음
T1: ROLLBACK; -- T2는 존재하지 않은 값을 봤음
```
Non-Repeatable Read (같은 행, 다른 값)
```
T1: SELECT amount FROM balance WHERE id = 1; -- 100
T2: UPDATE balance SET amount = 50 WHERE id = 1; COMMIT;
T1: SELECT amount FROM balance WHERE id = 1; -- 50 (T1 입장에선 같은 트랜잭션 안인데 결과 다름)
```
Phantom Read (조건 일치 행 수 변화)
```
T1: SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 3
T2: INSERT INTO orders (user_id, ...) VALUES (1, ...); COMMIT;
T1: SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 4 (없던 행이 유령처럼)
```
2.3 실무 디폴트 — Spring/MySQL
- **MySQL InnoDB 기본**: REPEATABLE READ (MVCC로 Phantom 거의 방지)
- **Spring
@Transactional기본**: DB 디폴트 따름 (별도 명시 안 하면) - **실무 99%**: READ COMMITTED 또는 REPEATABLE READ로 충분
- **SERIALIZABLE**: 거의 안 씀. 성능 손실 큼.
면접 트랩: "왜 SERIALIZABLE 안 쓰나?" → 모든 읽기/쓰기에 락 → 동시성 처참. 차라리 비즈니스 로직 레벨에서 낙관/비관 락 직접 제어.
---
3. MVCC + 낙관/비관 락 DB 구현
3.1 MVCC (Multi-Version Concurrency Control)
핵심 아이디어: 읽기/쓰기 충돌을 락 대신 버전 관리로 해결.
```
T1 시작 (snapshot 시점 = SCN 100)
T2: UPDATE row → 새 버전 생성, Undo Log에 SCN 99 버전 보관
T1: SELECT row → SCN 100 시점 스냅샷 (=SCN 99 버전 봄, T2 변경 안 봄)
```
InnoDB의 REPEATABLE READ가 MVCC로 구현:
- 트랜잭션 시작 시점의 스냅샷 고정
- 읽기는 락 없음 → 동시성 ↑
- Undo Log가 길어지면 디스크 부하 ↑ (장기 트랜잭션 주의)
3.2 낙관적 락 — DB 구현 디테일
```java
@Entity
public class Order {
@Id Long id;
@Version Long version; // JPA가 자동으로 UPDATE 시 WHERE 조건에 추가
BigDecimal amount;
}
```
- UPDATE 시
WHERE id = ? AND version = ?자동 추가 - version 불일치 →
OptimisticLockException - **충돌 빈도 낮을 때 유리**, retry 비용 < 비관적 락 대기 비용
5/22 미해결 답변 보강: "낙관적 락 실패 시 처리"는 다음 3가지로 갈림:
1. 즉시 사용자 에러 (충돌 빈도 낮을 때) — "다시 시도해주세요"
2. 자동 retry with backoff (재시도로 해결 가능한 멱등 작업)
3. 비관적 락으로 fallback (충돌 빈도가 임계치 넘으면 코드 경로 전환)
3.3 비관적 락 — DB 구현 디테일
```java
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT o FROM Order o WHERE o.id = :id")
Order findByIdForUpdate(@Param("id") Long id);
```
- 내부적으로
SELECT ... FOR UPDATE발행 - 트랜잭션 commit/rollback까지 락 유지
- **충돌 빈도 높을 때 유리** (낙관적 락 retry 폭주 회피)
3.4 언제 무엇을 쓰나 — 판단 기준
| 조건 | 권장 |
|---|---|
| 충돌 빈도 < 5% | 낙관적 락 (성능 ↑) |
| 충돌 빈도 > 20% | 비관적 락 (retry 비용 ↑) |
| 결제·재고 차감 | 비관적 락 또는 idempotency key |
| 분산 환경 (여러 서비스) | 별도 코디네이션 (Redis lock, DB transaction outbox) |
| 단일 DB 내 | DB 락이 가장 깔끔 |
5/22 후속: Redis lock은 분산 코디네이션 용도지, 단일 DB 내 동시성 제어용이 아니다. 자세한 시나리오는 [Redis 분산락 + DB 트랜잭션 분리 시 정합성 문제](2026-05-22-redis-lock-db-commit-mismatch.html) 참조.
---
4. 내일 (5/23) 모의 면접 예상 질문 6개
Raphael 채널에서 순서대로 진행. 각 질문 후 답변 → 드릴다운.
Q1. 뮤텍스와 세마포어의 차이를 면접관이 처음 듣는 사람처럼 설명해보세요.
답변 라인: 소유 개념 차이 → 해제 권한 차이 → 용도 차이 (자원 보호 vs 시그널링).
Q2. 운영 중인 서비스에서 데드락이 발생했다는 알람이 왔다. 어떻게 진단하고 해결하시겠습니까?
답변 라인: 1) DB 로그에서 데드락 victim 트랜잭션 확인 → 2) 락 획득 순서 분석 → 3) 코드 레벨에서 ID 오름차순 락 또는 tryLock 도입 → 4) 모니터링 알람 임계치 설정.
Q3. REPEATABLE READ에서 Phantom Read가 발생하는 시나리오를 코드로 설명하시겠습니까?
답변 라인: 표준 SQL 정의로는 발생 가능. 단, MySQL InnoDB는 MVCC + Gap Lock으로 거의 방지. 표준과 실제 구현 차이 짚어야 가산점.
Q4. 낙관적 락을 쓰다가 충돌이 빈번해졌다. 비관적 락으로 바로 갈아탈 것인가?
답변 라인: 우선 충돌률 측정 → 임계치(예: 20%) 넘으면 전환 검토 → 단, 단순 전환 대신 로직 분리(낙관 우선 + N회 retry 후 비관 fallback) 또는 트랜잭션 범위 축소도 옵션.
Q5. MVCC에서 트랜잭션이 길게 열려있으면 어떤 문제가 발생하나요?
답변 라인: Undo Log 누적 → 디스크 부하 ↑ + 다른 트랜잭션의 read view 길어짐 → 장기 트랜잭션 회피, 읽기 전용 분리, 배치 작업 청크 단위.
Q6. Redis 분산락 vs DB 비관적 락 — 단일 DB 환경에서 어느 쪽을 추천하시겠습니까?
답변 라인: 단일 DB면 DB 비관적 락 (트랜잭션 경계 자동 일치). Redis는 분산 환경에서만 필요. 5/22 미해결 시나리오 답변 라인 그대로 인용 가능.
---
복습 일정
| 단계 | 날짜 | 완료 |
|---|---|---|
| Day 0 (초학습) | 2026-05-23 | ☐ |
| Day 7 | 2026-05-30 | ☐ |
| Day 37 | 2026-06-29 | ☐ |
| Day 127 | 2026-09-27 | ☐ |
참고
- [CS 인터뷰 커리큘럼 + 문제 뱅크](cs-interview-curriculum.html)
- [Redis 분산락 + DB 트랜잭션 분리 시 정합성 문제](2026-05-22-redis-lock-db-commit-mismatch.html)
- HAN-136: CS 인터뷰 커리큘럼 + 문제 뱅크 구축
- MySQL 공식 문서: InnoDB Locking and Transaction Model
- Spring
@TransactionalIsolation Level