포스트

동시성 문제를 이해하면서 정리한 생각 — 왜 락이 필요할까

동시성 문제를 이해하면서 정리한 생각 — 왜 락이 필요할까

1. 들어가면서

처음에는 동시성 문제를 단순히 “동시에 수정하면 문제가 발생한다” 정도로만 이해하고 있었습니다. 하지만 학습을 진행하면서 비관락, 낙관락, 분산락, 격리수준, 원자적 쿼리 같은 다양한 용어들이 한꺼번에 등장했고, “다 락 같은데 뭐가 다른 거지?”라는 의문이 들기 시작했습니다.

이번 글에서는 동시성 문제의 본질부터 다양한 락(Lock) 개념, 그리고 JPA에서 동시성을 어떻게 제어하는지까지, 제가 학습하며 정리한 내용을 회고 형식으로 기록해보고자 합니다.

2. 동시성 문제란

동시성 문제는 여러 스레드 또는 프로세스가 동일한 데이터를 동시에 수정하면서 데이터의 정합성(Consistency)이 깨지는 문제를 의미합니다. 가장 대표적인 예시가 바로 Lost Update입니다.

예를 들어, 게시글의 ‘좋아요’ 수를 증가시키는 시나리오를 생각해봅시다.

  • 초기 상태: Like Count = 100

두 명의 사용자가 거의 동시에 ‘좋아요’를 누르는 상황이 발생합니다.

  1. Thread A: Like Count를 조회하여 100을 읽습니다.
  2. Thread B: Like Count를 조회하여 100을 읽습니다.
  3. Thread A: 100 + 1을 계산하여 101로 업데이트합니다.
  4. Thread B: 100 + 1을 계산하여 101로 업데이트합니다.

결과:

  • 기대값: 102 (두 번의 ‘좋아요’ 증가)
  • 실제값: 101 (한 번의 ‘좋아요’ 증가만 반영)

이처럼 마지막에 저장된 값으로 인해 이전의 변경 사항이 유실되는 것을 Lost Update라고 합니다. 이는 데이터의 정합성을 심각하게 훼손하는 문제입니다.

동시성 문제 (Lost Update)

3. 락(Lock)이란

이러한 동시성 문제를 해결하기 위한 가장 기본적인 수단이 바로 락(Lock)입니다. 락은 특정 자원(데이터)에 대한 접근을 제어하여, 한 번에 하나의 트랜잭션(또는 스레드)만 해당 자원을 수정할 수 있도록 보장하는 메커니즘입니다.

여기서 중요한 점은 락 자체와 동시성 제어 전략은 다른 개념이라는 것입니다. 락은 동시성 제어를 위한 ‘도구’이며, 이 도구를 어떻게 활용하느냐에 따라 다양한 동시성 제어 ‘전략’이 파생됩니다.

4. 왜 용어가 헷갈렸는가

동시성 문제를 학습하다 보면 격리수준, 원자적 쿼리, 비관락, 낙관락, 분산락 등 수많은 용어들이 쏟아져 나옵니다. 이들이 모두 ‘락’이라는 단어를 포함하거나 ‘동시성 제어’와 관련되어 있어 처음에는 같은 계층의 개념으로 오해하기 쉽습니다.

하지만 사실 이들은 서로 다른 관점과 계층에서 동시성을 제어하는 방법들입니다. 다음 표를 통해 각 개념의 차이를 정리해 보았습니다.

방법락 사용 여부보호 대상주요 목적
격리수준내부적으로 사용트랜잭션트랜잭션 간의 데이터 일관성 보장
원자적 쿼리매우 짧게 사용데이터단일 연산의 원자성 보장
비관락사용데이터충돌 회피 (대기)
낙관락사용 안함데이터충돌 감지 후 처리 (재시도)
분산락사용분산 환경 공유 자원분산 환경에서의 정합성 보장

동시성 제어 방법 분류

이처럼 격리수준은 데이터베이스 트랜잭션의 ‘정책’에 가깝고, 비관락/낙관락/분산락은 동시성 문제를 해결하기 위한 ‘전략’이며, 락/버전 관리/원자적 쿼리는 이러한 전략을 구현하는 ‘기법’이라고 볼 수 있습니다.

5. JPA 동시성 제어

JPA(Java Persistence API)는 영속성 컨텍스트를 통해 엔티티를 관리하며, 동시성 제어를 위한 표준 명세를 제공합니다. 주로 @Lock 어노테이션을 사용한 비관적 락과 @Version 어노테이션을 사용한 낙관적 락을 지원합니다.

6. 비관적 락 (Pessimistic Lock)

비관적 락은 이름 그대로 충돌이 발생할 것이라고 비관적으로 가정하고 미리 락을 획득하는 전략입니다. 데이터베이스의 락 메커니즘을 직접 활용합니다.

특징:

  • 조회 시 락 획득: 데이터를 조회하는 시점에 바로 락을 걸어 다른 트랜잭션의 접근을 막습니다.
  • 다른 트랜잭션 대기: 락이 걸린 데이터에 접근하려는 다른 트랜잭션은 락이 해제될 때까지 대기합니다.

장점:

  • 데이터 정합성을 확실하게 보장합니다.
  • 충돌이 빈번하게 발생하는 환경에서 안정적입니다.

단점:

  • 락으로 인해 동시성이 저하될 수 있습니다.
  • 데드락(Deadlock) 발생 가능성이 있습니다.

사용하기 적합한 상황:

  • 충돌이 자주 발생하여 데이터 정합성이 매우 중요한 경우 (예: 재고 관리, 금융 거래)

실무 예시: JPA에서 @Lock(LockModeType.PESSIMISTIC_WRITE)를 사용하여 비관적 락을 적용할 수 있습니다. 이는 SELECT ... FOR UPDATE와 같은 SQL 쿼리를 발생시켜 데이터베이스 레벨에서 락을 겁니다.

비관적 락 동작 방식

7. 낙관적 락 (Optimistic Lock)

낙관적 락충돌이 자주 발생하지 않을 것이라고 낙관적으로 가정하고, 락을 걸지 않고 일단 작업을 진행하는 전략입니다. 대신 데이터 변경 시점에 충돌 여부를 검증합니다.

@Version 설명: JPA에서는 @Version 어노테이션을 사용하여 낙관적 락을 구현합니다. 엔티티에 version 컬럼(정수 또는 타임스탬프)을 추가하고, 데이터가 변경될 때마다 이 version 값을 자동으로 증가시킵니다.

버전 비교 과정 설명:

  1. 트랜잭션 시작 시 엔티티의 version 값을 조회합니다.
  2. 데이터를 수정하고 커밋할 때, 조회했던 version 값과 현재 데이터베이스의 version 값을 비교합니다.
  3. 두 값이 일치하면 업데이트를 진행하고 version 값을 증가시킵니다.
  4. 두 값이 일치하지 않으면 (다른 트랜잭션이 먼저 데이터를 변경했다는 의미) OptimisticLockException을 발생시킵니다.

장점:

  • 락을 걸지 않으므로 동시성이 높습니다.
  • 별도의 락 관리 비용이 없어 성능상 이점이 있습니다.

단점:

  • 충돌 발생 시 재시도 로직이 필요합니다.
  • 충돌이 빈번한 환경에서는 OptimisticLockException이 자주 발생하여 성능 저하로 이어질 수 있습니다.

사용하기 적합한 상황:

  • 데이터 조회 빈도가 높고, 충돌 발생 빈도가 낮은 경우 (예: 게시판 조회수, 댓글)

낙관적 락 동작 방식

8. 재시도 전략 (Retry Strategy)

실무에서 낙관적 락을 사용할 때는 OptimisticLockException 발생 시 재시도(Retry) 전략을 함께 적용하는 경우가 많습니다. 충돌이 발생하면 일정 시간 대기 후 다시 작업을 시도하는 방식입니다.

재시도 전략에는 여러 종류가 있습니다.

  • Fixed Backoff: 일정 시간(예: 1초)을 고정적으로 대기 후 재시도합니다.
  • Exponential Backoff: 재시도 횟수가 늘어날수록 대기 시간을 지수적으로 증가시킵니다 (예: 1초, 2초, 4초, 8초…). 이는 서버에 부하를 덜 주고, 일시적인 문제 해결에 효과적입니다.
  • Jitter: Exponential Backoff에 무작위 지연 시간을 추가하여, 여러 클라이언트가 동시에 재시도하여 또 다른 충돌을 일으키는 것을 방지합니다.
  • Exponential Backoff + Jitter: 가장 일반적이고 효과적인 재시도 전략으로, 지수적 백오프에 무작위 지연을 더해 안정성을 높입니다.

적절한 재시도 전략은 낙관적 락의 단점을 보완하고 시스템의 안정성을 높이는 데 필수적입니다.

9. 원자적 쿼리 (Atomic Query)

원자적 쿼리는 데이터베이스 자체의 기능을 활용하여 단일 쿼리 내에서 데이터 조회와 수정을 원자적으로 처리하는 방법입니다. 애플리케이션 레벨에서 락을 걸거나 버전을 관리할 필요 없이, 데이터베이스가 직접 정합성을 보장합니다.

예시: ‘좋아요’ 수를 증가시키는 경우, 다음과 같은 쿼리를 사용합니다.

1
2
3
UPDATE likes
SET like_count = like_count + 1
WHERE id = ?;

이 쿼리는 like_count 값을 조회하고 +1 한 후 다시 저장하는 일련의 과정을 데이터베이스 내부에서 원자적(Atomic)으로 처리합니다. 즉, 다른 트랜잭션이 중간에 끼어들 수 없도록 보장합니다.

언제 적합한가:

  • 단순한 카운터 증가/감소와 같이 복잡한 비즈니스 로직이 없는 경우
  • 데이터베이스의 강력한 정합성 보장 기능을 활용하고자 할 때

원자적 쿼리 동작 방식

10. 분산락 (Distributed Lock)

앞서 살펴본 락들은 주로 단일 애플리케이션 인스턴스 또는 단일 데이터베이스 내에서의 동시성 제어에 초점을 맞춥니다. 하지만 여러 서버 인스턴스가 동일한 자원에 접근하는 분산 환경에서는 이러한 락만으로는 부족합니다. 이때 필요한 것이 분산락입니다.

DB 락과의 차이: 분산락은 Redis, ZooKeeper, Apache Kafka 등과 같은 외부 분산 시스템을 활용하여 락을 관리합니다. 이는 여러 애플리케이션 서버가 공유하는 자원에 대한 접근을 제어하여 분산 환경에서의 정합성을 보장합니다.

예시:

  • 쿠폰 발급: 여러 서버에서 동시에 쿠폰 발급 요청이 들어올 때, 중복 발급을 방지합니다.
  • 재고 차감: 여러 서버에서 동시에 재고 차감 요청이 들어올 때, 재고가 음수가 되는 것을 방지합니다.
  • 커넥션 풀 보호: 특정 자원에 대한 커넥션 풀을 여러 서버가 공유할 때, 동시 접근을 제어합니다.

분산락 동작 방식

11. 그래서 무엇이 더 빠를까?

다양한 동시성 제어 전략들을 살펴보았는데, 그렇다면 어떤 전략이 가장 빠를까요? 이는 상황에 따라 다르며, 각 전략의 특징과 트레이드오프를 이해하는 것이 중요합니다.

비관적 락

  • 특징: 조회 시 락 획득
  • 속도 관점: 충돌이 많을 때 안정적이지만, 락으로 인한 대기 시간이 발생하여 처리량이 감소할 수 있습니다.

낙관적 락

  • 특징: 락 없이 버전 비교
  • 속도 관점: 충돌이 적으면 락 비용이 없어 매우 빠릅니다. 하지만 충돌이 많아지면 OptimisticLockException 발생 및 재시도 비용이 증가하여 성능이 저하될 수 있습니다.

원자적 쿼리

  • 특징: DB가 직접 처리
  • 속도 관점: 일반적으로 가장 빠릅니다. 별도의 조회, 버전 비교, 재시도 과정이 없기 때문입니다. 단, 복잡한 비즈니스 로직에는 적용하기 어렵습니다.

분산락

  • 특징: Redis 등 외부 시스템 사용
  • 속도 관점: 가장 느린 편입니다. 네트워크 비용과 락 획득/반납 비용이 존재하기 때문입니다. 분산락의 주 목적은 속도가 아니라 분산 환경에서의 정합성 보장입니다.

성능 비교 표 (일반적인 경향)

전략일반적인 속도충돌 발생 시 영향
원자적 쿼리가장 빠름영향 적음
낙관락빠름재시도 비용 증가
비관락보통안정적 (대기 발생)
분산락가장 느림안정적 (네트워크 비용 발생)

주의: 위 표는 절대적인 성능 비교가 아니라 일반적인 경향을 설명한 것입니다. 실제 성능은 시스템 환경, 트랜잭션 특성, 충돌 빈도 등에 따라 크게 달라질 수 있습니다.

12. 마무리

처음에는 동시성 제어와 관련된 모든 용어들이 그저 “락”처럼 보였습니다. 하지만 학습을 통해 동시성 문제, 그리고 이를 해결하기 위한 정책, 전략, 기법들이 서로 다른 계층의 개념이라는 것을 이해하게 되었습니다.

각 전략은 장단점과 적합한 상황이 명확하므로, 개발자는 시스템의 특성과 요구사항을 고려하여 가장 적절한 동시성 제어 방식을 선택해야 합니다. 무조건 빠르거나 무조건 안전한 방법은 없으며, 항상 트레이드오프를 고민해야 합니다.

이번 학습을 통해 동시성 제어에 대한 막연한 두려움을 덜고, 상황에 맞는 해결책을 고민할 수 있는 시야를 얻게 되어 기쁩니다. 앞으로 실제 프로젝트에서 이러한 지식들을 효과적으로 적용해보고 싶습니다.

Learned (배운 점)

  • 동시성 문제의 본질: Lost Update와 같은 데이터 정합성 훼손 문제를 명확히 이해했습니다.
  • 락의 역할: 락은 동시성 제어를 위한 도구이며, 다양한 전략과 기법이 존재함을 알게 되었습니다.
  • 용어의 계층 이해: 격리수준, 비관락, 낙관락, 분산락, 원자적 쿼리 등이 서로 다른 관점에서 동시성을 제어하는 개념임을 분류할 수 있게 되었습니다.
  • 전략별 트레이드오프: 각 동시성 제어 전략의 장단점, 성능 특성, 적합한 사용 사례를 파악하여 상황에 맞는 선택의 중요성을 깨달았습니다.
  • 실무 적용 관점: 재시도 전략의 필요성, 분산 환경에서의 분산락 활용 등 실무적인 고려 사항을 학습했습니다.

References

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.