우당탕탕
[동시성 제어 1편] 동시성 제어란? - 데이터가 꼬이지 않는 백엔드의 첫걸음 본문
동시성 제어란? - 데이터가 꼬이지 않는 백엔드의 첫걸음
안녕하세요!
오늘은 백엔드 실무에서 꼭 한 번은 마주치는 동시성 제어에 대해서 작성해 보려고 합니다.
회사에서 과제형식으로도 많이 나오는 문제이고 현금지급, 쿠폰 이벤트등과 같은 서비스를 만들 때 꼭 적용되어야 하는 부분이라 정독해 보시는 것을 추천드립니다.
1편에서는 기본적인 내용 설명을 진행하며 2~4편은 해결방법 설명과 예시코드를 작성해 보려고 합니다.
1. 동시성이란 무엇인가?
동시성(concurrency)이란, 여러 사용자가 ‘동시에’ 데이터나 시스템 자원(예: DB, 메모리 등)에 접근/변경을 시도하는 상황을 의미합니다.
예시: 쇼핑몰 재고 감소
• 유저A, B가 동시에 “마지막 1개 남은 상품”을 장바구니에 담고 동시에 주문을 진행
• 둘 다 주문이 성공하는 순간, 재고 데이터는 0, 심하면 -1이 될 수 있음
이것이 바로 유명한 Race Condition(경쟁 상태)입니다.
2. 왜 동시성 제어가 필요한가?
동시성 제어를 하지 않으면 어떤 문제가 생길까요? 실제 현업에서 흔하게 겪는 문제를 예시로 들어 보겠습니다.
🙅 데이터 꼬임(갱신 손실, Lost Update)
예시) 은행 계좌에 동시에 입금/출금 요청이 들어와
• 동시에 10만 원씩 입금/출금이 있으면
→ 마지막 트랜잭션만 반영, 한쪽 요청은 흔적도 없이 사라짐!
🙅 Dirty Read, Phantom Read
• 하나의 트랜잭션이 아직 커밋 안 된 데이터를 다른 트랜잭션이 읽거나
• 트랜잭션 중에 새로운 데이터가 쏟아져 엉뚱한 집계가 나옴
🙅 중복 지급/환불, 오작동하는 포인트/적립금
• 단 1ms 겹치면 돈이 두 배로 나가거나, 결제 건이 꼬임
• 주문/배송 등 실시간성이 중요한 비즈니스에서는 치명적
3. 동시성 제어 기술의 전체 큰그림
이런 문제를 막기 위해 데이터베이스와 애플리케이션에서는 아래와 같은 동시성 제어 메커니즘을 사용합니다.
해당 해결방법은 다음 편에서 상세하게 설명드리도록 하겠습니다 ( 비관적락, 낙관적락, 분산락 )
• 락(Lock)
-> 한 번에 한 명만 접근 가능! (비관적락, 낙관적락, 분산락 등)
• 트랜잭션 격리 수준(Transaction Isolation Level)
-> Read Uncommitted/Committed, Repeatable Read, Serializable 등
• 버전 관리(Optimistic Versioning)
-> 어떤 단위(행/테이블)에 버전 번호를 붙여 충돌 감지
4. 주로 발생하는 실무 동시성 문제 코드 예시
@Service
public class StockService {
@Autowired
private StockRepository repo;
// 아무런 락/제어도 없는 경우
public void order(Long stockId, int amount) {
Stock stock = repo.findById(stockId).get();
stock.setQuantity(stock.getQuantity() - amount); // Race Condition 위험
// 바로 저장/커밋
repo.save(stock);
}
}
겉으로 보이기에는 수량을 감소시키는 간단한 코드처럼 보입니다. 하지만 동시에 여러 트랜잭선이 실행되면 해당코드는 서로의 작업 결과를 덮어쓰는 현상(갱신 손실)이 발생하게 됩니다.
예를 들면 amount값이 1로 -1씩 감소하는 것을 생각했지만 겹쳐 -n씩 감소되는 현상이 발생할 수 있죠
5. 자바/스프링에서 동시성 제어, 어떻게 시작하나?
• JPA/Hibernate, MyBatis 등에서
락 걸기, 버전 관리, 트랜잭션 관리를 활용
• 간단하게는 @Transactional로 트랜잭션 단위 제어, @Lock (비관적락), @Version (낙관적락)으로 동시성 제어
이 외에도 마이크로서비스(MSA) 시대에는 Redis/Redisson 등 분산락을 애플리케이션 레벨에서 직접 쓰기도 합니다.
6. 다음 글 맛보기 ( 동시성 해결법 맛보기 )
• 비관적 락(Pessimistic Lock):
아예 “내가 끝날 때까지 접근 금지!” 물리적으로 데이터베이스를 잠가버리는 방법
• 낙관적 락(Optimistic Lock):
모두 접근은 허용, 커밋 직전 충돌을 체크해서 실패 시 재시도
• 분산락(Redis/Redisson 등):
DB 락 한계를 넘어 다수 서버/마이크로서비스가 동시에 충돌하지 않게 메모리 기반으로 관리
다음 연재 글에서는 이 세 가지를 한 가지씩, “실제 코드 중심 + 실전에서 언제/어떻게 적용할지”까지 디테일하게 파고들 예정입니다! 더 보고 싶으신 내용이나 글이 있다면 댓글에 남겨주시면 해당 주제로 작성해 보도록 하겠습니다.
'Tech > Spring' 카테고리의 다른 글
[동시성 제어 3편] 낙관적 락(Optimistic Lock) - @Version 어노테이션을 활용한 락 (2) | 2025.08.06 |
---|---|
[동시성 제어 2편] 비관적 락(Pessimistic Lock) - JPA 스프링으로 경험해보는 실전 가이드 (1) | 2025.08.05 |
[JPA] JPA로 게시글 CRUD 만들기 – 10분 만에 따라하기 (3) | 2025.07.29 |
[Spring] QueryDSL vs MyBatis vs JPA, 실무에서 언제 쓰는게 좋을까? (0) | 2025.04.29 |
[Spring] JPA 영속성 컨텍스트 (Persistence Context) 란? (0) | 2025.04.28 |