RedissonClient.getLock이 선착순 티켓팅 서비스에 사용될 수 없는 이유

shinny·2024년 6월 30일

순서 보장이 안 되는 non-fair locking

이것을 통해 선착순 구현을 해도 될까?

'안 된다.'

동시성 이슈가 발생할 때, 데이터 일관성 보장을 위해 잠금은 반드시 필요하다.
잠금에는 fair lock과 non-fair lock이 있다.
그리고 비공정 락은 공정 락보다 성능이 더 우수하고, 전반적인 처리량이 높다.
이것은 barging이라는 현상 때문이다.

예를 들어, 스레드 A가 락을 보유하고 있고, 스레드 B가 그 락을 요청했다면, B는 대기 상태로 들어가고, A가 락을 해제하면, B는 다시 실행된다. 그런데 그 사이에 스레드 C가 락을 요청하면, C는 B가 깨어나기도 전에 락을 얻을 수 있다.

즉 순서 보장이 안 되는 것이다. 말 그대로 비공정(non-fair)이다.
그래서 일부 스레드는 계속 잠금을 획득 못하는 기아 현상 또한 발생할 수 있다.
하지만 통계적 공정성 즉, 거의 모든 요청이 확률적으로는 공정하게 처리될 것이라고 가정하기 때문에 비공정 락을 사용한다.

언제 non-fair lock을 사용해도 될까?

서비스 입장에서 선착순, 순서가 중요하지 않은데 대규모 요청이 올 것이라고 예상이 될 때, 이른 요청인 스레드 A가 스레드 B보다 늦게 처리되는 것이 괜찮다면 비공정 락을 사용해도 된다.

하지만 이른 요청인 스레드 A가 스레드 B보다 먼저 처리되는 것이 반드시 보장되어야 한다면, 이 non-fair locking으로 구현되어서는 안 된다.

non-fair locking으로 구현했을 때, 다음과 같은 문제가 발생했다.

API 요청의 경우 user id = 151가 먼저 들어왔다. 하지만 그 때 redis는 잠금 상태였고, 스레드 2가 대기 중인 상태에서, 스레드 4가 먼저 락을 획득하여 요청이 먼저 처리되었다.

즉, non-fair locking의 경우 순서를 보장하지 않기 때문에, 서비스 정책 상 선착순 마감 등의 로직이 포함되어 있다면 RedissonClient를 사용하여 getLock을 하는 것은 지양해야 한다.

단일 서버 내에서도 스레드 간 순서가 보장되지 않는데, 다중 서버로 운영될 경우에도 당연히 순서 보장이 되지 않을 것이다.

  • (참고) RedissonClient.java
/*
Returns Lock instance by name.
Implements a non-fair locking so doesn't guarantees an acquire order by threads.
To increase reliability during failover, all operations wait for propagation to all Redis slaves.
*/

RLock getLock(String name);

정리

서비스 정책 상 선착순 혹은 순서 보장 등이 있다면, RedissonClient로 락을 획득하는 방법 말고 다른 방법을 사용해야 한다.

해결책

Lua 스크립트를 사용하여 Redis 스크립트를 작성한다.

Redis는 단일 스레드 모델로 실행되기 때문에, Lua 스크립트를 실행할 때는 원자성이 보장된다. 그리고 Redis 6.0 이후부터 클라이언트로부터 전송된 네트워크를 읽는 부분과 전송하는 부분은 Multi Thread 기반의 이벤트 루프로 처리가 된다. 그래서 들어오는 요청들은 이벤트 루프에서 순차적으로 처리된다.

결론

Lua 스크립트를 직접 작성하는 형태로 운영하면, 단일 서버일 경우에도, 다중 서버일 경우에도, 동시성 이슈로 인한 데이터 일관성 문제를 막을 수 있다.

다른 방법?

서버와 redis 사이에 메세지 큐를 두고 순차적으로 처리하는 방법도 고려해볼 수 있을 것 같다.

profile
꾸준히, 성실하게, 탁월하게 매일 한다

0개의 댓글