[CS공부] 동시성, 데이터가 꼬이기 전에 잡아야 한다.

백엔드 취준생·2025년 9월 9일
0

CS공부

목록 보기
13/13

서버와 동시 실행

클라이언트 요청마다 스레드를 할당해서 처리

클라이언트 요청을 처리할 때 사용할 동시 요청 개수만큼 스레드가 동시에 실행

비동기 IO (또는 논블로킹 IO)를 사용해서 처리

단일 스레드가 아닐경우 실행

잘못된 데이터 공유로 인한 문제 예시

필드 주입을 이용한 데이터 값 삽입 시 생기는 문제

  1. genPayId()로 생성한 값을 payId 필드에 할당
  2. payId 필드를 이용해서 임시 저장
  3. sendPayDate()를 호출하고 리턴 결과를 resp 변수에 저장
  4. applyResponse() 호출
  5. 파라미터로 받은 resp를 이용해서 PayDate를 생성한다.
  6. updatePayDate() 메소드에 payId 필드를 전달한다.
  • payId : 1, payId : 2 스레드가 동시에 작동 → 값이 변경되는 오류가 발생할 수 있습니다.

프로세스 수준에서의 동시 접근 제어

잠금(lock)을 이용한 접근 제어

잠금은 공유 자원에 접근하는 스레드를 한 번에 하나로 제한할 수 있다

  1. 잠금을 획득함
  2. 공유 자원에 접근(임계 영역)
  3. 잠금을 해제함

임계 영역 : 동시에 둘 이상의 스레드나 프로세스가 접그한면 안되는 공유 장원에 접근하는 코드 영역을 말함

synchronized : 간단하게 잠금 설정 가능
ReentrantLock : 잠금 획득 대기 시간 지정, 가상 스레드

세마포어

동시에 실행할 수 있는 스레드 수를 제한

  • 자원에 대한 접근을 일정 수준으로 제한하고 싶을 때 사용
  • 퍼밋 : 자바 세마포어 구현체
  1. 세마포어에서 퍼밋 획득(허용 가능 숫자 1 감소)
  2. 코드 실행
  3. 세마포어에 퍼밋 반환(허용 가능 숫자 1 증가)

읽기 쓰기 잠금

읽기 빈도가 높을 때 좋음

  • 쓰기 잠금은 한 번에 한 스레드만 구할 수 있음
  • 읽기 잠금은 한 번에 여러 스레드가 구할 수 있다
  • 한 스레드가 쓰기 잠금을 획득했다면 쓰기 잠금이 해제될 때까지 읽기 잠금을 구할 수 없다
  • 읽기 잠금을 획득한 모든 스레드가 읽기 잠금을 해제할 때까지 쓰기 잠금을 구할 수 없다.

→ 읽기 성능 문제 완화

원자적 타입

잠금을 사용하면 간단하게 동시성 문제 해결은 가능하나 CPU 효율이 떨어진다.

  • AtomicLong, AtomicBoolean 사용
    • 내부적으로 CAS 연산 사용
    • 스레드를 멈추지 않고 다중 스레드 환경에서 안전하게 값 변경

동시성 지원 컬렉션

스레드에 안전하지 않은 컬렉션을 여러 스레드가 공유하면 동시성 문제 발생했을 때 동기화된 컬렉션을 사용

  • 자바의 Collections 클래스 : 동기화된 컬렉션을 생성하는 메소드를 제공
    • 기존 컬렉션 객체를 쉽게 동기화된 컬렉션 객체로 변환 가능
  • 모두 동기화된 블록에서 실행되어 동시성 문제 해결

컬렉션 타입을 사용하는 방법

  • HashMap → ConcurrentHashMap 사용
  • 데이터 변경할 때 잠금 범위를 최소화
  • 키의 해시 분포가 고르고 동시 수정이 많으면, 동기화된 맵을 사용하는 것보다 더 나은 성능이다.

DB와 동시성

DB 트랜잭션이 동시성 문제 해결 불가 → DB에 맞는 잠금 기능 활용

  • 선점 잠금 : 동일한 레코드에 대해 한 번에 하나의 트랜잭션만 접근할 수 있도록 제어
  • 비선점 잠금 : 쿼리 실행 자체는 막지 않으면서도 데이터가 잘못 변경되는 것을 막을 수 있다.

선점 잠금

데이터에 먼저 접근한 트랜잭션이 잠금을 획득하는 방식

  • 프로세스 잠금하고 같음

비선점 잠금

명시적으로 잠금을 사용하지 안ㅎ고 데이터를 조회한 시점의 값과 수정하려는 시점의 값이 같은지 비교하는 방식

  1. select 쿼리를 실행할 때 version 칼럼을 함께 조회
  2. 로직 수행
  3. update 쿼리를 실행할 때 version 칼럼을 1 증가, 이때 version 칼럼 값이 1에서 조회한 값과 같은지 비교하는 조건을 where 절에 추가
  4. update 결과로 변경된 행 개수가 0이면, 이미 다른 트랜잭션이 version 값을 증가시킨 것이므로 데이터 변경에 실패한 것이다. 이 경우 트랜잭션을 롤백
  5. update 결과로 변경된 행 개수가 0보다 크면, 다른 트랜잭션보다 먼저 데이터 변경에 성공한 것이므로 트랜잭션을 커밋

외부 연동과 잠금

트랜잭션 범귀 내에서 외부 시스템과 연동해야 한다면 → 선점 잠금을 고려해야 한다.

  • 외부 연동이 취소됐는데, 데이터 변경에 실패해서 트랜잭션이 롤백되는 문제 발생
  • 비선점 잠금을 사용하고 싶으면 트랜잭션 아웃박스 패턴 적용

증분 쿼리

동시에 쿼리를 실행시 2가 아닌 1만 증가하는 문제 발생할 시

  • 선점 잠금 사용 시 잠금 대기 시간만큼 응답 시간이 길어진다.
  • 비선점 잠금을 사용하면 대기 시간은 없지만 변경 실패 에러가 자주 발생할 수 있다.
  • 원자적 연산으로 처리하면 문제점을 없앨 수 있습니다.

잠금 사용 시 주의 사항

잠금 해제하기

잠금을 획득한 뒤에는 반드시 잠금을 해제해야 한다.

  • 그렇지 않을 경우 잠금을 시도하는 스레드가 무한정 대기하게 됨, 세마포어도 함께

대기 시간 지정하기

동시 접근이 많아지면 대기 시간이 길어지는 문제 발생 → 대기 시간을 지정해주어 문제 해결

  • 긴 대기가 초래하는 불안감을 줄여주어 사용자를 안심시킴

교착 상태 피하기

한 작업에서 2개 이상의 자원의 잠금을 획득하는 코드 구조

  • 교착 상태 : 2개 이상의 스레드가 서로가 획득한 잠금을 대기하면서 무한히 기다리는 상황
  • 잠금 대기 시간을 제한하는 방법으로 교착상태를 풀 수 있음
  • 지정한 순서대로 잠금을 획득하게 하는 방법으로 문제 해결

자료 출처

https://product.kyobobook.co.kr/detail/S000216376461

profile
코딩하는 대학생

0개의 댓글