일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- 글또 #다짐
- Database
- 오블완
- redis
- 후기
- 티스토리챌린지
- 부꾸러미
- 테오의 스프린트
- 북극곰
- 구슬
- jscode
- 체험
- dto projection
- 부꾸
- bean
- Spring
- 보따리
- SQL
- Java
- 프로그래머스
- 트러블슈팅
- jooq
- 동적 SQL
- 사이드 프로젝트
- 눈송이
- 코드트리
- open contribution jam
- 모의면접
- spring context
- 글또
- Today
- Total
벤티의 개발 로그
[Database] Lock, Lock, Lock 본문
첫 업무
출근 3일 차에 첫 업무를 받게 되었다. 아직 코드 분석도 완벽하게 끝내지 못했고, 이제 겨우 프로젝트 코드가 어떻게 돌아가는지 이해한 후였다.
업무를 분석하면서 생각해 볼 것이 굉장히 많다는 것을 깨달은 데는 오랜 시간이 걸리지 않았다. 다행히 구현 전에 어떻게 구현할 것인지에 대한 계획을 작성한 후 보고를 올리라는 지시를 해주셨기 때문에, 혼자 고민의 늪에 빠지지 않아도 되었다.
피드백: 동시성 제어
당연히 업무에 대해 자세하게는 작성할 수는 없지만, 가장 중요하면서도 고민됐던 것은 말로만 듣던 '동시성 제어'였다. 나는 기존 기술 스택과 3일 동안 분석했던 프로젝트 코드를 기반으로, 이를 통제하기 위한 3가지 방안을 제시했다.
1. Java에서 제공하는 Lock을 쓰자.
[개발] JavaScript or TypeScript?
드디어, 취업했다!이번 글을 작성하기에 앞서 간단한 근황을 공유해보고 싶다. [후기] 글또를 마무리하며: 1편모든 것의 시작우리 학교에는 지금까지도 활발한 단톡방이 있다. 주로 소융대 학생
ventilog.tistory.com
일단 결과적으로, 직전 글에서 작성한 대로 JavaScript나 TypeScript가 아닌 Java로 개발하게 되었기 때문에 Java에서 제공하는 Lock들을 최우선으로 고려했다. 기존 프로젝트 코드에서는 ReentrantLock로 동시성을 제어하는 부분이 있었고, unlock까지 제대로 처리하고 있었기 때문에, 큰 이상이 없다고 생각했기 때문이다.
하지만 이는 곧바로 선택지에서 지워버렸다. 타이밍은 기획과 프로젝트들에 대한 추가 설명을 들은 후였다. 이것도 자세하게 말할 수는 없지만, 기존에 쓰이고 있던 부분에 해당하는 로직과 달리 내가 받은 업무에 해당하는 로직은 사용자의 요청에 따라 실행되는 빈도가 훨씬 높았기 때문이다.
그리고 결정적으로, 조사하면서 확인한 내용 중 ReentrantLock은 단일 JVM 내에서만 동기화가 가능하므로 분산 환경에서는 동시성 제어가 불가능하다는 내용이 있었는데, 우리 프로젝트는 다중 서버 환경에서 구현되었기 때문에 쓰지 않는 것이 좋겠다고 판단했다. (기존에 쓰이고 있던 ReentrantLock은 비즈니스 로직 상으로 하나의 서버에서 쓸 수 있었다.)
2. Redis를 쓰자.
이전에 했던 사이드 프로젝트에서 썼던 방법이다. 그때 나는 동시에 여러 명의 사용자가 이메일 인증을 요청할 경우를 대비해 Redis를 이용해서 중복되는 요청이 처리되지 않도록 구현했다.
하지만 이 방법도 포기했다. 이유는 프로젝트 일정이었다. 사이드 프로젝트였다면 Redis를 썼었겠지만, 놀랍게도 내가 받은 업무만 처리되면 곧바로 출시할 예정(...!)이라고 하셨기 때문에 새로운 기술 스택을 도입하면 그만큼 시간이 더 오래 걸릴 것이라고 판단했다.
그리고 Redis에 문제가 생길 때를 대비해 Master와 Slave도 나누어서 구현해야 한다는 것을 앞서 말한 사이드 프로젝트가 끝난 후 알게 되었는데, 이 내용까지 공부하고 구현하기에는 더 오랜 시간이 걸릴 것 같았다. 조사 결과, 이 방법을 제일 많이 사용하는 것 같았지만, 이는 다음 기회에...ㅠㅠ
3. MySQL에서 제공하는 Lock (DB Lock) 을 쓰자.
남은 방법은 하나였다. (물론 더 좋은 방법이 있을 것이라고 믿는다.) DB였다. 사수분의 피드백이자 추천이기도 했다. 학부 데이터베이스 강의에서 배운 'DB의 Lock을 쓰는 것은 권장하지 않으며, 생각할 것도 많다'라는 내용이 머릿속에 생생했지만, '과연 진짜 그럴까?'에 대한 검증과 구현은 해본 적 없었기 때문에 이번에 공부하고 사용해 보기로 결정했다.
비관적 락 vs 낙관적 락
비관적 락 (Pessimistic Lock)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET points = points - 100 WHERE id = 1;
비관적 락은 조금이라도 먼저 들어온 트랜잭션이 어떤 레코드를 읽으려고 할 때, 읽기에 대한 Lock을 획득하는 방식이다. 즉, 한 트랜잭션이 DB에 완전히 커밋되기 전까지 다른 트랜잭션은 동일한 레코드를 동시에 수정하지 못하도록 막을 수 있다.
따라서, 데이터 무결성이 중요한 경우에 사용하는 것이 좋다. 어느 한 트랜잭션이 종료된 후에 Lock을 반환하기 때문이다.
MySQL에서 쓰이는 InnoDB에서는 위 예시 쿼리처럼 SELECT... FOR UPDATE 문을 이용해서 구현할 수 있다!
낙관적 락 (Optimistic Lock)
SELECT version, points FROM users WHERE id = 1;
UPDATE users
SET points = points - 100,
version = version + 1
WHERE id = 1 AND version = 3;
비관적 락과 달리, Lock은 Lock인데 진짜 Lock은 아니다. 대신, 비관적 락과 달리 레코드를 읽는 시점이 아니라 레코드를 수정하려는 시점에 검사를 하는 방법으로 '논리적으로 Lock을 구현'한다. 따라서, 위 예시 쿼리처럼 version이나 타임스탬프를 통한 검증이 필수적이며, 필요에 따라 이를 위한 별도의 속성이 필요하다.
물론 논리적으로 Lock을 구현하는 것이기 때문에, 구현 방법도 다양하고 락으로 인한 대기도 비관적 락에 비해 상대적으로 적어 Deadlock으로부터 자유롭다.
하지만 충돌을 사전에 차단하는 비관적 락과 달리, 낙관적 락은 충돌이 발생한 후에 트랜잭션을 롤백한다. 바로 이 점 때문에 어떤 락을 사용할지 결정할 수 있었다.
결론
일단 나는 비관적 락을 쓰기로 했다. 이유는 아래와 같다.
1. 외부 연동이 필요한 비즈니스 로직이다. [1]
2. 그리고 데이터 무결성이 매우 중요하기 때문에, 레코드를 읽을 때 잠그는 것이 비즈니스 로직 상으로 안전하다고 판단했다.
첫 업무였던 만큼 조사하는 데 시간이 걸렸지만, 결과적으로 '동시성 제어'에 대해 깊이 생각해 볼 수 있는 좋은 기회였다! 개발자에게 매우 중요한 주제인 만큼, 앞으로도 꾸준히 마주할 것이기 때문에 따로 깊게 공부해야 할 것 같다!