LODY/정리

리눅스 시스템 프로그래밍 / 메시지 큐

메시지 큐

🟣 파이프로 부족했던 세 가지

앞 장에서 파이프는 부모와 자식을 잇는 한 방향 바이트 흐름이었다. 실무의 통신 요구를 여기에 대 보면 세 군데가 비었다.

첫째, 파이프는 바이트 흐름이라 경계가 없다. 보낸 쪽이 100바이트씩 세 번 써도 받는 쪽은 그게 300바이트 한 덩어리인지 100바이트 세 개인지 알 수 없다. 메시지 단위로 주고받으려면 프로그래머가 길이 헤더를 직접 붙여 프레이밍을 해야 한다. 둘째, 익명 파이프는 혈연관계(fork)가 있어야만 이어진다. 셋째, 파이프에는 순서만 있고 우선순위가 없다. 급한 메시지를 먼저 꺼낼 방법이 없다.

메시지 큐는 이 셋을 커널 차원에서 해결한다. 무관한 프로세스끼리, 메시지 경계를 유지한 채, 우선순위를 얹어 주고받게 해 준다.


🟣 커널이 들고 있는 메시지들의 줄

메시지 큐는 커널 안에 있는 메시지들의 대기열이다. 보내는 쪽은 큐에 메시지를 넣고(send), 받는 쪽은 큐에서 꺼낸다(receive). 파이프와 결정적으로 다른 점은 넣은 단위 그대로 꺼내진다는 것이다. 커널이 각 메시지의 경계를 기억하기 때문에 받는 쪽이 프레이밍을 직접 할 필요가 없다.

생산자send()커널 메시지 큐msg3 | msg2 | msg1소비자receive()메시지 단위로 적재경계 유지한 채 꺼냄

또 하나의 차이는 수명이다. 파이프는 그것을 연 프로세스들이 모두 사라지면 함께 사라진다. 메시지 큐는 커널이 들고 있어서, 만든 프로세스가 죽어도 큐와 그 안의 메시지가 남는다. 나중에 뜬 프로세스가 이어서 꺼내 갈 수 있다. 이 지속성 덕분에 생산자와 소비자가 동시에 살아 있지 않아도 통신이 성립한다.


🟣 System V와 POSIX: 같은 개념의 두 세대

리눅스에는 메시지 큐 API가 두 벌 있다. 오래된 System V와 나중에 정리된 POSIX다. 개념은 같지만 손에 잡히는 방식이 다르다.

System V 큐는 큐를 정수 키와 식별자로 다룬다. ftok으로 키를 만들고 msgget으로 큐를 얻는 식이라, 파일 시스템과 따로 노는 별도의 이름 공간을 쓴다. 존재하는 큐를 확인하려면 ipcs 같은 전용 명령을 써야 한다. 역사적으로 먼저 자리 잡아서 예제가 많지만, 인터페이스가 파일과 동떨어져 있어 손에 익히기 번거롭다.

POSIX 큐는 큐를 /myqueue 같은 이름으로 다룬다. mq_open이 파일을 여는 감각과 비슷하고, 반환값이 파일 디스크립터라서 select·poll 같은 IO 멀티플렉싱과 자연스럽게 엮인다. 새로 짠다면 대체로 POSIX 쪽이 더 일관된다. 디스크립터로 통일된 인터페이스라는 유닉스의 방향과도 맞다.

둘의 표면적 차이 아래에는 같은 질문이 있다. 커널 자원에 어떻게 이름을 붙이고 찾을 것인가. System V의 정수 키와 POSIX의 경로 이름은 그 답이 세대에 따라 어떻게 정리됐는지를 보여 준다. 이 이름 붙이기 문제는 다음 장의 공유 메모리에서 똑같이 반복된다.


🟣 라이브러리로 올라간 큐: ZeroMQ

커널 메시지 큐는 한 대의 기계 안에서만 통한다. 프로세스가 다른 서버에 있으면 못 쓴다. 공부하다가 이 한계에서 자연스럽게 만난 것이 ZeroMQ(zmq)였다.

zmq는 커널 기능이 아니라 소켓 위에 얹은 라이브러리다. 하지만 쓰는 감각은 메시지 큐와 같다. 메시지 단위로 보내고 받되, 상대가 같은 기계든 네트워크 너머든 같은 코드로 다룬다. 브로커를 따로 두지 않고 라이브러리가 큐잉, 재연결, 다대다 연결 패턴을 처리해 준다.

여기서 배운 건 문법이 아니라 추상화의 층이 어떻게 쌓이는가였다. 커널 메시지 큐가 "한 기계 안에서 메시지 경계와 지속성"이라는 문제를 풀었다면, zmq는 같은 프로그래밍 모델을 네트워크로 확장했다. 아래층이 무엇을 보장하는지 알고 나니 위층 라이브러리가 무엇을 대신해 주는지가 선명해졌다. 도구를 바꿔 가며 같은 개념이 어디까지 늘어나는지 확인하는 이 감각이, 나중에 새로운 메시징 스택을 볼 때도 그대로 통했다.


🟣 정리

  • 파이프의 세 한계(경계 없음, 혈연관계 필요, 우선순위 없음)를 메시지 큐가 커널 차원에서 메운다.
  • 메시지 큐는 넣은 단위 그대로 꺼내지고, 만든 프로세스가 죽어도 커널이 큐를 들고 있어 지속된다.
  • System V와 POSIX는 같은 개념의 두 세대다. 커널 자원에 이름을 붙이고 찾는 방식이 정수 키에서 경로 이름으로 정리됐다.
  • ZeroMQ는 같은 메시지 모델을 네트워크로 확장한 라이브러리다. 아래층의 보장을 알면 위층이 무엇을 대신하는지 보인다.

메시지 큐는 편하지만 대가가 있다. 메시지를 보낼 때마다 데이터가 프로세스에서 커널로, 커널에서 다시 프로세스로 두 번 복사된다. 큰 데이터를 자주 주고받으면 이 복사가 병목이 된다. 복사를 아예 없애려면 두 프로세스가 같은 메모리를 직접 보게 해야 한다. 다음 장의 공유 메모리가 그 방법이다.