[DB] 동시성 이슈 해결하기 (1)

한호성·2023년 4월 30일

Introduction

프로젝트 런칭 이후, 치명적 문제인 동시성 이슈가 나타났습니다.
이전에는 여러 Client가 api를 동시에 이용하는 경우에 많지 않아서, 해당 문제에 대해 인지하지 못했던거 같습니다.

동시성 문제를 해결하기 위해 어떤 방법을 사용하였는지, 그 방법에 기본적인 개념부터 설명해보도록 하겠습니다.

[목차]

  • 동시성 이슈가 날 수 있는 상황
  • Transcation & Lock
  • 비관락, 낙관락
  • 문제를 겪었던 상황
  • 문제 해결과정

동시성 이슈 발생 시나리오

동시성 이슈가 발생할 수 있는 상황을 생각해보겠습니다. 많은 상황들이 있겠지만, 우리 실생활에서의 예시를 보도록 하겠습니다.

우리가 어떤 쇼핑몰에서 물건을 구매하려고 합니다. 이 때, 물건에 개수가 1개가 남아있었다고 생각해봅시다.

다음과 같은 순서로 일이 진행된다고 생각해보겠습니다.
1. 서로 다른 두명이 쇼핑몰에 접속하였습니다.
2. 서로 다른 두명이 재고가 1개인 물건을 사기 위해 구매버튼을 동시에 누르게 됩니다.

(이 때, 동시성 이슈가 처리되지 않았다면 서로 다른 두명에게 모두 주문성공이라 안내가 나가게 될 것입니다. 하지만, 실물인 제품은 1개 뿐이니, 쇼핑몰 입장에서는 누군가에게는 물건을 주지 못하게 될 것입니다.)

동시성 이슈가 처리 된 상태에서 상황을 이어가 보도록 하겠습니다.
3. 서로 다른 두명이 동시에 요청을 보내고 서버에 요청이 날라옵니다.
4. 서버에서 해당 데이터에 접근하기 위해 DB에 query를 날리게 됩니다.
5. 이 때, 0.0001초라도 빨리 query를 날리는 client에게 해당 데이터를 수정하는 권한(lock)을 갖게 되고, 주문이 가능하게 됩니다.
6. 주문버튼을 눌렀지만, 이전 요청에 의해 재고가 0이되고 lock 획득한 client는 주문할 수 없다는 exception을 받게 됩니다.

실생활에서 접근해볼 수 있는 간단한 예시를 들어보았습니다. 당연히 발생할 수 있겠다 라고 이해할 수 있겠지요?

그림으로 한번 더 동시성 문제를 이해해 보셔도 좋을거 같습니다.

그렇다면, 이런 동시성 이슈를 어떻게 풀어내야 하는지 가장 기초적인 방법들을 소개해 보도록 하겠습니다.

Transcation & Lock

Transaction

Lock 개념을 이야기 하기 전에 , Transaction 이라는 개념을 설명하고 넘어가도록 하겠습니다.

Transaction 이란, 데이터베이스의 상태를 변화시키기 위해 수행하는 작업의 단위를 뜻합니다. (select, insert, delete, update)

이 Transaction 이란 단위는, 한 개의 쿼리문을 나타내지 않습니다. Transaction 은 개발자가 설계하는 대로 그 작업을 정할 수 있습니다.

Spring boot api service 코드로 간단히, 예를 들어 보겠습니다.

다음 코드는 게시판에 파일+게시글을 업로드하는 Service Method 입니다.

빨간색 밑줄친 부분을 보면 한 Transaction 내에서, 여러가지 작업 (파일정보도 저장 ,게시글 저장)등을 하는것을 보실 수 있습니다.

이처럼 Transaction은 개발자가 작업의 단위를 정의해서 사용할 수 있습니다.

그렇다면 왜 하나의 작업단위로 묶는 Transaction 이란 단위가 있게 된 것일까요?

간단하게 설명해 보겠습니다.

어떤 작업 도중 오류가 나게 되면, 프로그램은 exception을 날리고 해당 요청을 이어서 수행하지 못하게 됩니다.
일련의 과정 중 한 과정에서 문제가 생기면, 처리해야 되는 문제들 간의 선행되어야 하는 것들이 안되서 더 이상 비지니스로직을 수행 할 수 없게 되는 경우도 있고, 이전에 수행됬던 작업이 취소가 되어야 하는 상황이 발생하게 됩니다.
이 때, Transcation 이란 단위로 묶어져 있지 않으면, 하나하나 상황을 체크하면서, 분기처리를 해줘야 할 것입니다.

이러한 것들을 효율적으로 관리하기 위해 Transaction으로 묶는 단위가 생겨났다고, 이해하였습니다.

Transcation 의 특징

Transcation은 ACID (원자성,일관성, 독립성 지속성) 을 보장해야한다.

트랜잭션의 4가지 특징에 대해 간단하게만 설명하겠습니다.

원자성 : 트랜잭션이 데이터베이스에 모두 반영되던가,아니면 전혀 반영되지 않아야 한다는 것을 의미합니다.
( 위에 transcation의 필요성과 이어지는 내용입니다.)

일관성 : 트랜잭션의작업 처리 결과가 항상 일관싱이 있어야 한다는 것입니다.

독립성 : 둘 이상의 트랜잭션이 동시에 실행되고 있을 경우 어떤 하나의 트랜잭션이 다른 트랜잭션 연산에 끼어들 수 없다는 것을 의미합니다. 즉, 한 트랜잭션이 완료될 때까지, 다른 트랜잭션이 특정 트랜잭션의 결과를 참조할 수 없다는 것을 의미합니다.

지속성 : 트랜잭션이 성공적으로 완료된 경우, 결과는 영구적으로 반영되어야 한다는 것을 의미합니다.

#cf) 트랜잭션에는 격리레벨 (isolation level) 이란 것이 존재하고 이것을 통해 동시성 문제가 해결되는 것들도 있지만, 우리가 이야기한 예시는, 격리레벨로 해결되지 못하기 때문에 lock이라는 개념이 필요하다.

Lock

이제 Lock에 대해 알아보겠습니다.

Lock은 트랜잭션 처리의 순차성을 보장하기 위한 방법입니다. DBMS 마다 락을 구현하는 방식과 세부적인 방법이 다릅니다.

(진행중인 프로젝트는 Postgresql 에서, 테스트는 h2로 진행하다보니, 약간의 혼돈이 왔습니다.. ㅠㅠ)

Lock 종류

  1. Shared lock (읽기 잠금)

    어떤 트랜잭션에서, 데이터를 읽고자 할 때, 다른 shared lock은 허용이되지만, exclusive lock은불가하다. 즉 다른 사용자가 동시에 읽을 수 있게 하되, 변경은 불가하게 하는 것입니다..

  2. Exclusive lock(배타적 잠금)

    어떤 트랜잭션에서 데이터를 변경하고자 할 때, 해당 트랜잭션이 완료 될 때까지, 레코드를 읽거나 쓰지 못하게 하기 위한 lock 입니다.

다음 글에서는 이어서, JPA에서 지원하는 동시성 제어 메카니즘 낙관락, 비관락에 대해 알아보도록 하겠습니다.

Reference

https://mommoo.tistory.com/62
https://hudi.blog/jpa-concurrency-control-optimistic-lock-and-pessimistic-lock/

profile
개발자 지망생입니다.

0개의 댓글