Redis를 활용한 Lock 관리하기
1. Lock 이란?
Lock 은 동시성 문제를 해결하기 위한 방법 중 하나입니다.
동시성 문제란, 여러 개의 프로세스나 스레드가 동일한 자원에 동시에 접근하려고 할 때 발생하는 문제를 의미합니다.
이러한 문제는 데이터의 일관성과 무결성을 해치는 원인이 되므로, 동시성 문제를 해결하기 위한 다양한 방법들이 제안되고 있습니다.
이번에는 Redis 를 활용한 Lock 관리 방법에 대해 알아보겠습니다.
단일 인스턴스를 활용한 Lock 관리
Redis는 단일 스레드로 동작하는 인메모리 데이터베이스이므로, 단일 인스턴스에서도 Lock을 관리할 수 있습니다.
가장 쉽게 생각할 수 있는 구조는 다음과 같습니다.
if(redisTemplate.opsForValue().setIfAbsent("lock", "value")) {
try {
// 잠금을 획득한 경우, 작업을 수행합니다.
} finally {
// 작업을 완료하면 잠금을 해제합니다.
redisTemplate.delete("lock")
}
}
위와 같은 코드의 형태를 가지는 Lock 은, Redis 에서는 잘못된 형태의 Lock입니다.
Lock을 획득한 클라이언트가 비정상 종료되거나, Lock 을 해제하지 않는 경우 다른 클라이언트가 Lock 을 획득할 수 없는 상황이 영원히 지속될 수 있습니다.
이러한 문제를 해결하기 위해서는, Lock 을 획득할 때마다 유효 시간을 설정하고, 유효 시간이 지나면 Lock 을 해제해야 합니다.
if(redisTemplate.opsForValue().setIfAbsent("lock", "value", Duration.ofSeconds(10))) {
try {
// 잠금을 획득한 경우, 작업을 수행합니다.
} finally {
// 작업을 완료하면 잠금을 해제합니다.
redisTemplate.delete("lock")
}
}
위와 같이 유효 시간을 설정하고, 작업을 완료한 후에는 잠금을 해제하는 방식으로 Lock을 관리하면, Redis 를 활용한 Lock 을 안전하게 사용할 수 있습니다.
클라이언트가 장애가 나도 잠금이 해제되므로, 다른 클라이언트가 잠금을 획득할 수 있습니다.
하지만, 이러한 방식은 단일 인스턴스에서만 동작하며, 여러 개의 인스턴스를 활용하는 분산 환경에서는 동작하지 않습니다.
단일 인스턴스에 장애가 발생했을 경우에, 모든 서비스가 장애가 발생하게 됩니다.
이러한 문제를 해결하기 위해서는, 여러 개의 인스턴스에서 Lock 을 관리할 수 있도록 설계해야 합니다.
이를 해결하기 위해 replication을 사용하면 어떻게 될까요?
Replication을 활용한 Lock 관리
Redis는 replication을 통해 데이터를 복제할 수 있습니다.
이를 활용하면, 단일 인스턴스에서 Lock을 관리하는 방식을 여러 개의 인스턴스에서도 동일하게 사용할 수 있습니다.
if(redisTemplate.opsForValue().setIfAbsent("lock", "value", Duration.ofSeconds(10))) {
try {
// 잠금을 획득한 경우, 작업을 수행합니다.
} finally {
// 작업을 완료하면 잠금을 해제합니다.
redisTemplate.delete("lock")
}
}
이 과정이면 모든 문제가 해결될까요?
이 과정에서도 문제가 발생할 수 있습니다.
Redis는 replication을 통해 데이터를 복제할 때, 비동기 방식으로 데이터를 복제합니다.
이는 데이터의 일관성을 보장하지 않으므로, replication 을 통해 복제된 데이터는 일정 시간 동안 일관성이 보장되지 않습니다.
이러한 문제를 해결하기 위해서는, replication 을 통해 복제된 데이터가 일관성을 보장할 때까지 기다려야 합니다.
하지만, 기다리는 방식이 가능할까요? 서버 입장에서 현재 데이터가 있는지 없는지를 알 수 없습니다.
이러한 문제를 해결하기 위해서는, replication 을 통해 복제된 데이터가 일관성을 보장할 때까지 기다리는 방식이 아닌, 일관성을 보장하는 방식으로 Lock을 관리해야 합니다.
이를 위해선, Redis의 replication을 활용하는 것이 아닌, Redis 를 여러개 두는 방식으로 활용해야 합니다.
Redis 여러개를 활용한 Lock 관리
이 기능을 활용해서 Lock을 관리하면, 여러 개의 인스턴스에서도 Lock 을 안전하게 관리할 수 있습니다.
이를 위해 redis 공식문서에서 추천하는 방법이 Redlock입니다.
Redlock 이란?
Redlock 은 여러 개의 Redis 인스턴스를 활용해서 Lock을 안전하게 관리할 수 있는 알고리즘입니다.
Redlock 은 다음과 같은 과정으로 Lock 을 관리합니다.
총 5개의 Redis 인스턴스가 있다고 가정합니다.
클라이언트는 모든 Redis 인스턴스에 Lock 을 획득하려고 시도합니다.
그중에서 3개 이상의 Redis 인스턴스에서 Lock을 획득하면, Lock 을 획득한 것으로 간주합니다.
이때, Lock 을 획득한 Redis 인스턴스의 시간을 기준으로 Lock의 유효 시간을 설정합니다.
가장 늦게 응답이 온 Redis에 시간부터 유효 시간을 설정합니다. 마무리는 가장 빠르게 응답이 온 Redis 인스턴스를 기준으로 시작합니다.
예를 들어 10초짜리 Lock을 획득하려고 하는 상황을 가정해 봅시다.
- 0초에 Lock 5개 요청을 보냅니다 (A, B, C, D, E).
- 1초에 Lock 요청에 대한 응답이 옵니다 (A, B).
- 2초에 Lock 요청에 대한 응답이 옵니다 (C, D).
- 3초에 Lock 요청에 대한 응답이 옵니다 (E).
이때, 이때 2초부터 Lock 이 취득되었다고 가정하고, 11초까지 유효하다고 가정합니다.
2초에 이미 과반수를 넘었기에, 그 시간이 기준이 되고 가장 빠른 응답이 온 1초를 기준으로 10초만큼 시간을 잡아서 11초로 Lock을 관리합니다.
이렇게 하면, Lock 을 관리하는 Redis 인스턴스가 다운되는 상황이 발생해도, Lock 을 관리할 수 있습니다.
이 과정에서 문제점은 없을까요?
이 과정에서도 문제점이 발생할 수 있습니다.
하나의 인스턴스를 기준으로 Lock 을 봤을 때 생길 수 있는 문제점
- 클라이언트가 Lock 을 요청합니다.
- Redis에 Lock 이 설정됩니다.
- Redis 에 Lock 이 설정되고, 영속성이 생기기 전에 down 됩니다.
- Redis 가 재부팅됩니다.
- 다른 클라이언트가 Lock을 요청합니다. (이때, Lock 이 설정되어 있지 않습니다.)
이 과정에서 문제가 생길 수 있습니다.
이 문제를 해결하기 위해서는 다운되었을 때, 10초 이상 복구를 기다려야 합니다.
Redis 가 10초 동안은 다시 재부팅이 되지 않는다면, Lock 이 하나만 유지되는 것을 보장할 수 있겠죠
당연하지만 성능상 문제가 발생하긴 합니다. 따라서 이 부분도 결국 얼마나 장애를 감내할 것인지, 얼마나 빠르게 Lock 을 관리할 것인지에 대한 Trade Off 가 발생합니다.
긴 글을 읽어주셔서 감사합니다.
'인프라' 카테고리의 다른 글
private 서브넷에 인스턴스를 외부와 연결할 때, public ip? private ip? (4) | 2023.07.23 |
---|---|
github 에 Self Hosted Runner 로 EC2 에 CD 구축하기 (2) | 2023.05.25 |