작성
·
473
답변 1
33
안녕하세요 SW09님^^
실무를 많이 경험해보신 분의 좋은 질문이네요.
결론적으로 여러가지 좋은 방법이 있지만, JPA가 제공하는 낙관적 락(@Version)을 사용하면 됩니다.
이 문제는 A 유저와 B 유저가 동시에 같은 데이터를 수정하는 단순한 문제로 정의할 수 있습니다.
예를 들어 A 유저가 상품의 가격을 봤을 때는 1000원 이었는데, 이 가격을 1500원으로 수정하려는 순간, B 유저가 같은 상품의 가격을 2000원으로 갱신하고, 커밋까지 완료해버렸습니다.
그런데 이후에 A 유저는 상품의 가격이 2000원으로 변한 것을 모르고, 1500원으로 수정을 하고, 커밋을 완료하게 됩니다.
결과적으로 상품의 최종 가격은 2000원은 무시되고, 1500원이 됩니다.
이것을 두 번의 갱신 분실 문제(second lost updates problem)이라 합니다.
쉽게 이야기해서 마지막에 커밋한 내용이 인정된다는 것이지요.
이렇게 마지막에 커밋한 내용을 인정하면 아무 문제가 없습니다.
문제는 누군가 중간에 내가 수정하려는 데이터를 변경해버렸을 때, 그것을 감지하는 것이 어렵지요.
결국 다음과 같이 3가지 선택지가 있습니다.
- 마지막 커밋만 인정하기
- 최초 커밋만 인정하기
- 충돌하는 갱신 내용 병합하기
최초 커밋만 인정하기나, 충돌하는 갱신 내용 병합하기는 결국 A 유저가 상품을 조회한 1000원 부터 A 유저가 상품을 1500원으로 변경할 때 까지 누군가 데이터를 중간에 변경하면, 변경 되었다는 사실을 알아야 합니다.
그래야 A의 커밋을 포기할지(최초 커밋한 B의 커밋만 인정), 아니면 사용자 UI 같은 것에 경고를 띄워주어서 누군가 1000원의 상품을 2000원으로 변경했으니 가격을 다시 수정해주세요 라고 표기할지(충돌하는 갱신 내용 병합하기) 선택할 수 있습니다.
JPA는 이 문제를 해결하기 위해 낙관적 락(@Version)이라는 메커니즘을 제공합니다.
쉽게 이야기해서 엔티티와 테이블에 버전 필드를 하나 만들어서 @Version 어노테이션으로 매핑하면 됩니다.
JPA는 엔티티가 갱신될 때 마다 자동으로 버전을 체크하면서 동시에 버전을 증가하기 때문에 충돌을 인식할 수 있습니다. 만약 충돌이 발생하면 JPA는 낙관적 락 예외를 터트립니다.(OptimisticLockException)
추가로 벌크 연산을 수행할 때는 수동으로 이 버전 필드의 값을 직접 하나 증가시켜주면 됩니다.
제가 최대한 쉽게 설명해드리려고 노력은 했는데, 락 문제는 단순하지 않고, 상황에 따라 적절한 해결 방안이 다릅니다. 그리고 잘못 적용하면 장애로 이어질 수 있습니다. 더 깊고 자세한 내용은 자바 ORM 표준 JPA 프로그래밍 책 16장 트랜잭션과 락 부분을 한번 보시길 권장드립니다.
감사합니다^^