Post

<오라클 성능 고도화 원리와 해법1> Ch02-01 트랜잭션 동시성 제어

오라클 성능 고도화 원리와 해법1 - Ch02 트랜잭션과 Lock - 01 트랜잭션 동시성 제어

(1) 동시성 제어

오랜 기간 시스템을 운영하다 보면 데이터 구조가 점점 복잡해지고 데이터 정합성도 많이 흐트러져 새로운 업무 요건에 빠르게 대응할 수 없는 상황에 직면하게 된다. 결국, 엄청난 예산을 들여 신 시스템 구축에 착수하게 되고 대규모 인력이 오랜 기간 휴일 밤낮을 잊은 채 고생하면서 우여곡절 끝에 시스템 오픈을 맞이한다. 그런데 시스템 오픈 후 한두 달이 지나고 나서 데이터를 조회해 보면 또 다시 곳곳에서 이해할 수 없는 데이터들이 쌓이는 것을 발견하게 된다. 밤을 새워가며 프로그램 로직에 구멍이 있었는지 확인해보지만 끝까지 원인이 밝혀지지 않는 경우도 있다. 왜 이런 일이 발생하는 걸까?

사용자의 잘못된 조작, 프로그램 버그가 원인이겠지만 보다 근본적으로는 다중 사용자 환경에서 발생할 수 있는 갖가지 동시성 문제에 대한 충분한 고려 없이 시스템 개발이 이루어지는 데에 그 원인이 있다고 생각한다.

지금처럼 인터넷으로 사용자가 직접 자신의 주문을 처리하는 환경에서는 미처 생각지 못했던 일들이 이전보다 훨씬 자주 발생한다. 더군다나 동시 접속자 수도 이전과는 비교할 수 없을 정도로 많아졌기 때문에 동시성에 대한 이슈는 훨씬 더 중요해졌다. 하지만 필자가 여기저기 컨설팅하러 다니면서 경험한 바로는 동시성을 신중하게 고려하면서 개발이 이루어지는 경우는 많지 않았다. 동시성 제어에 대한 기본적인 개념조차 없는 설계자와 개발자들도 많이 만나게 된다.

지금 당장 자신이 회원으로 가입해 있는 어떤 사이트든 방문해서 브라우저를 좌우로 두 개 띄우고 회원 정보 변경 화면으로 각각 이동해보자. 그리고 좌측 화면에서는 전화번호를 바꾸고 커밋, 우측 화면에서는 생년월일을 고치고 커밋한 후에 다시 회원 정보를 조회해보면 전화번호가 이전으로 롤백된 것을 확인할 수 있을 것이다. 대부분의 사이트에서 사용자가 갱신한 필드 값만 업데이트하지 않고 화면에 뿌려진 전체 필드를 업데이트하도록 구현하기 때문에 생기는 현상이며, 동시 트랜잭션에 의해 일부 갱신 정보가 유실될 수 있음을 간과한 채 프로그래밍했기 때문에 발생하는 문제점이다.

동시성 제어(Concurrency Control)란, 동시에 실행되는 트랜잭션 수를 최대화하면서도 입력, 수정, 삭제, 검색 시 데이터의 무결성이 유지될 수 있도록 노력하는 것을 말한다. 여러 개의 트랜잭션이 동시에 수행될 때, 데이터베이스 애플리케이션은 이들 트랜잭션이 서로 간섭을 일으키는 현상을 최소화하면서 데이터의 일관성과 무결성이 보장되도록 개발되어야 하는 것이다.

그림 2-1

  • 동시성(Concurrency): 다중 사용자가 같은 데이터를 동시에 액세스
  • 일관성(Consistency): 자신이 발생시킨 변경 사항과 다른 트랜잭션의 변경 사항(읽을 수 있는 버전만 허용)을 포함해 일관성 있는 상태로 데이터를 제공

그런데 동시성 제어가 어려운 이유는 동시성과 일관성이 트레이드오프(Trade-off) 관계에 있다는 데에 있다. 즉, 동시성을 높이려고 Lock의 사용을 최소화하면 읽기 일관성을 유지하기 어렵고, 데이터의 일관성을 높이려고 Lock을 많이 사용하면 동시성이 떨어지게 된다. 따라서 동시성과 일관성이라는 두 마리 토끼를 한꺼번에 잡으려면 매우 세심한 주의가 필요하다.

동시성 제어를 구현하는 방법을 본격적으로 설명하기에 앞서 트랜잭션의 기본 개념부터 살펴보자.

(2) 트랜잭션이란?

일관성 있게 데이터가 처리되려면 두 개 이상의 갱신 연산이 동시에 실행될 수 있어야 하는데, 불행히도 이는 불가능한 일이다. 따라서 트랜잭션 관리 능력을 갖춘 DBMS는 차선책을 사용해서 동시 실행을 구현한다. 즉, 여러 개의 수정 작업이 하나의 작업처럼 전부 처리되거나 아예 전부 처리가 안 되도록(All or Nothing) 하는 것인데, 이러한 일의 최소 단위를 트랜잭션이라고 한다. (-> 트랜잭션의 ‘원자성’)

트랜잭션은 업무 처리의 기본 단위를 정의하는 것이므로 실제 애플리케이션 설계 단계에서는 업무적인 관점에서 좀 더 포괄적으로 정의하게 된다. 예를 들어, “계좌 이체”라는 트랜잭션을 정의할 때 최초에 현금 잔고를 확인하고 이체 비밀번호와 보안 카드 코드 값을 입력받아 최종 이체가 완료되는 순간까지를 포함해서 넓게 정의할 수도 있다.

실제 있었던 사례인데, 필자가 회사 근처 ATM기에서 잔고가 충분한 것을 확인하고 인출을 시도했는데 “잔고가 부족합니다”라는 메시지를 받은 적이 있다. 순간 당황했지만, IT 직종에 근무하는 덕에 금방 원인을 짐작할 수 있었다. 아내에게 전화를 걸어 확인해 보니 아내도 역시 같은 시각에 집 근처 은행에서 현금을 인출하고 있었던 것이다. 뒤에서 설명하겠지만 하나의 레코드를 읽고 트랜잭션이 계속 진행되는 동안 다른 트랜잭션에 의해 앞서 읽은 데이터가 다른 값으로 변경되는 Non-Repeatable Read 현상이 발생했던 사례다. 이런 현상 때문에 사용자가 당황하는 일을 미연에 방지하려면 현금 잔고 조회부터 하나의 트랜잭션으로 묶어서 정의하고 그에 따라 출력 메시지도 바뀌었어야 옳지 않을까라는 생각을 했다. 값이 잘못 갱신되지는 않았으므로 데이터 조작 측면에서의 트랜잭션은 완벽했다고 볼 수 있지만, 업무적인 관점에서의 좀 더 세밀한 트랜잭션 제어가 필요하다는 뜻이다.

온라인 쇼핑몰에서 이루어지는 “주문” 트랜잭션은 어떨까? 상품 정보를 확인하고 각종 주문 정보를 입력하고 나서 최종 결제를 완료하는 순간까지를 하나의 트랜잭션으로 정의하는 것이 타당하다. 결제는 완료되었는데 적립금 누적 처리가 누락되거나 배송지 입력이 실패하는 일이 발생해서는 안 되며, 모든 처리가 성공 또는 실패로 매듭지어져야 한다. 뿐만 아니라 적어도 수분 이상 소요되는 주문 처리가 진행되는 동안 다른 트랜잭션에 의해 상품 가격이 바뀌거나 재고량이 변경될 때는 어떻게 처리할지에 대한 세심한 고려가 있어야 한다. 프로세스 설계 및 구현 과정에서 이를 간과함으로 인해 일마감 또는 월마감 시점마다 데이터를 일일이 수작업으로 보정해서 맞추는 회사를 여러 번 경험했다.

(3) 트랜잭션의 특징(ACID)

데이터베이스 갱신과 관련해 트랜잭션이 4가지 중요한 특성을 갖는다.

원자성(Atomicity): 앞서 트랜잭션의 정의 부분에서 이미 설명한 바와 같고 더 이상 분해가 불가능한 업무의 최소 단위를 말한다.

일관성(Consistency): 트랜잭션이 그 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 변환한다. 즉, 트랜잭션 실행의 결과로 데이터베이스 상태가 모순되지 않는다.

격리성(Isolation): 트랜잭션이 실행 중에 생성하는 연산의 중간 결과는 다른 트랜잭션이 접근할 수 없다.

영속성(Durability): 트랜잭션이 일단 그 실행을 성공적으로 완료하면 그 결과는 데이터베이스에 영속적으로 저장된다.

우리가 고급 DBMS를 사용하는 이유는 성능, 관리의 편이성 등 여러 가지 측면이 있지만, 무엇보다도 트랜잭션 처리 능력이 가장 기본적이고 핵심적인 요소라 할 수 있다. 트랜잭션의 처리 결과가 데이터의 일관성을 해치지 않도록 하려면 트랜잭션의 순차적 진행을 보장할 수 있는 직렬화 장치가 필요하다. 이를 위해 DBMS가 공통적으로 사용하는 메커니즘이 Lock이다. 그뿐만 아니라 오라클은 더 높은 수준의 읽기 일관성을 보장하기 위해 Undo 데이터를 활용한 독특한 읽기 일관성 모델을 사용하고 있다.

This post is licensed under CC BY 4.0 by the author.