본문 바로가기

[Kotlin&Spring] 5기 내일배움캠프

[Kotlin&Spring] 5기 어플리케이션 내의 동시성(Concurrency)

오늘은 동시성(concurrency)에 대해 알아보고자한다

어플리케이션에서 동시성은 다음의 두 가지가 존재한다
1. 오프라인 동시성(offline concurrency)
여러 데이터베이스 트랜잭션에 걸쳐 조작되는 데이터에 대한 동시성 제어
2. multi-thread 지원하는 어플리케이션 서버 시스템의 동시성

경합(race confidition)이 발생하면 생길 수 있는 동시성 문제는 아래 두개의 문제가 있다
1. 손실된 업데이트(lost update): 하나의 스레드만 읽고 수정하고 다른 스레드 수정 행동이 손실되는것
2. 일관성 없는 읽기(inconsistent read): 하나의 스레드가 여러 데이터를 읽는 사이, 다른 스레드의 수정이 읽는 정보를 오염시키는 것

동시성 문제 해결하는 법
1. 정합성(consistency)
2. 활동성(liveness)을 모두 갖추어야한다

1. 격리(isolation)
잠금(lock)이 필요하다 - 낙관적 락(optimistic lock), 비관적 락(pessimistic lock)이 있다

 


1) 낙관적 락: 한 세션에서 커밋하려는 변경 내용이 다른 세션의 변경 내용과 충돌하지 않는지 확인
레코드 데이터에 대한 변경을 진행해도 좋다 - 는 의미의 잠금을 얻는다
충돌 가능성이 낮음을 전제로 한다 -> 여러 사용자가 동시에 동일한 데이터에 작업하는 것 허용
구현방법: 시스템의 각 레코드에 버전 번호를 붙인다
낙관적 락을 얻는다는 것: 세션 데이터에 저장된 버전 vs 레코드 데이터 버전/비교
버전 증가 -> 이전 버전을 가진 세션이 잠금을 얻지 못하게 함 -> 일관성 보호

다른 방법 - UPDATE 문의 WHERE 절에 행의 모든 필드 포함시킨다
버전 필드를 추가할 수 없는 경우에 사용한다
WHERE 절이 커짐 -> 모든 필드를 비교하기 때문에 성능에 영향 줄 수 있음 -> 인덱스 사용 방법에 따라 다름

낙관적 락의 가장 큰 한계는 사용자가 작업 완료 후 커밋할 때 작업 실패를 알린다는 것이다
충돌 가능성이 높거나 충돌이 나면 안되는 상황에서 비관적 락을 사용해야한다

 


2) 비관적 락: 작업이 시작될 때 대상 데이터에 대한 잠금을 획득 -> 비즈니스 트랜잭션 -> 동시성 제어 문제 거의 발생 안함
충돌을 미연에 방지한다

잠금 유형은 대표적으로 3개가 있다
1. 배타적 쓰기 잠금(exclusive write lock): 세션 데이터 편집할 떄 비즈니스 트랜잭션이 잠금을 얻도록 요구함
동시에 두 비즈니스 트랜잭션이 동일한 레코드를 변경(write)할 수 없게 한다
데이터 읽기 관련 문제는 무시된다
2, 배타적 읽기 잠금(exclustive read lock): 비즈니스 트랜잭션의 편집 여부와 관계없이 항상 최신 데이터를 읽어야 할 경우 -> 비즈니스 레코드 로드 시마다 잠금을 얻어햐함
동시성에 대한 심각한 제한이기 때문에 잘 사용하지 않는다
3. 읽기/쓰기 잠금(read/write lock): 두 잠금 유형을 결합해 향상된 동시성을 제공
읽기와 쓰기 잠금은 상호배타적/ 읽기 할때 쓰기가 불가하고, 쓰기할 때 읽기가 불가하다
동시 읽기 잠금은 가능 -> 다수의 읽기 잠금 허용으로 동시성 향상

잠금은 어떻게 정해야할까?
잠금 시기: 비즈니스 트랜잭션이 데이터를 로드하기 전에 해야한다
잠금은 데이터의 최신 버전을 얻는다는 보장이 있을 때만 필요하다/확실한 상황!

잠금 해제는 언제 해야할까?
비즈니스 트랜잭션이 완료된 시점에 이루어져야한다

잠금 세분성(lock granularity)은 말 드대로 잠금을 세분화한다

또한 굵은 입자 잠금으로 잠금 테이블 경합 완화한다

잠금 대기 시간을 정해야 하는데 그 이유는 잠금이 오랜기간 이어지면 교착상태에 이르게 되기 때문이다
잠금을 할 수 없어 예외을 던지면 시스템 복잡성을 줄이고, 시스템에 부하가 줄어든다
장기 트랜잭션(long transaction)은 바람직하지 않지만 프로그래밍에 있어서 쉽다

굵은 입자 잠금(Coarse-Grained Lock)
애그리거트aggregate(여러 객체가 하나의 그룹으로 편집되는 경우)일 떄 한 항목을 잠그면 연관 항목을 모두 잠가야한다
굵은 입자 잠금은 이런 상황에서 여러 객체를 다룰 수 있는 단일 잠금이다
단일 경합 지점(경로)을 만든다 -> 하나의 잠금 -> 전체 그룹 잠금
공유된 낙관적 잠금은 단일 경합 지점에서 버전을 관리한다
공유된 비관적 잠금은 잠금 가능 토큰(loackable token)을 공유한다
루트(root)는 애그리거트와 경계(boudary)에 대한 접근 지점을 말한다
루트에 잠금을 부여한다

암시적 잠금(implicit lock)은 잠금 획득 코드를 모두 체크하기 위해 어플리케이션이 암시적으로 처리하는 것이다

잠금에 대한 코드 한 줄을 놓치게 되면 동시성 제어에 실패하기 때문에 이루어진다
프레임 워크, 계층 상위 형식, 코드 생성의 조합을 활용한다

 

출처: https://lena-chamna.netlify.app/post/concurrency_programming_thread_and_queue/

 

동시성 프로그래밍(Concurrency programming): 스레드와 큐(Thread and Queue)

Concurrency programming: introduce thread and queue in iOS

lena-chamna.netlify.app

 

 

이론을 배우고 코드 짜는 것은 작은 단위에 있어서 불필요하고, 역효과를 낼 수 있다

하지만 장차 큰 트래픽, 트랜잭션을 다루게 된다면 조금 더 효율적이고 차별화된 코드를 짤 수 있을 것이라고 믿는다

내 스스로 판단해서 효율적인 코드를 짤 수 있을 때 까지 조금 더 열심히 공부하고 배우고 싶다

어려운 내용이 많지만 힘내자!