본문 바로가기
공부/프로그래밍

[mysql] 동시성 문제 해결하기(FOR UPDATE)

by demonic_ 2018. 12. 5.
반응형

이전에 서비스를 하나만 했을때는 트랜젝션에 대한 처리만 고민했으면 됐었다.

당시에는 서비스도 작고 대부분 1개의 미들웨어만 사용하기 때문에 동시성(Concurrency) 문제를 고민할 필요가 없었다. 그런데 최근 서비스를 하고 있는 것은 최소 서버를 2개이상 띄워놓고 로드밸런싱을 하기 때문에 중요한 사안일수록 동시성 문제는 이슈가 되었다. 


현재 서비스 하는 것중에는 스탬프를 찍어주는게 있는데 어느날 보니 스탬프가 2중으로 찍혀있었던 것을 발견했다. 즉 5개가 나가야 하는게 10개가 찍힌 샘. 당시에는 동시성 문제는 아니었고 WEB에서 호출할 때 잠시 멈춰있는 사이에 한번 더 클릭을 하게되어 2중으로 처리되었던 것이다. 지금은 몇개 되지않지만 유저가 몰리고 해당 서비스가 더욱 활성화 될수록 피해가 기하급수적으로 늘어날 것이라 판단했다. 그래서 급히 어제 수정에 들어갔다.


WAS에서 처리를 하기에는 최종 데이터 저장소가 아니기 때문에 적합성을 검토할 수가 없었다. 그래서 데이터가 저장되는 곳, DBMS에서 처리하기로 했다. 현재 사용하고 있는 DBMS는 Mysql 이며, Isolation level을 변경하는 방법도 있겠지만 하루에도 수십, 수백만 건이 쌓이는 데이터에 동시성을 항상 검토하게 하는 것은 DBMS에 무리가 갈 것이라고 판단했기에 ROW 단위의 LOCK을 쓰기로 했다.


Mysql의 InnoDB의 경우 2가지로 사용할 수 있는데 하나는 LOCK IN SHARE MODE와 FOR UPDATE 이다. 이 둘의 용도는 약간 다르지만 트랜잭션이 끝나기 전까지만 유효하기에 auto_commit 을 꺼야 한다. 그래서 이제와 DBMS의 설정을 바꿀 수 없던 터라 FOR UPDATE를 사용하기로 했다.


SELECT 

NAME, CODE, CNT

FROM STAMP 

WHERE id=10 

FOR UPDATE;




FOR UPDATE 로 SELECT를 하면 다른 세션에서 DML 이 요청이 와도 잠김상태가 된다. 그래서 만약 WAS1에서 호출을 했다면 WAS2에서 동일한 쿼리로 호출했을 때 SELECT를 하지 못해 지연이 된다. FOR UPDATE는 트랜잭션이 끝나는 시점에 풀린다. 그래서 WAS1에서 모든 처리가 다 될때까지 다른곳에서 호출한다 하더라도 계속 잠겨있고, 트랜잭션이 풀리면 변경된 데이터를 가져가게 된다.


테스트를 하다가 해당 ROW가 풀리지 않아 한참 찾았었는데 알고보니 트랜잭션 안이 아닌 밖에다가 SELECT FOR UPDATE를 실행했었고, 트랜잭션이 풀리지 않아(commit 이든 rollback 이든 둘중 하나를 해줘야 하는데 그러지 않았음) 해당 WAS가 계속 LOCK을 잡고 있었다. WAS를 종료하니 그제서야 LOCK이 풀렸다.


만약 DBMS에서 LOCK을 하고 있는지 확인하려면 아래 명령어를 이용하면 된다.


# DBMS에서 LOCK 확인하는 법

show processlist


LOCK이 걸려있으면 State 항목에 'Waiting for table metadata lock' 이라고 되어있다. 

해당 프로세스를 해제하는 방법은 kill 명령어를 사용하면 된다.


kill [id];



- 고민점


추후 서비스를 어떻게 확장시킬지 생각해봐야 겠지만 만약 NoSQL로 가게된다면 DBMS의 성능에 너무 기대지 않는편이 좋지않을거라 생각한다. WAS단에서 막는게 좋을거라 판단되는데 차선으로 생각한 최고의 방식은 다음이라 생각했다.


1) 관련 사항을 우선 업데이트 하고 그 결과값(성공 1, 실패 0)을 받는다.

2) 성공을 했을경우 로직을 실행하고, 실패한 경우 해당 로직을 건너뛴다


이렇게 될경우 둘중 동시에 들어온다 하더라도 먼저 UPDATE 하는쪽이 데이터를 처리할 것이기 때문에 문제가 없을거라 판단했다.

반응형

댓글