-
[Spring] Lock 종류 정리 (낙관적 락, 비관적 락, 분산락, 데드락, 등) 및 예시Spring 2025. 5. 8. 10:21반응형
1. Lock이란?
1.1 설명
Lock은 동시에 여러 개의 트랜잭션이나 프로세스가 같은 리소스(데이터, 파일 등)에 접근하려고 할 때 발생할 수 있는 경쟁 조건(Race Condition)을 방지하기 위한 제어 장치입니다. 데이터 정합성을 보장하고 예기치 않은 충돌을 방지하기 위해 필수적인 메커니즘입니다.
1.2 원리
Lock은 주로 다음의 방식으로 작동합니다.
- 하나의 트랜잭션이 리소스를 점유하면, 다른 트랜잭션은 해당 리소스가 해제될 때까지 대기하거나 실패 처리됩니다.
- Lock의 종류에 따라 점유 방식(낙관적, 비관적)이나 위치(DB, 분산 시스템 등)이 달라집니다.
2. 낙관적 락 (Optimistic Lock)
2.1 낙관적 락이란?
낙관적 락은 충돌이 거의 발생하지 않을 것이라고 가정하고 처리하는 방식입니다. 실제 충돌이 발생했을 때만 충돌을 감지하고 예외 처리를 합니다. 주로 버전 번호나 타임스탬프를 이용하여 트랜잭션 간 충돌 여부를 판단합니다.
- 사용 예: 데이터 조회 후 변경이 드물 경우
장점
- 충돌이 적은 환경에서 성능 우수
- 락을 걸지 않아 Deadlock 우려 없음
단점
- 충돌 발생 시 예외 처리 필요 (재시도 로직 요구)
- 실시간 동시성 제어에는 부적합
2.2 JPA 샘플 코드 및 설명
Entity
@Entity @Getter @Setter public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @Version private Long version; }
멀티스레드 동시성 테스트
@SpringBootTest @ActiveProfiles("test") class OptimisticLockConcurrentTest { @Autowired ProductRepository productRepository; @Test void concurrentOptimisticLockTest() throws InterruptedException { int threadCount = 5; ExecutorService executorService = Executors.newFixedThreadPool(threadCount); CountDownLatch latch = new CountDownLatch(threadCount); Product savedProduct = productRepository.save(new Product("Initial")); for (int i = 0; i < threadCount; i++) { executorService.submit(() -> { try { Product product = productRepository.findById(savedProduct.getId()).get(); product.setName("Updated by thread"); Thread.sleep(100); // 충돌 유도 productRepository.save(product); } catch (Exception e) { System.out.println("Exception: " + e.getClass().getSimpleName()); } finally { latch.countDown(); } }); } latch.await(); executorService.shutdown(); } }
3. 비관적 락 (Pessimistic Lock)
3.1 비관적 락이란?
비관적 락은 충돌이 자주 발생한다고 가정하고, 리소스를 조회하는 시점에 즉시 락을 걸어 다른 트랜잭션이 접근하지 못하게 합니다. 데이터 정합성이 매우 중요하거나 충돌 확률이 높은 경우 사용합니다.
장점
- 높은 정합성 보장
- 실시간 동시성 제어 가능
단점
- 락 보유 시간 길어지면 성능 저하 가능
- 데드락 발생 위험 존재
3.2 JPA 샘플 코드 및 설명
Repository
public interface ProductRepository extends JpaRepository<Product, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("select p from Product p where p.id = :id") Optional<Product> findByIdWithPessimisticLock(Long id); }
멀티스레드 동시성 테스트
@SpringBootTest @ActiveProfiles("test") class PessimisticLockConcurrentTest { @Autowired ProductRepository productRepository; @Test void concurrentPessimisticLockTest() throws InterruptedException { Product product = productRepository.save(new Product("Initial")); ExecutorService executorService = Executors.newFixedThreadPool(2); CountDownLatch latch = new CountDownLatch(2); Runnable task = () -> { try { Product lockedProduct = productRepository.findByIdWithPessimisticLock(product.getId()).get(); lockedProduct.setName(Thread.currentThread().getName()); Thread.sleep(300); // 락 점유 시간 부여 productRepository.save(lockedProduct); } catch (Exception e) { System.out.println("Exception: " + e.getMessage()); } finally { latch.countDown(); } }; executorService.submit(task); executorService.submit(task); latch.await(); executorService.shutdown(); } }
4. 분산락 (Distributed Lock)
4.1 분산락이란?
분산락은 여러 서버나 프로세스에서 공유 리소스를 동시에 제어할 필요가 있을 때 사용하는 락입니다. 중앙 집중형 데이터베이스 락으로는 커버할 수 없는 분산 환경에서 사용됩니다.
- 주요 도구: Redis, Zookeeper, etcd 등
장점
- 멀티 인스턴스 환경에서도 동기화 가능
- Redis 기반일 경우 빠른 성능
단점
- Redis 장애 시 위험
- 구현에 따라 스핀락, 중복 실행, Deadlock 위험 존재
4.2 Redis를 활용한 분산락
Redis의 SET key value NX PX timeout 명령어를 이용하여 간단한 락을 구현할 수 있습니다.
4.3 Lettuce 활용 (스핀락)
예시 코드
@Service @RequiredArgsConstructor public class LettuceLockService { private final StringRedisTemplate redisTemplate; public boolean tryLock(String key, String value, long timeoutMs) { long end = System.currentTimeMillis() + timeoutMs; while (System.currentTimeMillis() < end) { Boolean success = redisTemplate.opsForValue() .setIfAbsent(key, value, Duration.ofSeconds(5)); if (Boolean.TRUE.equals(success)) { return true; } try { Thread.sleep(100); } catch (InterruptedException ignored) {} } return false; } public void unlock(String key, String value) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), List.of(key), value); } }
4.4 Redisson 활용 (pub/sub 기반)예시 코드
@Service @RequiredArgsConstructor public class RedissonLockService { private final RedissonClient redissonClient; public void executeWithLock(String key, Runnable task) { RLock lock = redissonClient.getLock(key); try { lock.lock(); task.run(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }
Redisson 특징
- pub/sub 기반으로 락 대기 프로세스를 관리함 → CPU 낭비 없음
- 자동 연장 기능 (watchdog) 제공
5. 데드락 (Deadlock)
5.1 데드락이란?
둘 이상의 트랜잭션이 서로 상대방의 락이 풀리기를 기다리며 무한히 대기하는 상태를 의미합니다. 데이터베이스나 분산 환경 모두에서 발생할 수 있으며, 시스템 장애의 주요 원인 중 하나입니다.
5.2 예시 상황
- 트랜잭션 A가 Row 1에 락을 건다.
- 트랜잭션 B가 Row 2에 락을 건다.
- 트랜잭션 A가 Row 2를 요구한다. (대기)
- 트랜잭션 B가 Row 1을 요구한다. (대기)
- 두 트랜잭션 모두 대기 상태 → 데드락 발생
5.3 주의점
- 트랜잭션에서 리소스 접근 순서를 통일해야 함
- 가능한 락 보유 시간을 줄이고, 락 획득 실패 시 재시도 로직을 포함시켜야 함
- DBMS마다 데드락 감지 및 해소 기능을 제공하므로 적절한 예외 처리 필요
5.4 DeadLock 발생 조건
한 시스템 내 네가지 조건이 동시에 성립할 때 데드락이 발생합니다.
결국, 하나라도 성립하지 않도록 한다면 데드락은 해결 가능합니다.① 상호 배제
한번에 하나의 프로세스만 해당 자원을 사용할 수 있다. 즉, 자원을 동시에 사용할 수 없는 경우
👉 해결방법 : 여러 개의 프로세스가 공유 자원을 사용하도록 함
② 점유 대기
자원을 붙잡은 상태로 다른 자원을 기다리고 있다. 자원을 최소한 하나 보유하고, 다른 프로세스에 할당된 자원을 점유하기 위해 대기하는 프로세스가 존재해야 한다.
👉 해결방법 : 프로세스가 실행되기 전 필요한 모든 자원을 할당
③ 비선점
이미 할당된 자원을 강제로 빼앗을 수 없다.
👉 해결방법 : 자원을 점유하고 있는 프로세스가 다른 자원을 요구할 때 점유하고 있는 자원을 반납하고, 요구한 자원을 사용하기 위해 기다리게 한다.
④ 순환 대기
대기 프로세스의 집합이 순환 형태로 자원을 대기해야 한다.
👉 해결방법 : 자원에 고유한 번호를 할당하고, 번호 순서대로 자원을 요구하도록 한다6. 락 전략 선택 기준
상황권장 락 전략
충돌 가능성 낮음, 성능 중요 낙관적 락 충돌 가능성 높음, 정합성 중요 비관적 락 멀티 인스턴스 환경 분산락 락이 많은 자원에 걸리는 경우 락 분할 (Shard) 전략 고려
7. 테스트 및 모니터링- 락 사용 여부와 성능 이슈는 운영 환경에서 반드시 모니터링 필요
- Redisson: metricsEnabled 옵션, Redis 모니터링 툴 활용
- DB 락: InnoDB status, Deadlock 로그 분석
✅ 요약 비교표
락 종류 장점 단점 사용 예시 낙관적 락 성능 우수, Deadlock 없음 충돌 시 예외 발생 조회 후 변경이 적은 데이터 비관적 락 정합성 보장, 실시간 제어 데드락, 성능 저하 가능성 주문 처리, 재고 관리 등 분산락 멀티 인스턴스 지원 구성 복잡, 장애 시 리스크 스케줄링, 크론 중복 방지 데드락 (결과) - 시스템 장애 원인 다중 리소스 접근 병렬 트랜잭션 반응형'Spring' 카테고리의 다른 글
[Spring] Redis의 Redisson을 활용한 분산락 처리 (0) 2025.05.10 [Aws + Spring] SNS(Simple Notification Service) + Spring Boot 에러 메일 전송 기능 구축하기 (0) 2024.01.20 [Aws + Spring] Spring Boot + Aws Glue + S3를 활용한 방문자 통계 구축 (1) (0) 2024.01.02 [Spring] Spring Boot에서 아임포트 다날 본인인증 연동(Rest Api) (0) 2023.09.21 Spring boot Gitlab CI/CD 구축 (2) (0) 2023.09.19