일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 인프런강의
- datagrip
- 워커노드
- elasticahe
- Docker
- AWS
- ssafy #싸피 #ssafy 12기 #싸피 12기 #ssafy 합격 #싸피 합격 #합격 후기
- redis oss
- cloud controller manager
- ingress-nginx
- EC2
- 쿠버네티스
- sshtunneling
- jnlp
- kaniko
- Certbot
- ssh
- multibranch
- Tunneling
- RDS
- 인프런
- port forawrding
- vue3
- ElastiCache
- k8s
- ChatGPT
- helm-chart
- helm
- argocd
- Kubernetes
- Today
- Total
처누
[CS] 멀티스레드 환경에서의 동시성 문제(feat. ChatGPT) 본문
ChatGPT와 함께하는 면접 준비
질문 4: 멀티스레드 환경에서의 동시성 문제
상황:
여러 사용자가 동시에 여행지 검색을 요청한다고 가정합니다. 특정 API는 요청마다 데이터베이스에서 여행지 조회수를 업데이트하는 로직이 포함되어 있습니다. 그러나, 동시에 여러 요청이 들어올 경우 조회수가 정확히 업데이트되지 않는 문제가 발생하고 있습니다.
질문:
- 동시성 문제를 방지하기 위해 어떤 방법을 사용할 수 있을까요?
- 데이터베이스 레벨에서 해결하기 위해 어떤 잠금 메커니즘(예: Row Lock, Optimistic Lock, Pessimistic Lock)을 선택하실지 설명해 주세요.
- 이러한 잠금 메커니즘을 사용할 때 발생할 수 있는 단점과 이를 최소화하기 위한 대안을 제시해 주세요.
내 답변
1. 동시성 문제를 방지하기 위해 Transaction처리.
2. x
3. x
정리
잠금(lock)
- 트랜잭션이 특정 데이터에 접근할 때, 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 하는 메커니즘.
- 잠금은 데이터의 무결성을 유지하고, 여러 트랜잭션이 동시에 같은 데이터를 수정하려 할 때 발생할 수 있는 충돌을 방지함.
잠금 메커니즘
낙관적 잠금(Optimistic Lock)
- 데이터를 읽을 때 버전 정보를 함께 가져오고, 업데이트 시 현재 버전과 데이터베이스의 버전을 비교해 동일한 경우에만 업데이트.
- 만약 다른 트랜잭션에서 변경했다면 버전이 다르므로 업데이트 실패.
장점
- 데이터 업데이트 시 락을 거는 대신, 업데이트를 시도할 때 충돌이 발생하면 예외를 발생시키고, 해당 예외를 처리하는 방식을 사용하기 때문에 성능 향상이 됨.
- 동시에 업데이트를 시도하는 경우 충돌이 발생하면 예외를 발생시키고, 이 예외를 처리하는 방식으로 데이터 일관성을 유지함. 이 방식은 업데이트 대상 데이터가 다른 클라이언트에 의해 변경됐는지 여부를 확인하는 방법으로, 더욱 유연한 처리 방식을 제공함.
- 업데이트 시 락을 걸지 않기 때문에, 병행성 제어가 필요하지 않음. 이는 데이터베이스 성능을 향상시키고, 더 많은 클라이언트에서 동시에 데이터를 처리할 수 있음.
단점
- 충돌이 자주 발생하면 성능 저하 가능(ex. 자주 변경되는 데이터)
- 업데이트 실패 시 재시도가 필요하며, 충돌이 자주 발생하는 환경에서는 성능 저하 가능.
낙관적 잠금이 유용한 경우
1. 데이터 업데이트가 자주 발생하지 않는 경우
- 낙관적 잠금은 데이터 업데이트 시 충돌이 발생할 가능성이 높기 때문에, 업데이터가 자주 발생하지 않는 경우에 사용된다.
2. 충돌이 발생할 가능성이 낮은 경우
- 낙관적 잠금은 충돌이 발생할 가능성이 높은 경우보다는 충돌이 발생할 가능성이 낮은 경우에 사용된다. 예를 들어, 데이터베이스에 대한 업데이트 요청이 매우 드문 경우에는 낙관적 잠금이 유용함.
3. 데이터 일관성을 보장하는데 충분한 방법이 될 수 있는 경우
- 낙관적 잠금은 업데이트 대상 데이터가 다른 클라이언트에 의해 변경됐는지 여부를 확인하는 방법으로 데이터 일관성을 보장한다. 따라서, 데이터 일관성을 보장하는 데 충분한 방법이 될 수 있는 경우에 낙관적 잠금이 사용된다.
4. 락 경합이 적은 경우
- 낙관적 잠금은 락 경합이 적은 경우에 사용된다. 락 경합이 많은 경우 비관적 잠금을 사용하는 것이 더 적합함.
비관적 잠금(Pessimistic Lock)
- 데이터를 읽거나 변경하기 전에 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 잠금을 설정
- 쓰기 잠금(Lockmodetype.PESSIMISTIC_WRITE), 읽기 잠금(LockmodeType.PESSIMISTIC_READ)
장점
- 충돌이 발생하지 않도록 예방
- 데이터 일관성을 보장
단점
- 잠금 대기가 발생해 응답 시간이 증가할 수 있으며 이로 인해 성능이 낮아질 수 있음.
- 여러 트랜잭션이 서로를 대기하는 상황 (데드락(Deadlock) 발생 가능성이 있음.
비관적 잠금이 유용한 경우
1. 데이터의 충돌 가능성이 높은 경우
2. 경쟁이 빈번한 경우
- 동시에 여러 사용자가 같은 데이터를 업데이트하려고 시도하는 경우
3. 데이터 무결성이 매우 중요한 상황
- 금융 거래, 재고 관리, 티켓 예약 등 충돌이 큰 손실로 이어질 수 있는 시스템
4. 동시성보다 정확성이 중요한 경우
- 성능이 약산 희생되더라도 데이터 일관성이 보장되어야 할 때
Row Lock(SELECT ... FOR UPDATE)
- 특정 행에 대한 배타적 잠금을 설정하여 다른 트랜잭션이 수정하지 못하도록 함.
장점
- 특정 데이터에 대한 동시성 문제를 완벽히 해결
단점
- 높은 부하가 걸리면 성능 병목이 발생할 가능성이 있음.
- 자주 변경되는 행에 대해 높은 잠금 대기 시간이 발생함.
대안
1. 읽기-쓰기 작업 문리(Read-Write Separation)
- 조회 요청과 쓰기 요청을 분리하여 읽기 요청은 캐시나 읽기 전용 복제본을 활용
2. 비동기 작업 처리
- 조회수 업데이트를 즉시 데이터베이스에 반영하지 않고, 비동기 메시지 큐(Kafka, RabbitMQ 등)를 사용해 나중에 배치 처리.
- 비동기 메시지 큐는 충돌이 자주 발생하는 작업을 큐에 저장하고, 백그라운드에서 순차적으로 처리함으로서 데이터베이스 부하를 줄이고 동시성 문제를 완화할 수 있음.
3. 사용자 정의 Counter 캐시
- 조회수와 같이 자주 업데이트되는 데이터를 캐시에 저장한 뒤 주기적으로 데이터베이스에 반영.
- 캐시를 사용해 데이터를 일정 시간 동안 메모리에 저장하고, 주기적으로 DB에 반영하면 효율성을 높일 수 있음.
Redis를 활용한 Lock 제어
1. SETNX(SET if Not eXists) 명령을 활용한 스핀락
- 레디스의 SETNX는 키가 존재하지 않을 때 값을 세팅하는 방법.
- 이를 통해 특정 키를 락으로 설정하고, 락이 이미 사용중이면 주기적으로 락을 획득하기 위해 요청하는 스핀락을 구현할 수 있음. (SETNX 명령은 deprecated 됐으므로 SET명령과 NX 옵션을 사용하는 것이 권장됨.)
단점
- 구현은 단순하지만, 락을 획득할 때까지 계속 요청을 시도하기 때문에 레디스 서버에 부하를 줌.
2. Redission을 사용한 분산락
- Redisson은 레디스에서 분산락을 효율적으로 처리할 수 있도록 도와주는 오픈소스임.
- pub/sub과 Lua 스크립트를 활용해 효과적으로 분산락을 처리하는 기능을 제공함.
pub/sub 기능
- Redisson은 레디스의 pub/sub 기능을 활용하여 락을 획득할 때까지 subscribe로 채널의 메시지를 기다린다. 이후 unlock이 발생하여 채널의 메시지가 publish되면 락 획득을 시도함.
Lua 스크립트를 사용한 원자적인 명령 실행
- 레디스는 Lua 스크립트를 사용해 명령어 집합을 원자적으로 실행할 수 있음. Redission에서도 이를 활용해 lock, unlock 명령 집합을 원자적으로 실행함.
수정된 답변
1. 잠금 메커니즘을 활용하여 동시성 문제를 방지할 수 있다고 생각합니다. 로직에 따라 낙관적 잠금 혹은 비관적 잠금을 이용한다면 데이터의 일관성, 동시성 문제를 방지할 수 있습니다.
2. 낙관적 잠금 메커니즘을 사용할 것입니다. 낙관적 잠금은 데이터 업데이트 시 락을 거는게 아닌 동시에 데이터 업데이트를 시도하는 경우 예외를 발생시켜 데이터의 일관성을 유지합니다. 이는 유연한 처리 방식을 제공하고, 업데이트 시 락을 걸지 않기 때문에 병행성 제어가 필요하지 않아, 데이터베이스의 성능을 향상시키고, 더 많은 클라이언트에서 동시에 요청을 처리할 수 있기 때문입니다.
3. 낙관점 잠금 메커니즘 사용 시 발생할 수 있는 단점은 자주 변경되는 데이터의 경우 충돌이 자주 발생하기 때문에 성능 저하의 가능성이 있습니다. 이를 최소화 하기 위해 비동기 메시지 큐 또는 캐시에 저장한 뒤 데이터 베이스에 반영하는 방이 있습니다.
ChatGPT의 꼬리 질문
2-1. 낙관적 잠금을 사용한 후 충돌이 발생했을 때, 예외 처리 로직은 어떻게 구성하실 계획인가요?
- 예외 발생 시 클라이언트에게 어떤 응답을 반환할지, 혹은 재시도 로직을 서버 측에서 처리할지 설명해 주세요.
- 충돌 시 예외를 잡아, 클라이언트에게 적절한 HTTP 상태 코드와 함께 "데이터 충돌 발생, 다시 시도하세요"와 같은 메시지를 반환할 것입니다.
- 충돌이 빈번하지 않다면, 서버측에서 재시도 로직을 구현할 것입니다. 예를 들어, 최대 3회까지 충돌이 발생하면 동일 트랜잭션을 다시 실행하고, 여전히 실패한다면 클라이언트에 오류를 반환할 것입니다.
2-2. 낙관적 잠금을 사용하는 경우, 특정 상황에서 데이터 정합성을 보장하기 어려운 사례가 있을까요?
- 예를 들어, 트랜잭션 간 데이터 의존성이 강한 경우에 대한 대안을 말씀해 주세요.
- 낙관적 잠금을 사용할 때, 트랜잭션 간 데이터 의존성이 강한 경우에는 데이터 정합성이 깨질 가능성이 있습니다.
이에 대한 대안으로는
1. 최종 일관성(Eventual Consistency) 모델 도입
- 충돌이 발생해도 트랜잭션 간 완벽한 즉각적 일관성을 요구하지 않는다면, 메시지 큐 등을 통해 데이터 갱신을 비동기로 처리합니다. 예를 들어, 조회수 업데이트와 같이 정합성이 상대적으로 덜 중요한 경우, 최종적으로만 데이터 일관성이 유지되면 충분합니다.
2. 트랜잭션 설계 최적화
- 트랜잭션의 범위를 줄이고, 중요한 데이터에 대해서만 잠금을 적용해 동시성 문제를 최소화합니다. 예를 들어, 한 번에 읽고 쓰는 범위를 줄이거나, 비관적 잠금과 결합하여 중요한 데이터만 보호합니다.
3-1.메시지 큐를 사용할 때의 단점은 무엇이라고 생각하시나요? 예를 들어, 메시지 손실, 처리 지연 등에 대한 문제를 어떻게 해결할 계획인가요?
단점
1. 여러 문제로 인해 메시지가 손실될 수 있습니다.
- 이에 대한 해결 방안으로는 메시지 큐에서 At-least-once 또는 Exactly-once 전달 보장 설정을 할 것입니다. 예를 들어, RabbitMQ의 Acknowledgement(ACK) 또는 Kafka의 Commit Log를 사용해 메시지 전달을 보장하는 것입니다.
2. 대량의 메시지가 쌓이면 처리 지연이 발생할 수 있습니다.
이에 대한 해결 방안으로는
- 컨슈머(Consumer) 스케일링 :메시지 처리를 병렬화하여 소비 속도를 증가 시키거나
- 우선순위 큐 : 중요한 메시지를 먼저 처리할 것입니다.
3. 동일 메시지가 여러 번 처리될 가능성이 있습니다.
이에 대한 해결 방안으로는 메시지 ID를 기반으로 중복 여부를 확인해 한 번만 처리하도록 할 것입니다.
3-2. 캐시를 사용할 경우, 캐시의 데이터와 DB 간의 데이터 불일치 문제(Cache Inconsistency)를 어떻게 방지할 수 있을지 말씀해 주세요.
- 캐시에 데이터가 갱신되지 않으면, 오래된 데이터가 반환될 수 있습니다.
이는 다음과 같은 방법으로 해결할 수 있습니다.
1. Write-through 캐싱
- 데이터베이스에 쓰기 작업 시 캐시에 동시에 갱신
2. Write-behind 캐싱
- 캐시를 먼저 갱신하고, 일정 주기로 데이터베이스에 비동기로 반영
3. TTL(Time-to-Live)
- 캐시의 유효 기간을 짧게 설정하여 정기적으로 갱신
데이터가 자주 변경되는 경우 Write-through 방식으로, 데이터가 자주 읽히고 변경 빈도가 적은 경우는 TTL을 활용해 갱신 주기를 최적화 할 것입니다.
또한 초기 요청 시 캐시에 데이터가 없기 때문에 Cold Start문제가 발생하여 성능 저하가 발생할 수 있습니다.
이는 애플리케이션 시작 시 자주 사용하는 데이터를 미리 캐시에 로드해놓는 Prewarming(ex. 서버 시작 시 지역 목록, 인기 여행지 목록을 Redis에 미리 저장)이나 캐시에 데이터가 없을 경우, DB에서 조회한 데이터를 캐시에 자동으로 저장하도록 로직을 추가하는 Fallback전략을 사용할 것입니다.
참고
'CS' 카테고리의 다른 글
[CS] HTTP와 HTTPS(feat. ChatGPT) (0) | 2025.01.12 |
---|---|
[CS] Redis의 데이터 구조와 활용(feat.ChatGPT) (0) | 2025.01.05 |
[CS] 실시간 데이터 처리(feat. ChatGPT) (0) | 2025.01.04 |
[CS] 데이터베이스 설계 및 성능 최적화(feat. ChatGPT) (4) | 2024.12.23 |
[CS] SpringSecurity(feat. ChatGPT) (2) | 2024.12.20 |