Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

선착순 시스템에서 데이터 무결성을 어떻게 보장할까? #45

Open
since1909 opened this issue Feb 13, 2025 · 0 comments
Assignees
Labels
CS 리스트 백엔드 질문 리스트의 직무 구분을 위한 라벨

Comments

@since1909
Copy link
Member

since1909 commented Feb 13, 2025

📝 선착순 시스템에서 데이터 무결성을 어떻게 보장할까?

📚 주제:
여러 사용자가 동시에 빠른 접근이 이루어지는 선착순 시스템에서 정확한 데이터를 읽기 위한 방법


🎯 스터디 목표
선착순 시스템에서의 데이터 락


트랜잭션의 격리 레벨 (Transaction Isolation Level)

트랜잭션의 격리 레벨(Isolation Level) 은 동시에 여러 트랜잭션이 실행될 때, 서로의 작업이 얼마나 영향을 미칠 수 있는지를 결정하는 설정입니다. 격리 레벨이 높을수록 데이터 정합성이 보장되지만, 동시성이 낮아지고 성능 저하가 발생할 수 있습니다. ANSI SQL 표준에서 정의한 4가지 격리 수준과 발생할 수 있는 문제점은 다음과 같습니다.

1. READ UNCOMMITTED (읽기 미확정)

  • 트랜잭션이 커밋되지 않은 데이터도 읽을 수 있는 가장 낮은 격리 수준
  • 성능은 가장 좋지만, Dirty Read(더티 리드) 발생 가능

발생할 수 있는 문제

  • Dirty Read(더티 리드): 다른 트랜잭션에서 변경했지만 아직 커밋되지 않은 데이터를 읽을 수 있음 → 이후 롤백되면 잘못된 데이터를 읽은 셈이 됨

2. READ COMMITTED (읽기 확정)

  • 커밋된 데이터만 읽을 수 있는 수준
  • 대부분의 RDBMS에서 기본 설정 (ex. Oracle, PostgreSQL)
  • Dirty Read 방지 가능하지만, Non-Repeatable Read 발생 가능

발생할 수 있는 문제

  • Non-Repeatable Read(비반복 읽기): 같은 트랜잭션 내에서 같은 데이터를 두 번 읽었을 때 값이 다를 수 있음 → 다른 트랜잭션이 데이터를 변경하고 커밋했기 때문

3. REPEATABLE READ (반복 가능한 읽기)

  • 같은 트랜잭션 내에서는 읽은 데이터가 변하지 않도록 보장 (즉, 트랜잭션이 끝날 때까지 다른 트랜잭션이 해당 데이터를 수정할 수 없음)
  • MySQL의 기본 격리 수준
  • Dirty Read, Non-Repeatable Read 방지 가능하지만, Phantom Read 발생 가능

발생할 수 있는 문제

  • Phantom Read(팬텀 리드): 같은 조건으로 데이터를 조회했을 때, 새로운 행이 추가되거나 삭제될 수 있음 → 다른 트랜잭션이 데이터를 삽입하면 조회 결과가 달라짐

4. SERIALIZABLE (직렬화)

  • 가장 높은 격리 수준으로, 트랜잭션을 직렬적으로 실행하는 것과 같은 효과를 가짐 (Locking을 사용하여 동시 실행 방지)
  • 모든 문제(Dirty Read, Non-Repeatable Read, Phantom Read)를 해결하지만 성능 저하가 심함

READ COMMITTED + 행 락(FOR UPDATE)을 활용한 선착순 시스템 구축

1. READ COMMITTED + FOR UPDATE를 사용하는 이유

  • Dirty Read 방지: 커밋된 데이터만 읽을 수 있음
  • 선착순 보장: 같은 데이터를 여러 사용자가 동시에 수정하는 것을 방지
  • 트랜잭션 내에서 데이터 일관성 유지: 하나의 트랜잭션이 데이터를 수정 중이면 다른 트랜잭션은 대기

2. 선착순 시스템에서의 적용 예시

✅ 좌석 예약 시스템 (Seat Booking)

BEGIN;

-- 특정 좌석을 조회하면서 해당 좌석에 대한 락 설정
SELECT * FROM seats WHERE seat_id = 1 FOR UPDATE;

-- 좌석이 예약되지 않았다면, 예약 진행
UPDATE seats SET status = 'RESERVED' WHERE seat_id = 1;

COMMIT;

📌 처리 흐름

  1. seat_id = 1인 좌석을 조회하면서 행 락 설정
  2. 다른 사용자가 같은 좌석을 예약하려고 하면 대기 상태가 됨
  3. UPDATE 실행 후 COMMIT락 해제

이 방식의 장점

  • 같은 좌석에 대한 중복 예약 방지
  • 여러 사용자가 동시에 요청해도 첫 번째 트랜잭션이 완료될 때까지 다른 요청은 대기

🚨 주의점

  • 트랜잭션이 길어지면 성능 저하 발생 → 최대한 빠르게 커밋해야 함
  • 대기하는 트랜잭션이 많으면 타임아웃 문제 발생 가능 → 이를 방지하려면 NOWAIT 또는 SKIP LOCKED 옵션을 사용할 수 있음

3. 성능 최적화를 위한 추가 옵션

✅ NOWAIT: 즉시 실패하도록 설정

SELECT * FROM seats WHERE seat_id = 1 FOR UPDATE NOWAIT;

🚨 즉시 실패 (대기 없음)

  • 락이 걸려 있다면 즉시 오류 발생
  • 대기하지 않고 다른 처리를 할 수 있음

✅ SKIP LOCKED: 사용 가능한 데이터만 조회

SELECT * FROM seats WHERE status = 'AVAILABLE' FOR UPDATE SKIP LOCKED LIMIT 1;

장점

  • 대기 시간을 줄여 빠른 응답 가능
  • 동시에 많은 사용자가 몰리는 선착순 시스템에서 유용

🚨 주의점

  • 특정 요청이 계속해서 락이 걸려 있는 데이터를 건너뛸 가능성이 있음
  • 공정성(Fairness)이 부족할 수 있음

4. 결론

READ COMMITTED + 행 락(FOR UPDATE)이 적절한 경우

  • 좌석 예약, 한정 수량 쿠폰 발급, 재고 감소 등의 선착순 시스템
  • 동시에 여러 사용자가 같은 데이터를 수정하는 경우

효율적인 트랜잭션 관리 방법

  1. FOR UPDATE를 사용하여 중복 요청 방지
  2. NOWAIT을 활용해 즉시 실패하도록 설정 (불필요한 대기 방지)
  3. SKIP LOCKED를 사용하여 빠른 처리 가능 (대량 트래픽 대응)

💡 결론적으로, READ COMMITTED + FOR UPDATE가 선착순 시스템에서는 가장 현실적인 조합!
하지만 상황에 따라 SKIP LOCKED 등의 추가 옵션을 활용하면 성능을 더욱 향상시킬 수 있습니다. 🚀


Kafka를 활용한 선착순 시스템 구축 및 부하 분산 방법

1. Kafka를 활용한 순차적 선착순 처리

  • 사용자가 요청을 보냄 → Kafka의 특정 Topic에 메시지 전송
  • Kafka Producer가 메시지를 저장 → 메시지 큐에 보관
  • Kafka Consumer가 하나씩 메시지를 가져와 순차적으로 처리

Kafka Producer 예제

KafkaTemplate<String, String> kafkaTemplate;
public void sendReservationRequest(String userId) {
    kafkaTemplate.send("reservation_topic", userId);
}

Kafka Consumer 예제

@KafkaListener(topics = "reservation_topic", groupId = "reservation_group")
public void processReservation(String userId) {
    System.out.println("Processing reservation for user: " + userId);
}

2. Kafka 부하 분산 방법

✅ 다중 Consumer를 활용한 병렬 처리

  • 파티션을 늘려 Consumer Group을 병렬 실행
  • Consumer 인스턴스를 Auto Scaling하여 부하를 동적으로 조절

✅ Retention 설정 조정

log.retention.hours=6  # 메시지를 6시간 동안만 유지

✅ Batch 처리 방식 적용

@KafkaListener(topics = "reservation_topic", containerFactory = "batchFactory")
public void processReservation(List<String> messages) {
    for (String message : messages) {
        System.out.println("Processing batch message: " + message);
    }
}

결론

Kafka를 활용하면 비동기적 선착순 처리가 가능하며, 다음 방법을 통해 부하를 효율적으로 분산할 수 있음:

  1. 파티션을 늘려 Consumer를 병렬 처리
  2. Auto Scaling을 활용한 동적 확장
  3. Retention 설정을 최적화하여 불필요한 데이터 삭제
  4. Batch 처리 방식을 적용하여 성능 최적화

💡 참고 자료:

  • 링크나 참고한 자료 출처를 여기에 적어 주세요.
@since1909 since1909 self-assigned this Feb 13, 2025
@since1909 since1909 added 백엔드 질문 리스트의 직무 구분을 위한 라벨 CS 리스트 labels Feb 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CS 리스트 백엔드 질문 리스트의 직무 구분을 위한 라벨
Projects
None yet
Development

No branches or pull requests

1 participant