🧩 DB 락
DB 락은 DB에서 여러 트랜잭션이 동시에 같은 데이터에 접근할 때, 데이터의 무결성(일관성)을 보장하기 위해 사용되는 메커니즘이다.
한 트랜잭션이 특정 데이터에 대해 작업을 하고 있을 때 다른 트랜잭션이 그 데이터에 접근하지 못하도록 잠금으로써 데이터의 일관성을 유지하고 동시성 충돌을 방지할 수 있다.
DB 무결성(동시성) 문제
- Dirty Read: 한 트랜잭션이 데이터를 수정 중일 때 다른 트랜잭션이 그 데이터를 읽는 상황. 만약 첫 번째 트랜잭션이 롤백된다면, 두 번째 트랜잭션은 잘못된 데이터를 읽은 것이 된다.

- Non-repeatable Read: 한 트랜잭션이 데이터를 읽은 후, 다른 트랜잭션이 그 데이터를 수정하고 커밋하여 첫 번째 트랜잭션이 동일한 데이터를 다시 읽을 때 값이 달라지는 상황

- Lost Update: 두 개의 트랜잭션이 동시에 같은 데이터를 수정하려고 할 때, 한 트랜잭션의 수정 내용이 다른 트랜잭션에 의해 덮어쓰여져 사라지는 상황

DB 락의 종류
- 공유 락(S Lock): 데이터를 읽을 때 사용된다. 여러 트랜잭션이 동시에 같은 데이터를 읽을 수 있지만, 공유 락이 걸린 동안에는 데이터를 수정할 수 없다.
- 예: 고객 정보 시스템에서 여러 직원이 동시에 같은 고객의 정보를 조회할 수 있지만, 조회 중에는 그 정보를 수정할 수 없다.

- 배타 락(X Lock): 데이터를 수정할 때 사용된다. 배타 락이 걸린 데이터는 다른 트랜잭션이 읽거나 수정할 수 없다. 한 트랜잭션이 배타 락을 획득하면 다른 모든 트랜잭션은 해당 데이터에 접근할 수 없다.
- 예시: 재고 관리 시스템에서 한 직원이 특정 상품의 재고를 수정하고 있을 때, 다른 직원이 그 상품의 재고를 조회하거나 수정하지 못하게 한다.

- 비관적 락(Pessimistic Locking)
- 데이터를 읽을 때부터 락을 걸어 다른 트랜잭션이 접근하지 못하도록 하는 방식으로, 데이터의 충돌 가능성이 높을 때 유용하다.
- DB에서 특정 행(row)나 테이블에 락을 걸어 다른 트랜잭션이 동시에 동일한 데이터에 접근 및 수정을 못하게 막는다.
- MSA 환경에서는 DB 커넥션을 장기간 점유하여 전체 처리량을 저하시키고, 서비스 간 결합도 및 데드락 위험을 높이기 때문에 좋지 않은 선택이 될 수 있다.
- 예시: 은행 시스템에서 한 사용자가 특정 계좌의 잔액을 조회하고 수정하려는 시나리오에서, 다른 사용자가 이 계좌에 접근하지 못하게 한다.
public interface ItemRepository extends JpaRepository<Item, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE) // 비관적 락 적용
@Query("select i from Item i where i.id = :id")
Item findByIdWithLock(Long id);
}

- 낙관적 락(Optimistic Locking)
- 데이터를 수정하기 전까지 락을 걸지 않고, 수정 시점에만 충돌을 확인하는 방식
- 주로 데이터의 버전 번호를 사용하여 동시성 문제를 해결한다.
- 일반적으로 version이라는 필드를 엔티티에 추가해, DB에 저장된 버전 번호와 트랜잭션이 처음 읽어온 버전 번호를 비교해 번호가 달라졌으면 현재 트랜잭션을 롤백하거나 재시도하도록 한다.
- 예시: 온라인 쇼핑몰에서 여러 사용자가 동시에 동일한 상품의 정보를 수정할 수 있는 상황에서, 수정 시점에 충돌을 감지하여 해결한다.
@Data
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
@Version
private Integer version; // 버전 필드를 통해 낙관적 락을 구현
}
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
@Transactional
public void updateProductPrice(Long productId, Double newPrice) {
try {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
product.setPrice(newPrice);
// 저장 시 버전 충돌이 발생하면 예외 발생
productRepository.save(product);
} catch (ObjectOptimisticLockingFailureException e) {
// 낙관적 락 예외 처리
System.err.println("낙관적 락 충돌 발생");
throw e;
}
}
}

- 네임드 락(Named Lock)
- DB에서 특정 이름으로 락을 설정하여, 동시에 하나의 프로세스만 특정 리소스에 접근하도록 하는 방식
- 주로 특정 리소스나 작업에 대한 접근을 직관적으로 제어하기 위해 사용된다.
- 예시: PostgreSQL에서 특정 보고서를 생성하는 작업에 대해 이름 기반으로 락을 걸어, 두 명의 사용자가 동시에 같은 보고서를 생성하지 못하게 한다.

- 분산 락(Distributed Lock)
- 여러 시스템이나 인스턴스에서 동시에 동일한 자원에 접근할 때, 자원의 일관성을 유지하기 위해 사용되는 락
- Redis와 같은 분산 시스템을 사용해 구현된다.
- 온라인 예약 시스템에서 여러 서버 인스턴스가 동일한 좌석을 동시에 예약하지 못하도록 한다.

🧩 Redis 분산 락 구현 방식
Lettuce 방식 (Spin Lock)
Lettuce는 별도의 라이브러리 지원 없이 Redis 명령어를 직접 사용하므로 스핀 락(Spin Lock) 방식을 주로 쓴다. 락을 얻지 못한 클라이언트가 락이 해제되었는지 주기적으로 Redis에 물어보는 방식이다.
- 장점: 별도 라이브러리 없이 구현 가능, Redis 부하가 적은 환경에서 간단함
- 단점: 락을 획득할 때까지 계속 Redis에 요청을 보내므로 Redis 부하가 급증할 수 있음(CPU 낭비)

Redisson 방식 (Pub/Sub)
Redis의 Pub/Sub(채널 구독) 기능을 활용한다. 락을 획득하지 못한 클라이언트는 대기하다가, 락이 해제되었다는 알림(Message)을 Redis로부터 받으면 그때 락을 획득하러 간다.
- 장점: 불필요한 재시도 요청이 없어 Redis 부하가 매우 낮음
- 단점: 별도의 라이브러리 의존성 필요, Pub/Sub 구현으로 인해 로직이 다소 복잡함

💡 실제 운영 환경에서는 Redis 서버에 과부하를 주지 않는 Redisson 방식을 사용하는 것이 훨씬 안정적이다. Redisson은 락 자동 갱신(Watchdog) 기능까지 제공하여 락 유지 시간을 관리하기가 훨씬 수월하다.
🧩 데드 락
데드락은 두 개 이상의 트랜잭션이 서로가 점유하고 있는 자원을 기다리면서 영원히 대기 상태에 빠지는 상황을 의미한다.

- SERIALIZABLE 격리 수준
- SERIALIZABLE 격리 수준은 트랜잭션 간의 완벽한 격리를 보장하기 위해 트랜잭션들이 마치 순차적으로 처리되는 것처럼 보이도록 만든다.
- 이 수준에서는 한 트랜잭션이 데이터를 읽거나 수정하고 있는 동안 다른 트랜잭션은 해당 데이터에 접근할 수 없다.
- 두 스레드가 서로 다른 아이템에 대한 락을 시도하는 경우, 스레드 1이 아이템 1에 대한 락을 획득한 상태에서 아이템 2에 대한 락을 기다리고, 동시에 스레드 2가 아이템 2에 대한 락을 획득한 상태에서 아이템 1에 대한 락을 기다리는 상황이 발생할 수 있다.
- 이로 인해 두 스레드는 서로의 락이 해제될 때까지 무한 대기 상태에 빠지게 되고, 이것이 바로 데드락이다.
- 타임아웃 설정
- timeout = 1은 트랜잭션이 시작된 후 1초 내에 완료되지 않으면 타임아웃을 발생시킨다.
- 데드락 상황에서 두 트랜잭션이 서로의 락을 기다리게 되면, 1초 내에 트랜잭션이 완료되지 않으므로 트랜잭션 시스템이 타임아웃을 발생시키고, 이로 인해 트랜잭션이 실패하게 된다.
- 이는 데드락이 발생하면 무한히 대기하지 않고, 지정된 시간 내에 트랜잭션이 완료되지 않을 경우 타임아웃 예외를 발생시켜 데드락 상태를 빠르게 탐지하고 해결하려는 것이다.
'내일배움캠프' 카테고리의 다른 글
| [내일배움캠프] Spring AI (0) | 2026.06.09 |
|---|---|
| [내일배움캠프] DB 복제 지연, 메모리 릭, 캐시 압력 및 설정 버전 관리 (0) | 2026.06.05 |
| [내일배움캠프] 시큐어 코딩 (0) | 2026.06.04 |
| [내일배움캠프] 모니터링 시스템 (0) | 2026.06.02 |
| [내일배움캠프] 로깅 (0) | 2026.05.15 |