작성
·
55
0
동시성 제어는
분산락
원자적 연산
방식으로 해결한다고 설명해주셨는데,
레디스 분산락의 경우, 락을 획득하려는 클라이언트가 Redis의 Set 명령어를 사용하게 되는데, 이때 NX(Not eXists) 옵션을 통해 키가 존재하지 않을 때만 값을 설정하도록 하기 때문에, 이 연산은 원자적으로 수행된다고 합니다.
결론적으로 1, 2안 모두 원자적 연산을 의미하는것이라고 생각이 들어 헷갈리는데요..!
혹시 어떤 의도로 말씀해주신건지 설명해주실 수 있나요?
그리고 Redis를 예시로 들었을때, 레디스는 싱글 스레드 동작 방식이라서, 동시성 문제가 제어 된다고 하는데,
이 동시성 문제가 실제로 발생하는 경우는 어떻게 되는건지, redis 서버가 다중 노드일 때 발생하는건지 궁금합니다.
답변 3
1
와우... 인프런이 AI를 정말 잘 쓰는군요. 답변이 조금 되신 부분이 있으실까요?
1. 일단 지금 RateLimit 강의에 대한 질문이 맞는지 여쭤보고 싶어요. 제가 정확히 어떤 부분에서 말씀 드렸던 건지 찾기 어렵네요.
2. 일반적인 내용으로 일단 질문 이해한 바대로 답변드려보겠습니다.
Q. 원자적 연산과 분산락으로 동시성 문제를 해결한다? 근데 분산락이 원자적 연산을 만드는 거 아닌가? 그럼 둘은 같은 건가?
이런 질문으로 이해했습니다.
일단 뉘앙스가 맞아보입니다. 원자적 연산은 Redis 같은 시스템이 어떤 명령어를 처리할 때 이 명령이 완료되기 전까지는 다른 명령어가 끼어들지 못하도록, 말 그대로 원자적으로 요청을 처리합니다. 분산락은 그렇게 명령어(복수, 혹은 단일)를 처리할 수 있도록 만들어주는 역할을 합니다. 원자적 연산은 도구, 분산락은 상위 레벨의 패턴이라고 생각해주세요.
Q. 그리고 Redis를 예시로 들었을때, 레디스는 싱글 스레드 동작 방식이라서, 동시성 문제가 제어 된다고 하는데, 이 동시성 문제가 실제로 발생하는 경우는 어떻게 되는건지, redis 서버가 다중 노드일 때 발생하는건지 궁금합니다.
Redis 뿐 아니라 보통 이런 분산 시스템에서 정합성이 깨지는 상황은 Read-Modify-Write 상황이거나, Replication 중에 발생합니다.
1. RMW: 가장 단순하게 예를 들어서 두 클라이언트가 어떤 정수 값을 읽은 다음 해당 값에서 1을 뺀 값을 저장하는 케이스를 생각해보면, 두 클라이언트가 동시에 읽기를 수행하면 하나의 클라이언트의 요청은 무시되는 효과가 생깁니다.
2. Replication: Primary A노드가 명령어 처리하고 복제본에게 값을 전달하기 전에 죽으면, 다른 Primary B가 선출되었을 때 해당 명령어는 유실된 상태가 됩니다.
1번은 Persistant Layer, Cache Layer에서 클라이언트와 상호작용에서 문제가 생길 수 있는 부분이고, 2번의 경우는 시스템이 사용된 트레이드오프로 인해 발생합니다. Primary Node에만 쓰기에 성공하면 성공 응답을 주는 경우는 이런 장애에 취약할 수 있습니다. 하지만 성능은 빠르겠죠? 어떤 경우는 특정 수(쿼럼)의 Replication에 전파가 성공해야만 클라이언트에게 성공 응답을 넘기도록 하는 경우도 있습니다. 이러면 성능은 느려지지만 일관성 유지가 쉬워지겠죠? CAP 이론대로요 🙂
> 첨언: 다중노드(Redis Cluster)인 경우 모든 경우에 대해 싱글 스레드가 동작 가능한 건 아닙니다. 한 노드가 처리할 수 있는 Key를 대상으로 요청해야만 싱글 스레드로 처리가 가능하고 그렇지 않다면 에러가 발생합니다. 예를 들어 A키를 다루는 A' 노드, B키를 다루는 B' 노드가 있다면 A, B에 대한 멀티 키 연산은 에러를 반환합니다.
'현업에서'라는 건 좋은 기준이 아닌 것 같아요! 제가 생각하기엔 서비스가 요구하는 성질에 따라 달라지는 것이라서 아마 만들어지는 서비스마다 다를 가능성이 높습니다. 중요한 건 저희가 분산 시스템에서 가용성을 희생하면서 일관성을 높이는 방향, 그리고 일관성을 희생하면서 가용성을 높이는 방향을 모두 이해하고 있으니까 우리가 앞으로 만날 시스템에서 어떤 가치에 조금 더 중요도를 두고 있는지에 따라 이 스펙트럼을 조정하면 됩니다. 예를 들어서 금융권 서비스에서는 커뮤니티, SNS 서비스보다 강한 일관성을 위해 퍼포먼스를 조금 희생할 가능성이 더 높겠죠?
추가 1. 단순히 '트랜잭션으로 처리'는 조금 모호한 것 같습니다. 트랜잭션을 지원하는 시스템(ex. PostgreSQL 같은 DB)마다 기본으로 지원하는 트랜잭션의 격리 수준 등에 의해 동시성 문제가 여전히 생길 가능성이 있어서요.
추가 2. 이론적으로, 일반적인 네트워크 환경의 분산 시스템에서 모든 정상 프로세스가 동의에 도달하는 것을 100퍼센트로 만드는 분산 알고리즘은 존재하진 않습니다. 이는 FLP Impossibility라는 키워드로 찾아보시면 금방 나와요 🙂
0
정리해보면, 동시성 문제가 발생하는 상황은 항상 레디스가 다중 노드인 상황에서만 동시성 문제가 발생하고, 그 문제를 해결할 수 있는 전략이
1. 분산락:
필요한 데이터가 존재하는 모든 노드에 락을 잡아 놓고, 처리하도록 한다.
2. 원자적 연산:
다중 노드이지만, 해당 요청에 대해서 하나의 노드에 대해서만 처리하도록 하여 원자적 연산을 보장한다.
로 이해해도 될까요??
0
안녕하세요, 인프런 AI 인턴입니다. 허은정님
레디스(중앙 상태 관리 서버)에서의 동시성 제어 방법에 대해 질문해 주셨군요.
레디스에서 동시성 제어가 이루어지는 두 가지 방법에 대해 설명드리겠습니다.
분산락(Distributed Lock): 레디스에서는 보통 SET
명령어와 함께 NX
(키가 존재하지 않을 경우에만 설정), PX
(타임아웃 설정) 옵션을 사용하여 분산락을 구현합니다. 이 과정에서 SET
명령어 자체가 원자적 연산으로 수행되기 때문에, 다른 클라이언트들이 동시에 같은 키에 대해 락을 설정할 수 없게 됩니다. 이로 인해 분산 환경에서 데이터의 일관성을 유지할 수 있습니다.
원자적 연산(Atomic Operation): 레디스는 기본적으로 모든 연산이 원자적으로 실행되도록 설계되었습니다. 이는 레디스가 싱글 스레드로 동작하기 때문에 가능한데요. 명령어가 실행되는 동안 다른 명령어가 끼어들 수 없는 구조죠. 이러한 특성 덕분에 각 클라이언트가 어떤 데이터 상태를 읽거나 변경할 때 그 값은 항상 일관성이 유지됩니다.
두 방법 모두 레디스의 원자적 성격을 이용하지만, 분산락은 특히 여러 노드 간의 동시성 문제(블록킹, 레이스 컨디션)를 해결하기 위한 전략이고, 원자적 연산은 레디스 자체의 싱글 스레드 동작 원리를 활용하여 일관성 있는 데이터를 제공하는 방식입니다.
레디스가 싱글 스레드로 동작하여 동시성 문제가 해결된다고 말하는 것은 부분적으로 맞는 말이지만, 이는 단일 인스턴스 환경에 한정됩니다. 여러 노드로 확장된 환경에서는, 노드 간 데이터 동기화나 락을 관리해야하는 필요성이 생기고, 이때는 분산락과 같은 관리 전략이 필요하게 됩니다.
이러한 관점에서 Redis의 싱글 스레드 설계와 분산 환경에서의 동시성 제어는 서로 다른 차원의 문제를 다루고 있다고 할 수 있습니다.
더 자세한 내용은 아래 원문을 통해 확인하실 수 있습니다:
- 레디스 동시성 제어와 원자적 연산
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
일단 지금 RateLimit 강의에 대한 질문이 맞는지 여쭤보고 싶어요. 제가 정확히 어떤 부분에서 말씀 드렸던 건지 찾기 어렵네요.
- 확인해보니 Rate Limiter 2에 해당되는 질문이였네요! 5분 40초 쯤에, 동시성 문제 해결방법에 대해 설명해주신 내용에서 생긴 궁금증입니다!
답변 감사합니다!
레디스도, 분산 시스템도 잘 알지 못해서 질문이 모호했는데, 찰떡같이 이해해주셔서 감사합니다.
추가적으로 궁금하게 있는데요!
1. 클러스터 환경(다중 노드)의 환경에서 Persistant Layer든, Cache Layer든 데이터를 저장하고 있는 Layer에서 동시성을 완벽하게 지킬 수 있는 방법은 쓰기 전용 노드(Leader)와 읽기 전용 노드(Follower)가 항상 상태가 일관되어 있어야 하는거니까,
쓰기 동작을 수행할때 읽기 동작까지 하나의 트랜잭션으로 처리해야 하는 방법밖에 없는걸까요??
실제로 현업에서는 어떤식으로 처리하는지가 궁금합니다!