어떤 lock을 사용할까 –

장난감 프로젝트를 작업하는 동안 동시성 문제를 해결해야 했습니다.

이것도 문서화가 필요해서 내용을 정리해서 블로그로 옮겼습니다.

목적

현재 저희 서비스에서는 두 개 이상의 스레드가 책에 접근하여 주문을 시도하면 데이터의 일관성이 깨집니다.
당사 서비스와 같이 다중 사용자 환경에서 2개 이상의 트랜잭션이 동시에 수행되는 경우 데이터 무결성을 손상시키지 않도록 트랜잭션의 데이터 액세스를 제어할 필요가 있습니다.
이 동시성 문제를 해결하기 위해 잠금을 사용하여 동시성 제어를 사용하려고 합니다.

제안

  • 레디송 사용

Redis Pub-Sub 기반 Message Broker 기능을 이용하여 잠금을 구현하는 방법입니다.
작동 방식은 잠금을 해제하는 쪽이 잠금을 기다리는 프로세스에게 잠금 획득을 시도해도 괜찮다는 메시지를 보내는 것입니다.
이 방법은 잠금을 획득할 수 있는지 지속적으로 확인하는 스핀 잠금 사용 Redis 서버에서.

이 방법을 직접 구현할 필요는 없으며 redisson이라는 라이브러리를 사용할 수 있습니다.
메시지 브로커 기능을 통해 잠금이 이미 구현되어 있기 때문입니다.

또한 이 라이브러리는 지정된 시간 동안 잠금이 획득되지 않으면 예외가 발생할 수 있도록 제한 시간을 구현합니다.
(비관적 락에서 타임아웃 구현이 가능하다고 합니다.
하지만 락을 유지한 상태에서 계속 리소스를 소모하기 때문에 데이터베이스를 계속 로드하는 문제가 있다고 합니다.
)

이렇게 하면 특정 서버에 문제가 있어도 처리가 가능하며 이 방법을 사용하는 것이 적절하다고 판단됩니다.

  • 비관적 잠금 사용

실제로 일관성을 보장하기 위해 데이터를 잠그는 방법입니다.
잠금이 설정되면 잠금이 해제될 때까지 다른 트랜잭션이 데이터를 건드릴 수 없습니다.

잠금으로 데이터를 제어하기 때문에 데이터 일관성은 보장할 수 있지만 데이터 자체가 잠겨 있기 때문에 성능이 저하될 수 있습니다.

또한 서로 다른 스레드가 잠긴 데이터에 접근할 때 교착 상태가 발생할 수 있으며, 잠금을 유지하고 있는 서버가 다운되면 데이터에 대한 잠금이 해제되지 않아 다른 서버가 데이터를 수정할 수 없습니다.

검문소

Redis 사용 시 별도의 설정 및 관리 비용이 발생합니다.
잠금은 라이브러리 수준에서 제공되기 때문에 사용 방법을 알아야 합니다.

대안

버전 열을 추가하고 고유한 재시도 논리를 작성해야 합니다.
저희 서비스의 경우 트랜잭션 충돌이 자주 발생하여 성능 저하가 예상되어 적합하지 않습니다.

  • Named Locking으로 사용하는 데이터를 잠그지 않고 별도의 데이터를 잠그는 방식

잠금은 분리하여 별도의 트랜잭션으로 관리해야 하며 트랜잭션이 종료되면 잠금을 해제해야 합니다.

실제로 저희 서비스에서 사용해보면 구현 방법이 복잡해서 적합하지 않다고 느꼈습니다.

-샐러드 Spring에서 레디스를 사용한다면 샐러드가 기본이기 때문에 별도의 라이브러리를 사용할 필요가 없다.
하지만 스핀락 방식이기 때문에 많은 쓰레드가 동시에 락을 얻기 위해 대기하고 있을 때 Redis에 과부하가 걸릴 수 있어 적합하지 않다고 판단했습니다.

-syncronized 해당 쓰레드를 제외한 다른 쓰레드의 접근을 차단하여 순차적 접근을 허용하는 키워드이다.
그러나 하나의 프로세스 내에서만 보장되기 때문에 우리와 같은 분산 환경에는 적합하지 않습니다.

참조

잠금 기술에 대해

https://mangkyu.30

(데이터베이스) 8. 트랜잭션, 동시성 제어, 복구

(이 이미지는 배우기 쉬운 Oracle로 데이터베이스 소개 및 실습 ppt에서 가져온 것입니다.
) 이 장에서는 트랜잭션, 동시성 제어(잠금 또는 통화 제어) 및 복구에 대해 알아봅니다.

mangkyu.tistory.com

MySQL 잠금
https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html

MySQL :: MySQL 8.0 참조 설명서 :: 12.15 잠금 기능

이 섹션에서는 사용자 수준 잠금을 조작하는 데 사용되는 함수에 대해 설명합니다.
표 12.19 잠금 기능 GET_LOCK(str,timeout) timeout 초의 제한 시간을 사용하여 문자열 str이 지정한 이름으로 잠금을 얻으려고 시도합니다.
음수 제한 시간 값은 무한대를 의미합니다.

dev.mysql.com

jpa 잠금(최대 절전 모드) https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#locking

Hibernate ORM 5.4.33 최종 사용자 가이드

기본적으로 검색은 데이터베이스에서 데이터를 가져와 애플리케이션에서 사용할 수 있도록 만드는 프로세스입니다.
애플리케이션의 폴링 동작을 최적화하는 것은 애플리케이션의 성능을 결정하는 가장 중요한 요소 중 하나입니다.
너무 많은 데이터 가져오기

docs.jboss.org

일반적으로 성의 내용. 이해하기 쉬운
https://velog.io/@tco0427/concurrency-to-control

제어 동시성

Moa Moa 서비스의 동시성 제어

velog.io

https://thalals.370

(Spring & Java) 인벤토리 시스템의 동시성 문제를 해결하는 방법

How to Solve Concurrency Problems Through the Inventory System이라는 강연을 보고 정리한 글입니다.
다중 스레드 또는 분산 환경에서 인벤토리 시스템, 변수 데이터를 사용하여 Spring 및 Java, Mysql, Redis 사용

thalals.tistory.com

레디스 락
https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html

https://velog.io/@hkyo96/Redis-using-distributed-concurrency-problem-solving

Redis Lock의 동시성 문제 수정

재배포 잠금 기본 사항

velog.io

Redis 및 분산 잠금(1/2) – Redis로 분산 잠금 및 안전하고 빠른 잠금 구현

Redis를 사용한 분산 잠금에 대해 자세히 알아보세요. 성능을 향상하고 일관성을 유지하는 방법을 알아보세요.

하이퍼커넥트.github.io