🖥️ CS/DB

DB 데이터 동시성 이슈 해결법

한국의 메타몽 2024. 11. 13. 21:10

목차

  1. 작성 계기
  2. 동시성 이슈의 원인
  3. 해결책 (1) - 물리적 DB 1개 사용
  4. 해결책 (2) - DB간의 동기화 (MQ)
  5. 해결책 (3) - Kafka + 물리적 DB 1개 사용


1. 작성 계기

Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)의 강의를 듣고 인상 깊은 내용을 정리 중이다.

현업에서는 Master DB 1대 <-> Slave DB 1대로 레플리카구조로 이루어진 프로젝트가 대부분이다.
만약 동일 서비스에 Master DB가 여러대일때의 상황을 고려해본 적이 별로 없다.
(레플리카에 대한 설명은 클러스터링, 레플리카, 그리고 샤딩 게시글에서 확인해보자.)

강의를 통해 학습했으니 내용을 정리해보자.



2. 동시성 이슈의 원인

동시성 이슈의 원인

해당 프로젝트에서는 USER SERVICE 에서 ORDER SERVICE API를 Feign Client를 통해 호출중이었다.
ORDER SERVICE의 인스턴스는 2개이며, 각 인스턴스는 개별 H2 DB가 존재한다.
따라서 주문 API를 호출시 데이터 역시 분산 저장이 되며, 이는 즉 데이터 동기화의 이슈를 야기한다.



3. 해결책 (1) - 물리적 DB 1개 사용

정말 단순한 방법이다. 애초에 시작부터 물리적 DB를 1개 사용한다면 데이터가 분산되어 저장 될 일도, 데이터 동기화의 이슈도 고민할 필요가 없다.

다만 이 경우에는 동시성 이슈를 잘 다뤄야 한다.
동시성 이슈를 해결할 수 있는 방법은 많은데, 대표적으로 낙관적 락비관적 락이 있다.
아래 설명에서는 실제로 사용해본 경험이 있던 낙관적 낙관적 락비관적 락을 예시로 든다.
사용해보지 않아서 잘은 모르지만, Redis를 사용해서 분산 락을 구현하는 것도 방법이다.

비관적 락 (Pessimistic Lock)

  • 트랜잭션이 충돌한다고 가정하고 락을 검
  • DBMS의 락 기능 사용 (ex : SELECT FOR UPDATE)
  • 데이터 수정 시 즉시 트랜잭션 충돌여부를 확인 가능
  • 구현 방법으로 공유 락베타 락이 있음

공유 락

SELECT * FROM FEEDS WHERE id = 1 FOR SHARE;
  • 다른 사용자가 동시에 읽을 수는 있지만 Update, Delete는 불가능

베타 락

SELECT * FROM FEEDS WHERE id = 1 FOR UPDATE;
  • 다른 사용자의 읽기, 수정, 삭제 모두 불가


낙관적 락 (Optimistic Lock)

  • 트랜잭션이 충돌하지 않는다고 가정
  • 자원에 락을 걸어서 선점하지 말고 (= DB Lock을 걸지 말고) 커밋할 때 동시성 문제가 발생하면 그때 처리하자
  • JPA에서는 자체적으로 제공하는 버전 관리 기능(@Version)을 사용
    • JPA를 사용하지 않을 경우, hashcodetimestamp로 자체 구현 가능
  • 트랜잭션을 커밋하기 전 까지는 충돌 여부를 확인할 수 없음
/* MyBatis 예시 코드 */
update PRODUCT 
set 
    productUniqueId = ?,
    stock = ?,
    ...
    version = version + 1
where
    productUniqudId = ?,
    version = ?(트랜잭션을 시작한 스레드가 조회한 버전)
/**
* JPA Entity 예시 코드
*/
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double price;

    @Version
    private Integer version;
}

낙관적 락 vs 비관적 락

구분 Optimistic Lock Pessimistic Lock (공유락, 베타락)
정의 충돌이 없을 것으로 가정하여 락을 걸지 않음 충돌을 예상하고 미리 락을 검
사용법 JPA 사용시 @Version 사용
Mybatis의 경우 직접 만들어 사용
Mode 설정 및 쿼리에 직접 사용, DB단에서 설정 가능
별칭 낙관적인 락 / 비선점적인 락 비관적인 락 / 선점적인 락
장점 데드락 가능성이 적으며 성능의 이점 충돌에 대한 오버헤드가 줄어들며 무결성을 지키기 용이
단점 충돌이 발생하면 오버헤드 발생 충돌이 없으면 오버헤드가 발생


언제 쓰는게 좋을까?

  • 동시 수정이 빈번하게 일어나는지가 기준
    • 빈번하게 일어남 -> 비관적 락
    • 빈번하지 않음 -> 낙관적 락


4. 해결책 (2) - DB간의 동기화 (MQ)

DB간의 동기화(MQ)
각 인스턴스가 Message Queue를 구독하고 변경된 사항이 있으면 업데이트하는 구조다.
대표적으로 Apache KafkaRabbit MQ가 있다.



5. 해결책 (3) - Kafka + 물리적 DB 1개 사용

kafka와 물리적 DB개 사용
위의 2번 해결책에서 조금 더 업그레이드 된 방법이다.
Kafka를 사용함과 동시에 물리적 DB는 1개를 사용하는 것이다.
Kafka는 아무리 많은 데이터가 들어와도 1초에 수 만 건의 데이터가 처리한 구조이며, 이를 활용해 물리적 DB를 1대만 구비해두면 동시성 이슈를 해결할 수 있다.
개인적으로 1초에 수 만 건의 데이터를 뛰어넘는 트래픽이 아니라면, 이 방법이 동시성 이슈를 해결함과 동시에 개발자 입장에서 관리 포인트를 줄일 수 있는 방법이 아닐까 싶다.