프로그래밍/database

DB동시성 문제와 격리성

브래드 킴 2023. 3. 31. 16:33
728x90

일단, DB격리성이란 무엇인지에 대해 알아보자.
 
DB 격리(Isolation)성은 관계형 데이터베이스에서 여러 개의 트랜잭션이 동시에 실행될 때, 각각의 트랜잭션들이 서로에게 영향을 미치지 않도록 데이터 일관성과 동시성을 보장하는 개념이다.

여러 개의 트랜잭션이 동시에 실행될 때 발생할 수 있는 문제점으로는 다음과 같은 것들이 있다.

Dirty Read  : 한 트랜잭션이 다른 트랜잭션이 수정 중인 데이터를 읽을 수 있는 문제이다. 예를 들어, A트랜잭션이 데이터를 update만 하고 아직 commit하지 않았는데, B트랜직션이 조회했을때 update한 사항이 조회가 되는 상황이다. 이게 왜 문제가 되냐면, A가 어떠한 이유에 의해서 트랜잭션을 rollback했을때 B는 잘못된 값을 알고 있게 되는 꼴이 되기 때문이다.

Non-Repeatable Read : 한 트랜잭션에서 같은 쿼리를 여러 번 실행했을 때, 그 결과가 다르게 나타나는 문제입니다. 예를 들어, B가 금액을 조회했을때 처음에는 10,000원이었는데, 트랜잭션이 끝나기 전에 그 사이에 A가 돈을 출금하여 데이터를 0으로 바꾼것이다. 이때에 B가 다시 select를 하였을때, 금액은 0원이 되어버려 있는데, 한 트랜잭션안에서 두번의 쿼리를 날렸을때 각각 다른 값이 조회가 되는 것이다. 이게 문제가 되는 이유는 B입장에서는 처음조회했을때 10,000이었기 때문에, 출금을 시도할수 있어,  데이터의 일관성이 깨지게 된다.

Phantom Read : 한 트랜잭션이 다른 트랜잭션에서 새로운 데이터를 추가하거나 삭제할 때, 해당 데이터를 읽을 때 발생하는 문제이다다. Non-Repeatable Read와의 공통점은 한트랜잭션에서 같은 조회를 여러번했을때 데이터의 차이가 발생하다는 것이다. 둘의 차이점은 phantom read는 데이터가 추가되거나 삭제되는 상황에 발생을 한다. Phantom Read(유령 읽기)는 데이터집합에 추가/삭제가 발생하여 데이터집합 자체가 달라지게 되어 갑자기 유령처럼 데이터들이 튀어나오는 것이다.
 
그래서, 위의 문제점들을 해결하기 위해 DB격리성이라는 개념이 나오게 된다. DB 격리성은 이러한 문제들이 발생하지 않도록 하기 위해 각각의 트랜잭션이 독립적으로 실행되는 것을 보장한다. 이를 위해 DBMS에서는 다양한 격리 수준(Isolation Level)을 제공하며, 격리 수준이 높을수록 데이터 일관성은 유지되지만 동시처리성은 낮아지게 된다.
 
관계형 데이터베이스에서 DB 격리(Isolation)은 여러 트랜잭션이 동시에 실행될 때 발생할 수 있는 문제를 해결하는 중요한 개념이다. DB 격리는 데이터의 일관성을 유지하고 동시성을 보장하기 위해 필요한 것으로, 다음과 같은 종류가 있다.

Read Uncommitted : 다른 트랜잭션이 아직 커밋되지 않은 데이터를 읽을 수 있다. 이로 인해 Dirty Read(더티 리드)문제가 발생할 수 있다. 즉, 데이터가 변경되었다면, 커밋되지 않았다 하더라도 읽을 수 있도록 하는 격리수준이다.

Read Committed : 다른 트랜잭션이 커밋된 데이터만 읽을 수 있다. Dirty Read(더티 리드) 문제는 발생하지 않지만 Non-Repeatable Read(반복 불가능한 읽기) 문제가 발생할 수 있다. 즉, commit된 데이터만 읽을 수 있도록 하는 격리 수준이지만, 데이터가 추가되거나 변경되어 커밋된 데이터를 다른 트랜잭션에서 여러번 조회하였을때, 일관성이 깨지는 문제가 발생할 수 있다는 것이다.

Repeatable Read : 한 번 읽은 데이터는 같은 트랜잭션 내에서는 항상 같은 값을 가지게 된다. 그 원리로, 트랜잭션이 실행되는 동안 해당 데이터에 대한 공유 락(Shared Lock)을 걸어 다른 트랜잭션이 해당 데이터를 변경하지 못하도록한다. 즉, 한 트랜잭션이 데이터를 조회를 시작하면, 다른 트랜잭션은 데이터에 추가/변경/삭제 사항을 가할수 없다는 것이다. 
참고로, mysql에서는 Repeatable Read가 기본(oracle, postgres는 read committed가 기본)이고, Repeatable Read는 shared lock을 기본으로 걸고 있다. 

Serializable :  동시에 실행되는 여러 트랜잭션들을 순차적으로 실행한 것과 같은 결과를 보장한다. 트랜잭션을 순차적으로 실행하므로, 한 트랜잭션이 작업을 하는 동안 다른트랜잭션이 동시에 작업을 하는 병렬처리 문제가 애초에 발생하지 않는 것이다. 즉, 한트랜잭션이 조회하는동안 다른 트랜잭션은 조회도 불가능하다. 따라서 앞서 말한 문제점들이 원천 차단되지만, 이 설정에서는 동시성이 매우 낮아지게 되므로 성능에 이슈가 발생하게 된다는점을 고려할 필요가 있다.
 
그렇다면, 대용량 트랜잭션을 많이 처리하는 Spring에서는 어떻게 동시성을 DBMS와 함께 유기적으로 처리하는지 살펴보자.
 
먼저, Spring에서 Service단에서 사용하는 Transactional 어노테이션에 대해서 살펴보자. Spring의 Transactional 어노테이션을 붙이게 되면, DBC 드라이버나 JPA 구현체에서 지원하는 DB 트랜잭션 격리 수준과 동일한 격리 수준을 사용하게 된다. 즉, mysql이면 repeatable read 격리 수준을 따르게 되고, 오라클의 경우 Read Committed를 따르게 되는 것이다.
 
스프링에서는 transactional 어노테이션을 통해 DB 격리수준을 따르고, 여기에 커스텀 설정을 옵션으로 줄 수가 있는데, 여기서는 Optimistic locking과 Pessimistic locking을 통한 동시성을 처리에 살펴보고자 한다.

Optimistic locking은 데이터를 조회할 때 버전 정보를 함께 가져오고, 데이터를 변경할 때 이 버전 정보를 확인하여 다른 사용자가 동시에 변경하지 않도록 합니다. 즉, where 조건에 버전정보를 조건에 걸어서 update를 하는 것이다. 다만, 트래픽이 많을때에 이경우엔 update시에 예외가 발생하는 예외상황이 많이 발생할수도 있다. 

Pessimistic locking은 데이터를 변경하려는 사용자가 작업을 완료할 때까지 다른 사용자들이 해당 데이터를 조회조차 할 수 없도록 lock을 거는 것이므로, serialize 또는 (select for update)와 같은 수준의 격리성을 갖는 것이라 볼 수 있겠다. 아무래도 사용하기엔 부담스러운 설정이다. 와 유사하게 생각하면 될듯하다.

결론적으로 spring에서 일반적으로 많이 사용되는 아키텍처를 적어보자면, 아무래도 DBMS에서는 일반적으로 read commited를 사용될 것이고, 프로그램에서는 일반적으로는 Optimistic Locking이 더 많이 사용되는 경향이 있다. 다른트랜잭션의 read까지 locking하는 것은 아무래도 너무 엄격하기 때문이다.
 

728x90

'프로그래밍 > database' 카테고리의 다른 글

로컬 커맨드창에서 docker mysql로 바로 접속  (0) 2023.01.16
redis expire time(set, hset, zadd) 에 관하여  (0) 2022.12.16
RDB index 원리  (0) 2022.12.15
MYSQL SHOW INDEX  (0) 2022.12.15
GROUP BY 2개 이상  (0) 2022.12.10