Post

[운영체제] 메시지 전달의 설계 이슈와 RabbitMQ


이번 방학 때 좋은 기회로 GDSC-CAU : 푸앙이 사진관 프로젝트에 백엔드 개발자로 참여하게 되었다. 메시지 큐로 몰려드는 AI 프로필 생성 요청을 관리하는 파트를 맡게 되었다. 메시지 큐 도구들을 살펴보기 전에, 운영체제에서 메시지 전달에 대해 복습하면서 기존 메시지 큐 도구들을 살펴보면 좋을 것 같아 글을 남긴다!


프로세스들이 서로 상호작용할 때 만족되어야 할 두 가지 기본적인 요구사항은 동기화(synchronous), 통신(communication)이다. 동기화는 상호배제를 보장하기 위해 필요하고, 통신을 프로세스들 간에 정보 교환을 위해 필요하다.

✅ 동기화, 통신을 동시에 제공하는 대표적인 방법이 메시지 전달이다!

  • 메시지 전달을 위해 제공되는 가장 기본적인 프리미티브
    • send : 프로세스는 전송하려는 정보를 메시지 형태로 만들어 destination(목적 프로세스)로 보낸다.
    • receive : 프로세스는 receive를 호출하여 정보를 수신할 수 있는데, source는 메시지를 보낸 프로세스를 의미한다.

설계 이슈

  • RabbitMQ, ActiveMQ, Kafka의 특징, 장점, 단점은 아래의 메시지 전달 설계 이슈와 밀접하게 관련되어 있다! 특히 RabbitMQ가 아래의 설계 이슈 중 어떤 전략을 택했는지 알아보기 위해 정리해보자.
동기화송신 : 블록킹/비블록킹수신 : 블록킹/비블록킹도착 여부 확인
주소 지정직접 : 송신/수신/명시적/암묵적간접 : 정적/동적/소유관계 
메시지 형식내용길이 : 고정/가변 
큐잉 방식선입선출(FIFO)우선순위 


1. 동기화

두 프로세스 사이의 메시지 통신은, 이들 간에 기본적인 동기화가 이루어짐을 의미한다.

  • “기본적인 동기화”를 오해하지 말자! 메시지를 받는 쪽은 상대편 프로세스가 메시지를 보내기 전에는 받을 수 없는, 기본적인 순서 관계가 존재한다는 의미이다! (당연하게도, 메시지가 없을 때는 수신자가 받을 수 없다.)
  • 프로그래머는 send, receive가 호출된 이후에 프로세스에 어떤 일이 발생하는지 명시해야 한다.

send

프로세스가 send를 호출한 이후, 상대편이 메시지를 받을 때까지 블록될 수도 있고, 아닐 수도 있다.

receive

프로세스가 receive를 호출한 이후, 메시지가 이미 도착해 있다면 메시지를 받고 수행을 계속 하거나, 도착한 메시지가 없다면 기다리거나 메시지를 포기하고 수행을 계속한다.

즉, 설계 이슈/동기화에 나온 것과 같이 메시지 송신자와 수신자는 블록킹 또는 비블록킹일 수 있다. 각각 어떤 유형이냐에 따라 세 가지 조합이 가능하다.

블록킹 송신 / 블록킹 수신

메시지가 전달될 때까지 송신자, 수신자 모두 대기 (랑데부), 프로세스 간의 긴밀한 동기화를 제공한다. 긴밀한 동기화란 말 그대로 송신자와 수신자가 서로의 상태에 따라 밀접하게 관련되어 있다는 것이고, 다른 방식에 비해 높은 결합도를 가진다.

비블록킹 송신 / 블록킹 수신

메시지 송신자는 계속해서 수행하지만, 수신자는 요청한 메시지가 도착할 때까지 대기한다. 하나의 송신자가 다수의 수신자에게 빠르게 하나 이상의 메시지를 보낼 수 있도록 해준다.

또한, 메시지가 도착한 이후에 유용한 작업을 시작할 수 있는 프로세스(요청에 따른 서비스 or 자원을 제공하는 서버)에서 이 조합을 사용한다.

✅ 후에 메시지 큐를 설명할 때 나오겠지만, RabbitMQ가 구현하고 있는 프로토콜과 ActiveMQ의 Queue/Topic 모델 모두 하나의 Producer에 대한 다수의 Consumer에게 메시지를 전달할 수 있다.

비블록킹 송신 / 비블록킹 수신

송신자, 수신자 모두 기다릴 필요가 없다.

병행 처리 (concurrent processing)

병행 처리의 경우 비블록킹 송신이 자연스럽다.

  • 예를 들어 푸앙이 사진관의 AI 프로필 생성 요청의 경우, 요청 프로세스는 요청을 메시지 형태로 만들고 전달한 후, 다음 작업을 계속하면 된다.
  • ✅ RabbitMQ에서, 요청 프로세스인 Producer가 요청을 메시지 형태로 만들고 Broker로 전달한 후, 다음 작업(메시지 형태로 만들고 Broker 전달)을 반복한다.
  • 그러나 비블록킹 송신은 한 프로세스가 많은 메시지를 반복해서 생성해 보내기 때문에 많은 메시지의 전송을 제어할 수 있는 방법이 없고, 시스템 자원(CPU, 버퍼 등)의 낭비를 가져와 다른 프로세스의 성능이 저하된다.
  • ✅ RabbitMQ에서 Producer가 Broker에 메시지를 전달하는 것을 담당하면, 프로그래머는 응답 메시지로 메시지 수신 확인 부분을 구현해야 한다.

수신의 경우 블록킹 수신이 자연스럽다.

  • 일반적으로 메시지 consumer 수신이 필요한 프로세스의 경우 메시지가 수신된 이후 서비스를 시작하기 때문이다. 앞서 얘기한 두 프로세스 사이의 메시지 전달에서 “기본적인 동기화”의 경우에 기본적인 순서관계가 있다는 걸 기억한다면, 자연스레 받아들일 수 있다.

여기까지 동기화에 대한 내용을 읽고 나서 설계 이슈/동기화를 다시 보면, 송신/수신/도착 여부 확인에 대해 여러 도구들이 고민하고 결정했음을 알 수 있다.


2. 주소 지정

프로세스를 명시하는 방법은 크게 직접 주소 지정과 간접 주소 지정으로 구분된다.

직접 주소 지정

직접 주소 지정은 프로세스의 식별자를 지정하는 것이다. destination, source를 명확히 알 수 있는 경우 명시적으로 지정하는데, 알 수 없는 경우 암묵적으로 지정한다.

간접 주소 지정

송신자가 수신자에게 메시지를 바로 보내는 것이 아니라, 잠시 메시지를 보관하는 공유 큐(메일 박스)로 보낸다. 두 프로세스가 통신을 할 때, 하나의 프로세스는 메일 박스에 메시지를 보내고 다른 프로세스는 메일 박스에서 메시지를 가져온다. ✅ AMQP 프로토콜(RabbitMQ가 구현하고 있는 프로토콜)의 구조를 보면 이해가 될 것이다.

간접 주소 지정의 장점은 송신자와 수신자를 나눔으로써 메시지의 사용이 유연해진다는 점인데, 메시지 큐에서 등장하는 RabbitMQ, ActiveMQ 모두 이런 장점을 가지고 있다.

간접 주소 지정에서 송신자와 수신자의 관계

  • 일대일 : 두 개의 프로세스만 연결되는 개인 통신로
    • 메일박스는 해당 프로세스에게 정적이고 영구적으로 연결
  • 다대일 : 하나의 프로세스가 여러 프로세스에게 서비스를 제공하는 클라이언트/서버 모델에 적합, 이때 메일 박스는 포트라고 함
    • 생성된 포트는 특정 프로세스와 정적인 관계
  • 일대다 : 하나의 송신자, 다수의 수신자 / 메시지나 정보를 프로세스 집합에 방송하는 경우 유용
  • 다대다 : 여러 개의 서버 프로세스가 동시에 여러 클라이언트에게 서비스를 제공할 때 사용
    • 다수의 송신자인 경우 송신자와 메일박스의 관계는 동적, 동적으로 프로세스와 메일박스를 연결하고 해제할 때 사용하는 프리미티브가 connect, disconnect

지루한데 송신자와 수신자의 관계를 얘기한 이유는, ✅ 프로세스와 메일박스의 관계는 메일박스의 소유권과 연관되어 있기 때문이다!

다대일에서 포트의 경우, 수신자가 생성하므로 결국 메일박스의 소유권도 수신자가 가진다. 따라서 수신자 프로세서가 종료되면 포트도 해제된다.

일반적인 메일박스의 경우 운영체제가 메일박스 생성 서비스를 제공한다. 이때 메일박스의 소유권은 생성 프로세스에게 주어지거나 운영체제에게 주어진다. 운영체제가 소유권을 가지는 경우, 메일박스를 소멸시키기 위한 명시적인 명령어가 제공된다. ✅ 생성 프로세스가 소유권을 가지는 경우, 프로세스가 종료될 때 메일박스도 소멸된다. RabbitMQ의 단점인 메시지 큐 서버 종료 후 재가동 시 큐 내용을 모두 삭제하는 손실 위험성이, 여기에서 발생함을 짐작할 수 있다.


3. 메시지 형식

메시지 형식은 메시지 전달 시스템의 목적에 따라 다르고, 메시지 전달이 단일 컴퓨터인지 분산 시스템인지에 따라 다르다. 유연하게 메시지를 작성하는 방법은 가변 길이 메시지를 이용하는 것이다.

  • 헤더 : 메시지에 대한 정보
  • 몸체 : 메시지의 실제 내용

스프링에서 메시지 큐를 사용해보면서, 메시지 형식이 어떻게 이루어져 있는지 확인해보고 다른 글을 남기도록 하겠다!


4. 큐잉 정책

가장 간단한 큐잉 정책은 FIFO이다. 긴급 메시지 지원이 필요한 경우, 메시지 우선순위를 명시할 수 있다.

스프링에서 메시지 큐를 사용해보면서, 다양한 큐잉 정책에 대해서 확인해보고 다른 글을 남기도록 하겠다!



This post is licensed under CC BY 4.0 by the author.