최근 운영 중이던 서버에서 데드락으로 인해 결제가 되지 않는 이슈가 발생하였다.
트랜잭션, 데드락 등의 개념은 항상 이해했다고 생각하면서도 문제 발생 시 바로 원인과 해결법이 떠오르지 않는 개념이라 생각한다. 이번 이슈에 있었던 내용과 함께 새로이 알게 된 내용을 정리해 보려고 한다.

 

데드락은 서로 다른 트랜잭션이 있을때 각 트랜잭션에서 점유된 내용을 대기할 때 교착상태에 이르는 경우라고 할 수 있다.
임의의 테이블 ORDER 테이블이 있다고 가정하자 이때 PK 는 ORDER_ID라고 하겠다
1번 트랜잭션과 2번 트랜잭션이 아래와 같은 상황일 때 데드락은 발생한다.

-- 1번 트랜잭션: ORDER_ID = 1 인 ROW에 대한 쓰기락
UPDATE ORDER
   SET ORDER_STATE = '신규값'
 WHERE ORDER_ID = 1;

                    -- 2번 트랜잭션: ORDER_ID = 2 인 row 에 대한 쓰기락
                    UPDATE ORDER
                       SET ORDER_STATE = '신규값'
                     WHERE ORDER_ID = 2;

-- 1번 트랜잭션: ORDER_ID = 2인 ROW에 대해 접근하나 2번 트랜잭션의 락으로 인한 대기
UPDATE ORDER
   SET ORDER_STATE = '신규값'
 WHERE ORDER_ID = 2;

                    -- 2번 트랜잭션: ORDER_ID = 1인 ROW에 대해 접근하나 
                    --              1번 트랜잭션의 락으로 인한 대기
                    UPDATE ORDER
                       SET ORDER_STATE = '신규값'
                     WHERE ORDER_ID = 1;

 

위의 예시처럼 트랜잭션에서 데이터에 수정이 이루어지면 row 에 lock 이 걸리기 때문에 다른 트랜잭션은 대기하게 되고 이러한 상황이 맞물리며 트랜잭션간 교착상태에 걸릴 경우 락이 걸리게 되는것이다.

이 ORDER 테이블에 ORDER_NO라는 유니크한 값이 있다고 할 때 해당 컬럼에 유니크 키 설정이 안되어있고, 아래와 같은 상황이 발생한 것이 운영중이던 서버에서 발생했던 락 상황이다.

-- 1번 트랜잭션: ORDER_ID = 1 인 ROW에 대한 쓰기락
UPDATE ORDER
   SET ORDER_STATE = '신규값'
 WHERE ORDER_ID = 1;

                    -- 2번 트랜잭션: ORDER_ID = 2 인 row 에 대한 쓰기락
                    UPDATE ORDER
                       SET ORDER_STATE = '신규값'
                     WHERE ORDER_ID = 2;

-- 1번 트랜잭션: ORDER_NO = 1 은 ORDER_ID = 1 인 row 와 같은 하나의 row
UPDATE ORDER
   SET ORDER_STATE = '신규값'
 WHERE ORDER_NO = 1;

                    -- 2번 트랜잭션: ORDER_NO = 2는 
                    --             ORDER_ID = 2인 row와 같은 하나의 row
                    UPDATE ORDER
                       SET ORDER_STATE = '신규값'
                     WHERE ORDER_NO = 2;

 

얼핏 보면 문제 없을 것 같은 상황이다. 1번 트랜잭션은 1번 row 에만, 2번 트랜잭션은 2번 row 에만 접근하고 있기에 서로의 트랜잭션에 영향을 주지 않을 것 같이 예상된다. 당시 운영 중이던 서버에서 발생한 상황도 위와 같았음에도 불구하고 트랜잭션 락에 걸리게 된다. 락에 걸리게 된 이유는 제목에 있는 내용처럼 유니크 키를 설정할 경우에 해결되는 상황이었다.

 

락에 걸린 상황에 대해 설명해보겠다.

각 업데이트 문을 1 ~ 4번 업데이트라고 순서대로 칭하겠다.

1 ~ 2번 업데이트는 평범한 락 획득 과정이다. 3번 업데이트가 발생할 때 무슨 일이 발생할까?

우선 ORDER 테이블의 ORDER_NO = 1 인 값을 테이블에서 찾아야 할 것이다. ORDER_NO는 유니크 키가 아니므로 해당 row 를 찾기 위해 인덱스 스캔이 아닌 풀스캔이 발생할 것이다. ORDER_NO = 1 인 값을 찾기 위해 테이블 전체를 스캔하게 될 것이고 2번 트랜잭션에서 ORDER_ID = 1 인 row에 락을 걸어두었기 때문에 1번 트랜잭션은 대기에 빠지게 된다. 같은 경우로 4번 업데이트 문도 대기에 빠지게 되며 두 트랜잭션은 데드락에 걸리게 되는 것이다.

 

하지만 유니크 키를 걸게 되면 어떻게 될까?
3번 업데이트문과 4번 업데이트문은 풀스캔이 아닌 인덱스 스캔을 하게 될 것이고 해당 row 에 락을 한 것은 동일 트랜잭션이므로 스캔하는 것에 문제가 없게 되고, 두 트랜잭션은 독립적으로 처리되어 데드락 문제가 해결되게 된다.

업데이트 문을 간단히 써두었지만 실제 운영하던 서버 코드에서는 해당 원인을 찾는게 쉽지 않았다.
락이 걸린 순간의 업데이트 문과 연관된 테이블을 체크한 이후 해당 테이블에 관한 트랜잭션의 로직들을 확인하면서 의구심이 가는 부분을 확인했고, 그것이 맞았다.

 

해당 내용에 대해 실제 체크하기 위하여 임시 테이블을 만들고 사용하는 DB 툴인 DBeaver 에서 두개의 세션 스크립트를 열어두고 오토 커밋이 아닌 메뉴얼 커밋을 이용하여 테스트를 진행하였다. 그 결과 위와 같은 원인을 찾을 수 있었다.

 

인덱스, 제약 조건 등은 데이터의 신뢰성 및 DB의 성능을 위해서는 잘 처리해두어야 하지만 가끔 초기 설계 시 누락되거나, 프로젝트 진행 중에 추가된 컬럼들로 인해 올바르게 정리가 되어있지 않을 때가 존재한다. 하지만 이처럼 올바르게 정의되어지지 않은 테이블을 이와 같은 예상치 못한 문제가 발생할 가능성이 있다는 것을 다시 한 번 생각하게 되었다.

유니크 키를 설정한 해당 서버는 현재까지 데드락 이슈가 발생하지 않고 있다.

프로젝트에서 인터페이스 관련한 부분을 유지보수 할 때 알게된 내용이다.
정해진 데이터를 순서대로 테이블에 INSERT 해야 하는 상황이었다.

그런데 자꾸 하나의 트랜잭션에서 for문 돌면서 순서대로 insert 하는 데이터가 순서대로 들어가지 않는 것이었다.

A B C D
1 1 2 2
2 1 1 3
3 1 1 1

 

대략 위와 같은 데이터를 INSERT 하는 상황이었다고 가정하고 설명하겠다.
분명히 순서대로 데이터를 INSERT 문을 실행하였고, Spring 로그에 찍힌 ISNERT 문도 A 데이터 기준으로 1,2,3 순서대로 되었는데 말이다.

 

당시 입력되는 순서를 보고 테스트해주시던 분이 다른 데이터 기준으로 입력되는 것 같다고 말씀해주셨다. A 데이터 기준으로 말하자면 3->1->2 순서로 입력되고 있었다.

도대체 어찌된 일인지 테이블의 구성을 살펴보던 중 예상되는 이유를 발견하였고,
해당 부분을 수정하고 테스트한 결과 원하는 순서대로 INSERT 할 수 있었다.


그 이유는 바로 테이블 키에 있었다. 당시 해당 테이블엔 별도의 PK 는 없었고, 유니크 키만 B,C,D의 복합키로 걸려있었다.

데이터가 실제로 입력되던 3->1->2(A 기준) 이 B -> C -> D 의 키 순으로 정렬되어 INSERT 되고 있다면 딱 설명이 되는 상황이었던 것이다.

 

실제로 해당 테이블의 키 설정을 A 컬럼을 PK로 하여 다시 설정한 후에는 원하는 순서대로 INSERT 할 수 있게 되었다.

해당 상황은 인덱스와 관련이 있지 않을까 생각된다. 인덱스 관련하여 학습 했을 때 데이터베이스는 인덱스를 기준으로 정렬한 테이블을 저장된다는 내용을 본 적이있다. 트랜잭션에서 데이터들을 해당 테이블에 INSERT 할 때 PK 값이 없다보니 유니크키에 걸린 인덱스를 기준으로 정렬해서 INSERT 된것으로 예상된다.

PK는 설정시 인덱스를 기본적으로 생성한다. 따라서 테이블에 PK를 설정한 이후에는 PK 인덱스를 기준으로 순서대로 INSERT 된 것으로 판단된다.

+ Recent posts