dev notes

Redis와 Kafka를 IPC로 설명해도 될까

2024-01-1513 min read
공유

OS 수업에서 IPC(Inter-Process Communication)를 배울 때는 공유 메모리, 파이프와 메시지 큐 같은 운영체제 기능을 중심으로 봤습니다. 각 프로세스가 분리된 주소 공간을 사용하므로, 서로 데이터를 주고받으려면 운영체제가 제공하는 통신 경로가 필요하다는 내용이었습니다.

서버 개발에서 HTTP, Redis와 Kafka를 사용하기 시작하면서 이 개념을 다시 떠올렸습니다. 처음에는 Redis를 네트워크 너머의 공유 메모리로, Kafka를 분산 환경의 메시지 큐로 생각하면 이해하기 쉬웠습니다. 서로 다른 실행 주체가 상태를 공유하거나 메시지를 전달한다는 큰 방향은 비슷해 보였습니다.

하지만 이 비유만으로 설계를 설명하기에는 빠지는 부분이 많았습니다. Redis를 사용하는 서비스는 같은 메모리 영역을 읽는 것이 아니고, Kafka의 레코드는 Consumer가 읽었다고 바로 사라지지 않습니다. 프로세스 간 통신에서 출발한 질문은 도움이 됐지만, 서비스가 네트워크로 분리된 뒤에는 상태의 소유자와 실패 조건을 다시 봐야 했습니다.

프로세스는 서로의 메모리를 직접 읽을 수 없다#

프로세스는 각자의 가상 주소 공간에서 실행됩니다. 한 프로세스가 만든 데이터를 다른 프로세스가 사용하려면 같은 메모리 객체를 각 주소 공간에 매핑하거나, 운영체제가 관리하는 통신 경로로 데이터를 전달해야 합니다.

Loading diagram...

POSIX 공유 메모리는 shm_open()으로 객체를 열고 mmap()으로 각 프로세스의 주소 공간에 매핑합니다. 데이터가 놓이는 영역은 공유되지만, 어느 프로세스가 언제 읽고 쓸 수 있는지는 별도의 문제입니다. POSIX shared memory 문서에서도 여러 프로세스가 같은 영역에 접근할 때 세마포어와 같은 동기화 수단이 필요하다고 설명합니다.

메시지 전달은 데이터를 직접 공유하는 대신 파이프, 소켓이나 메시지 큐 같은 경로를 통해 주고받습니다. 공유 메모리를 동시에 수정하는 경쟁은 피할 수 있지만, 메시지를 누가 받을지와 버퍼가 가득 찼을 때 어떻게 할지, 수신 프로세스가 종료되면 무엇을 남길지는 통신 방식에 맞게 정해야 합니다. 메모리를 공유하지 않는다고 동시성과 실패 처리가 없어지는 것은 아니었습니다.

프로세스 경계가 네트워크로 넓어지면 실패 조건이 늘어난다#

소켓은 같은 서버의 프로세스뿐 아니라 네트워크로 연결된 다른 서버와도 통신할 수 있습니다. HTTP와 gRPC는 이 연결 위에서 요청과 응답의 형식, 오류와 호출 규칙을 정의합니다. 호출하는 쪽에서 보면 메서드 하나를 실행하는 것처럼 보여도 요청 전송, 상대 서비스의 수락, 실제 처리 완료와 응답 수신은 서로 다른 시점입니다.

이 차이는 timeout과 retry를 다룰 때 드러납니다. 응답을 받지 못했다는 사실만으로 상대 서비스가 요청을 처리하지 않았다고 판단할 수는 없습니다. 같은 요청을 다시 보내도 되는지는 처리 결과의 중복을 막을 수 있는지와 함께 봐야 합니다. 한 프로세스 안에서 느린 작업을 다른 스레드 풀로 분리하는 경우와 달리, 서비스 사이의 호출은 네트워크 단절과 상대 프로세스의 독립적인 실패까지 고려해야 합니다.

따라서 HTTP나 gRPC를 소켓 IPC의 연장선에서 이해할 수는 있지만, 소켓을 사용한다는 사실만으로 서비스 간 호출의 완료와 복구 방식까지 설명되지는 않습니다.

Redis는 공유 메모리가 아니라 공유 상태를 제공한다#

여러 서비스가 Redis의 같은 key를 읽고 쓸 수 있다는 점은 공유 메모리와 닮았습니다. 차이는 접근 방식에 있습니다. 각 서비스는 Redis의 메모리를 자신의 주소 공간에 매핑하지 않고, 네트워크를 통해 Redis Server에 명령을 보냅니다. 값의 저장과 변경은 Redis가 담당하고 서비스는 명령의 결과를 전달받습니다.

Loading diagram...

이 구조에서 공유되는 것은 메모리 영역이 아니라 Redis가 관리하는 상태입니다. Redis 연결이 끊기거나 명령의 응답을 받지 못하는 경우도 있고, 여러 명령에 걸친 애플리케이션 규칙은 각 명령이 원자적이라는 사실만으로 보장되지 않습니다.

분산 락도 key 하나를 먼저 만든다는 설명만으로는 부족합니다. 락을 얻을 때는 key가 없을 때만 값을 저장해야 하고, 작업이 끝나지 못한 경우를 위해 만료 시간이 필요합니다. 해제할 때는 현재 저장된 값이 자신이 기록한 소유자 값과 같은지도 확인해야 합니다. Redis의 분산 락 문서가 고유한 값을 저장하고 소유자가 일치할 때만 삭제하도록 설명하는 이유입니다.

DB에 저장된 데이터를 변경하는 규칙이라면 DB 트랜잭션과 락 안에서 함께 처리하는 방법도 있습니다. MySQL S/X 락을 사용한 동시성 제어처럼 보호할 데이터와 락이 같은 저장소에 있을 때와, Redis에 실행 권한을 따로 둘 때는 실패 경계가 다릅니다. 어느 방식이 더 빠른지를 먼저 비교하기보다 어떤 상태를 같은 경계에서 보호해야 하는지부터 확인해야 합니다.

Kafka는 읽은 메시지를 바로 삭제하지 않는다#

Kafka도 Producer와 Consumer 사이를 분리한다는 점에서는 메시지 전달 모델과 닮았습니다. Producer는 Consumer를 직접 호출하지 않고 topic에 이벤트를 기록하며, Consumer는 자신의 처리 속도에 맞춰 이벤트를 읽습니다. 보내는 쪽이 받는 쪽의 완료를 기다리지 않아도 된다는 점을 이해하기에는 메시지 큐라는 비유가 유용했습니다.

Kafka의 저장과 소비 방식은 운영체제의 메시지 큐와 다릅니다. 이벤트는 topic의 partition에 순서대로 추가되고, Consumer가 읽은 뒤에도 보존 정책에 따라 남아 있습니다. Consumer는 메시지를 제거하는 대신 partition별 offset으로 읽은 위치를 관리합니다. Kafka 공식 문서도 이벤트가 소비와 동시에 삭제되지 않으며, 순서는 하나의 topic-partition 안에서 보장된다고 설명합니다.

Loading diagram...

같은 이벤트를 여러 Consumer Group이 각자의 offset으로 읽을 수 있고, 하나의 Group 안에서는 partition이 Consumer에게 나뉘어 할당됩니다. Kafka에서는 partition key, 순서 보장 범위, offset commit과 중복 처리 방식까지 함께 설계해야 합니다. 같은 group.id를 공유했을 때 배포 영향 범위까지 연결되는 이유는 Consumer Group의 장애 범위를 다룬 글에서 확인할 수 있습니다.

통신 수단을 고르기 전에 확인할 조건#

IPC의 공유 메모리와 메시지 전달은 통신 구조를 처음 나누어 보는 데는 여전히 유용했습니다. 다만 실제 서비스에서는 어떤 분류에 들어가는지를 맞히는 것보다, 통신 전후에 남는 상태와 실패했을 때의 복구 방법을 확인하는 편이 중요했습니다.

확인할 조건HTTP / gRPCRedisKafka
호출하는 쪽이 기다리는 결과요청에 대한 응답명령 실행 결과이벤트 기록 결과이며, Consumer의 처리는 별도
상태를 보관하는 주체호출받은 서비스Redis ServerKafka broker가 이벤트와 Consumer Group의 offset을 보관
실패 후 확인할 것처리 여부, timeout, retry 중복명령 반영 여부, 만료와 소유권offset commit, 재처리와 중복
순서를 판단하는 범위한 요청과 서비스의 처리 규칙명령 또는 원자적으로 묶은 실행 단위하나의 topic-partition

이 기준으로 보면 Redis와 Kafka는 IPC의 새로운 이름이 아닙니다. Redis는 네트워크를 통해 공유 상태에 접근하는 저장소이고, Kafka는 여러 Consumer가 각자의 위치에서 읽을 수 있는 분산 이벤트 로그입니다. 두 도구 모두 프로세스 사이의 협업을 돕지만, 보장하는 규칙과 실패했을 때 남는 상태는 다릅니다.

OS에서 배운 IPC가 쓸모없었던 것은 아니었습니다. 공유된 상태를 직접 다룰지, 메시지를 사이에 둘지 생각하게 해준 출발점이었습니다. 이후에는 도구를 IPC 모델에 대입하는 데서 멈추지 않고, 요청의 수락과 처리 완료를 어떻게 구분할지, 상태와 실행 권한은 누가 가지고 있는지, 재시도하면 무엇이 중복될 수 있는지까지 확인해야 할 것 같습니다.

Connected Notes