외부활동/immersion

Redis와 분산 락(Distributed Lock)

softmoca__ 2024. 3. 18. 16:10
목차

레디스(Remote Dictionary Server)

메모리에 저장하는 key-value기반의 NoSQL DBMS

 

- 캐싱 뿐 아니라 임시작업큐, 실시간 채팅,메시지 브로커

- 메모리에 저장되어 빠른 속도로 접근가능, get/post 경우 10만 TPS이상 가능

- 싱글 스레드로 한번에 하나의 명령만 처리, race condition(두개 이상의 프로세스가 동시에 하나의 리소스에 대해 접근할 때 서로 경쟁)이 거의 발생하지 않는다

- 다양한 자료구조 제공, 개발의 편의성 증가

- 메모리에 저장된 데이터를 디스크에 영속화Persistence( RDB,AOF 두가지 옵션 존재) → 서버에 치명적인 문제가 발생하더라도 복구 가능

  • RDB: 특정한 간격으로 현재 레디스의 메모리에 존재하는 데이터의 스냅샷을 남기는 방식
    • 장점 : 압축하여 저장하기 때문에 AOF 보다 크기가 작고 로딩/복구 속도가 빠르다
    • 단점 : 백업중 서버가 다운될경우 최신 데이터 유실 가능성
  • AOF: 입력,수정,삭제 명령이 실행될 때마다 LOG파일에 기록
    • 장점 : 저장 속도 빠름, 실시간 데이터 백업 가능, 데이터 손실 거의 없다
    • 단점 : 명령실행 기록을 모두 기록하기 때문에 파일 크기가 크며, 복원소요 시간이 길다.

레디스 사용시 주의할 점

 

 

 

1. 데이터 타임에 따른 적절한 자료구조 사용 예) 최근 검색 목록 표시

유저 id가 123인 사용자의 최근 검색 목록을 조회 하려한다.

위와 같은 user id로 5개의 제한을 걸어 검색 날짜로 정렬하는 쿼리가 필요하다.

뿐만 아니라 중복을제거 해야하고 사용자별로 데이터 개수를 확인한 다음 오래된 검색어를 삭제해주는 작업 또한 필요하다.

=> 이런 경우 중복을 허용하지 않고 정렬되어 저장되는 Redis의 sorted Set 자료구조를 사용하면 편리하게 위 로직을 구현할 수 있다.

 Redis의 sorted Set 은 가중치를 기준으로 오름차순으로 정렬되어 가중치로 시간값을 사용하면 가장 나중에 들어온 아이템이 맨 마지막 인덱스에 저장이 된다.

 

2.  O(N) 명령어 주의 (레디스는 싱글 스레드 !)

레디스는 싱글스레드이기 때문에 KEYS,FLUSHALL,FLUSHDB, Delete Collections, Gat All Collections과 같은 O(N) 명령어의 경우에는 해당 명령이 처리될 때까지 다음 명령어들이 대기 상태로 전환되는 문제가 발생한다.

그래서 Redis를 빠른 성능을 위해 사용하려 했지만 오히려 더 느린 성능으로 될수있어 사용에 주의해야한다.

 

 

3. 메모리 관리

Redis의 경우 인메모리 데이터 스토어이므로 메모리 관리가 필수적이다.

메모리 단편화로 인해 Redis는 7KB를 사용 중이라고 파악하지만 실제로 차지하는 공간은 10KB라고 인지하게 되는 문제가 발생한다.

따라서 실제 물리 메모리 사용량을 나타내는 RSS 값을 모니터링 해서 메모리 관리를 해줘야한다.

 

 

메모리 단편화 : 메모리가 작은 공간으로 나눠어져 관리되어서 사용 가능한 공간이 충분한데도 불구하고 해당 메모리를 할당하지 못하는 상태.

 

 

 

4. 레디스의 목적성

레디스를 캐시용으로 사용할지 저장소용으로 사용할지에 대한 목적을 분명히 할 필요가 있다.

그 이유로는 파일을 DB에 영속하는 기능의 Persistence 기능(RDB,AOF)이 장애 발생 가능성이 높은 것으로 알려져 있다.

그래서 Redis에 저장되었던 데이터가 없어져도 문제가 없는지 일부 값이 유실되어도 치명적인지 등을 판단해서 캐시용으로만 사용 한다면 persistence 기능을 사용하지 않느 것을 권장 하고 있다.

 

 

 

 

어떤 기준으로 도입을 해야 하나 ?

⇒ get에 대한 비중이높으면 캐싱 시스템이 큰 효율을 가진다.

레디스가 터지면 모든 서버가 다 터진다. 레디스는 안정성이 1순위이다.

그래서 안정성 측면에서 AWS의 ElasticCache Redis를 사용하는것도 좋은 방법이다.

‘내가 aws 클라우드 아키텍처보다 Redis를 EC2 내부에 설치하거나 외부에 설치해서 더 잘할 자신이 있다면 직접 설치를 하고  아니면 AWS의 ElasticCache Redis를 사용한다.

가격과 서비스 안정성을 비교해보아 레디스를 도입하지 않는게 더 좋은 옵션일 수 있다.

 

 

LOCK

트랜잭션 락과 비슷한 개념으로 어떤 데이터 수정, 접근 등에 Lock을 필요로 하게 설정하는 것이다.

데이터를 수정하려면 우선 Lock을 획득하고 수정 후에 Lock을 반납한다.

만약 Lock을 획득할 수 없는 상황에서는 Lock을 획득할 수 있을 때까지 대기하거나 수정을 취소할 수 있다.

 

동시성 이슈

하나의 스레드가 데이터를 수정 중인 상황에서 다른 스레드에서 수정 전의 데이터를 조회하여 수정함으로써 데이터의 정합성(consistency)이 깨지는 문제를 말한다.

분산 락 (Distributed Lock)

하나의 공유 자원에 대한 경쟁 상황에서 데이터에 접근할 때, 데이터의 결함이 발생하지 않도록 원자성을 보장하는 기법

NestJS에서는 @nestjs/redis(Lettuce 기반),Redisson(다양한 락, 스핀락,Pub/Sub) 라이브러리를 사용해서 구현할 수 있다.

 

Lettuce

Lettuce는 공식적으로 분산락 기능을 제공하지 않는다. 따라서 직접 구현해서 사용해야 한다.

Lettuce의 락 획득 방식은 락을 획득하지 못한 경우 락을 획득하기 위해 Redis에 계속해서 요청을  보내는 스핀락(spin lock)으로 구성되어 있다. 이 스핀 락 방식은 계속해서 요청을 보내는 방식으로 인해 redis에 부하가 생길 수 있다는 단점이 있다.

Redisson

Redisson은 락 획득 시 스핀 락 방식이 아닌 pub/sub 방식을 이용한다.

pub/sub 방식은 락이 해제될 때마다 subscribe중인 클라이언트에게 "이제 락 획득을 시도해도 된다."라는 알림을 보내기 때문에, 클라이언트에서 락 획득을 실패했을 때, redis에 지속적으로 락 획득 요청을 보내는 과정이 사라지고, 이에 따라 부하가 발생하지 않게 된다.

또한 Redisson은 RLock이라는 락을 위한 인터페이스를 제공한다. 이 인터페이스를 이용하여 비교적 손쉽게 락을 사용할 수 있다

게 lock 메서드와 tryLock 메서드로 구분된다.

lock 메서드는 락을 얻을 때까지 대기하며, tryLock(락상태를 바로 알수 있어 데이터 일관성과 성능향상) 메서드는 락 획득에 실패하면 추가 작업을 진행한다.

forceUnlock은 강제로 락을 해제해야 하는 경우에 사용하며, 동시성 문제 가능성이 있어서 사용에 주의가 필요하다.

 

 

 

 

 

+ rabbitMQ,redis queue,kafka란 ?

모두 메세지 큐잉 시스템의 종류들 중 하나이며 각각의 장단점과 적용 분야가 다르다.

크게 메세지브로커이벤트 브로커 두가지로 나뉜다.

메세지 브로커는 이벤트 브로커로 역할을 할수 없지만 이벤트 브로커는 메세지 브로커 역할을 할수 있다.

메세지 브로커에는 rabbitMQ,Redis queue가 있고 이벤트 브로커에는 Kafka나 AWS의 Kinesis가 있다.

 

 

메세지 브로커 : 대규모 메세지 기반 미들웨어 아키텍처에서 사용

- 메세지 브로커에 있는 큐에 데이터를 보내고 받는 프로듀서와 컨슈머를 통해 메시지를 통신하고 네트워크를 맺는 용도로 사용한다.

- 메세지를 받아 적절히 처리하고 나면 즉시 또는 짧은 시간 내에 삭제되는 구조

 

이벤트 브로커

- 이벤트/메세지라고 불리는 레코드 장부를 딱 하나만 보관하고 인덱스를 통해 개별 엑세스를 관리한다.

- 업무상 필요한 시간동안 이벤트를 보관할 수 있다.

-서비스에서 나오는 이벤트를 마치 DB에 저장하듯이 이벤트 브로커의 큐에 저장함으로써 ‘ 딱 한번 일어난 이벤트 데이터를 브로커에 저장함으로써 단일 진실 공급원으로 사용할 수 있다.

- 장애가 발생했을 때 장애가 일어난 지점부터 재처리를 할 수 있다. 

- 많은 양의 실시간 스트림 데이터를 효과적으로 처리할 수 있다.

-이벤트 브로커로 클러스터를 구축하면 이벤트 기반 마이크로 서비스 아키텍처로 발전하는데 중요한 역할을 할 뿐 아니라 메세지 브로커 로서도 사용할 수 있다.

 

 

 

 

 

 

올리브영 신규 재고 시스템 예시)

MSK(Amazon Managed Streaming for Apache Kafka)

- 실시간 데이터 파이프 라인

- 발생하는 이벤트는 Mysql DB를 거치지 않고, Redis와 호환되는 인메모리 DB에 적재가 가능하다.

Broker(MSK)와 Consumer(NestJS)는 AWS에서 신규 구축되기 때문에 문제가 없다.

하지만, Producer(중계서버)의 경우 한정된 레거시 시스템의 리소스에 추가해야 한다는 부담이 있다.

따라서 최소한의 I/O와 네트워크 리소스를 사용하며, 안정적인 데이터 전송이 보장된 Producer 개발이 필요하다.

그래서 비동기 호출 기반의 Batch 전송 방식으로 구현을 한다.

 

시스템과 데이터에 맞는 설정을 하려면 다양한 테스트가 필요하다.

처리량을 높이려면 batch.size와 linger.ms 값을 크게 설정하고, 지연 없는 전송이 필요하면 작게 설정한다.

필수 옵션은 다음과 같다.

  • buffer.memory : Producer 버퍼 메모리 옵션
  • batch.size : Record들을 묶어서 Batch 전송을 하는 크기 옵션
  • linger.ms : 버퍼 메모리에 대기하는 Message들의 최대 대기 시간

 

 

CircuitBreaker

Amazon MemoryDB는 클러스터 구조로 안정성이 보장된 시스템이지만, 장애 상황에 대한 준비는 필요.

MemoryDB에 일정 기준 오류가 발생할 경우, 기존 DB로 전환하는 CircuitBreaker를 구현.

MemoryDB가 정상화되면 자동으로 CircuitBreaker는 닫히게 된다.

 

자세한 참조는 아래 포스트 참조

https://oliveyoung.tech/blog/2023-08-31/circuitbreaker-inventory-squad/

 

 

 

 

 

 

 

 

 

 

https://oliveyoung.tech/blog/2023-10-04/inventory-project/

https://www.youtube.com/watch?v=tVZ15cCRAyE