Post

병렬분산컴퓨팅 - 분산 시스템 직접 통신 패러다임

분산 시스템 직접 통신 패러다임

1. Remote Procedure Call (RPC)

1) RPC의 개요

원격 프로시저 호출(Remote Procedure Call)은 네트워크를 통한 통신 과정을 로컬 함수를 호출하는 것처럼 간단하게 처리할 수 있도록 설계된 통신 패러다임이다.

로컬 호출은 동일한 메모리 공간에서 나노초 단위로 실행되는 신뢰할수있는 작업이지만 RPC는 ms 이상의 네트워크 지연, 패킷 손실 및 딜레이나 서버 크래쉬로 인한 타임아웃과 같은 부분적인 실패 가능성이 존재하며 서로 다른 노드간에 전달하기 때문에 데이터의 직렬화와 역직렬화가 필수이다.

2) RPC 구동 방식

img.png

  1. Main Program에서 MUL 함수를 호출한다 (로컬 호출처럼 보임).
  2. MUL 함수로의 요청은 Client Stub로 이동한다. Client Stub는 파라미터를 마샬링(Marshal, 직렬화)하여 요청 메시지를 생성한다.
  3. 메시지가 네트워크를 통해 서버로 전달된다.
  4. 네트워크를 통해 서버로 전달된 메세지는 Server Stub가 받게되고, Server Stub은 메시지를 언마샬링(Unmarshal)하여 파라미터를 추출하고 실제 서버 프로시저를 호출한다.
  5. 서버에서 작업을 수행한 후 결과 값을 다시 Server Stub을 통해 클라이언트로 보낸다.
  6. Server Stub이 보낸 메세지를 Client Stub이 받아 Main Program으로 전달한다.

2) Contract와 Stub 생성

통신을 위한 Stub을 사용자가 직접 만들어야한다고 생각하면 굉장히 일이 많을 것이다. 하지만 다행스럽게도 IDL(Interface Definition Language)를 통해
이러한 Stub 코드를 쉽게 만들 수 있다.

전통적인 RPC(SUN RPC, ONC RPC)나 현대적인 RPC(gRPC, Protocol Buffers)를 통해 Stub이 하는 일을 정의하면 IDL 컴파일러(rpcgen, protoc)가 해당 IDL을 인식하여 아래의 산출물들을 반환한다.

  • Client Stub
    메인 프로그램의 호출을 받아 파라미터를 마샬링(직렬화)하고, 네트워크를 통해 요청을 보낸 뒤 응답을 수신한다.

  • Server Stub
    네트워크로부터 받은 요청을 언마샬링(역직렬화)하여 실제 서버 내의 프로시저를 호출하고, 실행 결과를 다시 반환한다.

  • Serialization Code
    서로 다른 하드웨어 아키텍처 간에도 데이터를 올바르게 해석할 수 있도록 기계 독립적인 데이터 표현 방식(XDR 또는 Protobuf)을 구현한 코드이다.

3) Parameter Passing

RPC는 머신 간에 메모리 주소(포인터)를 직접 전달할 수 없다. 따라서 포인터 대신 데이터를 복사하여 전달하며, 서로 다른 아키텍처(엔디언 방식 등) 간의 호환성을 위해 XDR이나 Protocol Buffers(protobuf) 같은 기기 독립적인 직렬화 형식을 사용한다.

여기서 기기 독립적이라는 말은 Node들의 아키텍처에 따른 Endian과 Data type에 영향을 받지 않는 형태라는 뜻으로
XDR과 protobuf 둘 다 자체적인 방식을 통해 데이터를 직렬화한다.

4) Failure Semantics

원격 호출 중 타임아웃이 발생하면 서버가 실제로 명령을 실행했는지 알 수 없는 문제가 발생한다. 이를 해결하기 위해 다음과 같은 보장 수준을 정의한다.

a. Maybe

Client가 요청을 보낸 뒤 서버에서 요청이 없어도 신경쓰지 않는 방식이다.
응답이 없어도 재시도 하지 않으면 별다른 조치를 취하지 않는다.

Logging이나 Monitoring event와 같이 하나 하나의 요청이 엄청 중요한 정도까지는 아닌 시스템에서 사용한다.
가장 신뢰성이 낮고 간단한 방식이다.

b. At Least Once

최소 한번의 도착은 보장하는 방식이다. Client가 서버에 요청 후 일정시간 동안 응답이 없다면 한번 더 요청을 보낸다.
이후 응답을 받을때까지 요청하기 때문에 최소 한번 도착 보장이다.
이때 요청 자체가 멱등성(idempotency, 몇번을 실행해도 동일한 결과인 것) 있는 요청이 아니라면 중복해서 적용될 가능성이 있다.

※ 멱등성 있는 요청

아래와 같은 요청이 있다고 해보자.

1
x = x + 1;

위와 같은 요청이 원래는 한번만 적용되어야하는데 N번 요청했다고 해보자.
원래 x 초기값이 0이라 한번 실행시 1이어야하는데 N번 요청이 들어와버리면 x의 값은 N이 되어버린다.
하지만 아래와 같은 코드를 보자.

1
x = 1;

위의 코드는 몇 번을 실행해도 x에는 1만 들어있다.
이렇게 몇번을 실행해도 동일한 결과인 것을 멱등성이 있다고 한다.

At least Once 방식의 경우에는 이러한 멱등성이 있는 요청이 아니라면 첫번째와 요청과 같이 잘못된 결과가 있기 때문에 아래의 두가지 방법 중에 하나를 선택해야한다.

  1. 요청 내용을 멱등성이 있게 구성한다.
  2. 요청의 중복을 제거한다.

1번의 경우에는 x에 1을 대입하는 형태의 멱등성 있는 요청 내용이고, 2번의 경우에는 각 요청에 ID를 부여하는 것이다.
Application에서 해당 요청에 대한 ID를 Table로 유지하여 이미 받은 요청인지 대조하여 이미 처리한 요청이라면 무시하는식으로 서버측에서 해당 요청을 받았음을 체크하게 한다.

아무래도 2번 보다는 1번이 관리의 측면에서는 좀 더 쉬운 부분이 있으나 구현은 더 어려울 수 있다.
요청에 대해서 로그를 남길때는 1번 방식을 좀 더 선호하는 부분이 있는 듯하다. 실제로 MongoDB와 같은 Sharded Replication을 지원하는 DB는 동기화를 위해서 요청을 날리는데, 이 요청은 x += 1과 같은 형태의 요청이라도 내부적으로 oplog를 남길때는 x=1과 같이 멱등성 있는 형태로 유지하여 문제가 생길때 복구를 용이하게 구성한다.

c. At Most Once

무조건 한번의 도착을 보장하는 방식이다. Exactly Once 방식이라고도 하며, 이전에 “최소 한번 방식”은 Application에서 중복 제거 혹은 멱등성 있는 요청으로 처리를 해야했다면 이 경우 Stub에서 별도의 요청에 대한 ID table을 유지하여 해당 요청을 중복을 제거하는 방식이다. 최소 한번 방식보다 Stub의 Overhead가 훨씬 크다

4) Binding / Service Discovery

바인딩은 클라이언트가 RPC 요청을 전송할 수 있도록 서버의 네트워크 주소(IP + Port)를 찾아내는 프로세스를 의미한다. 바인딩 방식은 크게 정적 방식과 동적 방식으로 나뉜다.

a. 정적 바인딩

클라이언트가 호출할 서버의 IP 주소와 PORT를 미리 알고 있는 방식이다.

  • 서버 재시작이나 설정 변경으로 인해서 주소가 바뀌면 대응하기 어렵다. (특히 AWS와 같은 클라우드 서비스면 더욱이 이런 문제가 도드라진다)
  • 시스템 규모를 키우거나 복제본을 관리하기 어렵다. (IP와 PORT를 변경하고 클라이언트에 해당 내용을 반영하기 위해 서버 재시작이 필요할 수 있음)
  • 큰 서비스의 경우 유지보수가 어려우며 특정 서버만 사용한다면 해당 서버가 단일 장애점이 될 수 있기에 가용성에 문제가 생길 수 있다.

b. 동적 바인딩 (Service Discovery)

클라이언트가 서버의 주소를 동적으로 가져오는 방식으로 Service Discovery Mechanism을 사용한다.
절차는 아래와 같다.

  1. 클라이언트가 서비스 이름을 Service Registry에 요청하여 찾는다.
  2. 서비스의 주소와 포트등을 클라이언트에 반환한다.
  3. 클라이언트는 Registry에서 받은 주소를 기반으로 서버에 요청한다.

이 과정에서 Service Registry는 실제 서버 인스턴스가 살아있는지 Heartbeat 체크를 한다.

위와 같이 동적 바인딩으로 유지하면 좋은 점은 아래와 같다.

  • 복제, 문제시 서버 대체, 로드 밸런싱에 용이해진다.
  • 서버의 주소가 변경되도 서비스에 영향이 없다.
  • 서비스의 가용성과 확장성이 용이해진다 (복제, 서버 대체, 로드 밸런싱이 용이해지므로)
  • 클라이언트가 서버의 주소를 몰라도 되어 클라이언트에서 작업할게 줄어든다.

Service Discovery 방식의 예시는 아래와 같다.

  • DNS : IP 주소가 바뀌어도 도메인 네임서버가 IP 주소를 따로 갖고있다면 접속하는데 문제가 없다.
  • etcd : key-value store
  • Consul : Service registry를 위한 툴이다. 서버와 클라이언트로 나뉘어져있다.
  • Kubernetes Service : 쿠버네티스 환경에서 Service registry이다.

c. 예시 : SUN RPC - Port Mapper

전통적인 SUN RPC 환경에서의 서비스를 제공하는 Port를 반환하는 방식이다.
이 방식 경우에는 일단 Server의 IP는 있어야한다.
해당 Server내에 Well-known한 Port로 Port Mapper가 작동하고 있는데 클라이언트 해당 서버의 Port Mapper가 Listen하고 있는 Port로 해당 서비스의 Port가 몇번인지 요청하면 해당 Port Mapper가 서비스의 Port를 반환하게 되고, 클라이언트는 반환 받은 Port로 서비스와 통신을 하게 된다.

5) Asynchronous RPC

기본적으로 설명하던 RPC는 Blocking 방식으로 호출 받은 측에서 응답이 오길 기다리고있는 방식이다.
하지만 이런 경우 대기시간 때문에 성능이 떨어진다. 앞서 OpenMPI나 OpenMP, 혹은 GPU에서도 그렇듯이
비동기 방식으로 응답이 오길 기다리지 않고 넘어가면 전송과 처리가 Overlap 되기 때문에 좀 더 처리량을 높일 수 있다.

6) RPC 전송 지원 방식

UDP와 TCP의 자세한 통신 방식에 대해서는 앞서 포스팅 한 바 있다.
UDP 위에 RPC가 올라가느냐, TCP 위에 RPC가 올라가느냐에 따라 신뢰성, 성능 등이 달라진다.

항목UDP-based RPCTCP-based RPC
연결성비연결성연결기반
신뢰성신뢰할수없음신뢰가능
전송 의미최선노력 데이터그램신뢰성있는 바이트 스트림
오버헤드낮음높음
메세지 경계있음(1 데이터그램 = 1 메세지)없음 (프레임 필요)
에러 제어응용 단에서 에러제어가 필요함응용 단에서 에러 제어를 덜 신경써도 됨
RPC에서 전형적으로 쓰는 예시단순 요청/응답, 낮은 오버헤드, 로그 시스템과 같이 약간 소실되도 되는 것들일반적인 RPC, 큰/스트리밍 데이터, 현대의 대부분 시스템

※ TCP-Based 방식에서도 at least once 방식으로 통신해야하는 이유?

TCP는 기본적으로 에러제어와 연결의 맺고 끊음등을 제어하는 로직이 포함된 프로토콜이다. 그렇다면 TCP 기반 RPC에서도 최소 at least once 방식을 사용해야하는 이유는 뭘까?

그 이유는 패킷이 전송 안되는 주요 이유 두 가지 살펴보면 알 수 있다.

  • ⓐ 비트 에러 (패킷 깨짐)
    패킷이 비트 에러 레이트가 높은 네트워크를 지나던 중 노이즈로 인해 패킷의 비트가 바뀌어버리면 체크썸과 같은 에러 체크 로직에 의해 변조 판정을 받게되고 전송 실패로 처리된다.

  • ⓑ 패킷 드랍
    트래픽이 몰리거나 해서 받는 측에서 버퍼가 충분치 않을 경우 버퍼가 꽉 차고 도착한 패킷의 경우에는 버림 처리가 된다.

위와 같은 두 가지 경우 중 패킷 드랍의 경우에는 TCP가 처리할 수 없으며 아예 네트워크가 잠시 끊긴 경우도 처리할 수 없기 때문에 재전송 처리가 가능한 At least once 방식을 사용해야한다.

7) RPC 종류

ⓐ SUN RPC

전통적인 RPC 방식으로 한번 요청은 한번 응답을 받아야하며 전형적인 동기 방식이다. 한번 요청에 한번의 연결이 있어야하기 때문에 현대적인 클라우드 환경에서 확장성에 제한이 있다.

ⓑ gRPC

전통적인 RPC는 TCP나 UDP위에서 구동된다면, gRPC는 기본적으로 HTTP/2 위에서 구동된다.
떼문에 전통적인 RPC 방식에 비해서 많은 기능을 지원한다.
지원하는 기능들은 아래와 같다.

  • 단일 연결에서 다수의 RPC 통신이 가능하다.
  • Streaming(데이터를 쪼개서 보내는 방식)을 지원한다.
  • 비동기 방식을 지원한다.
  • HTTPS에서 구동되기 때문에 TLS 위에서 구동되기에 기본적인 보안이 보장된다.
  • 다른 언어나 Platform을 지원한다.
  • DNS나 Kubernates와 같은 Service discovery를 지원한다.
※ Streaming 기능에 대한 추가 설명

gPRC는 SUN RPC와 달리 Streaming 기능을 지원한다. 아래 4가지로 설명할 수 있다.

  • 1 to 1
    전통적이고 일반적인 방식으로 요청 한번에 응답 한번으로 통신하는 방식이다.

  • 1 to Many
    요청은 한번이 이뤄지나 응답은 N번의 다수로 오는 것으로 real-time이 중요하거나 큰 데이터 검색의 경우에는 이런 형태로 통신을 하게 된다.

  • Many to 1
    많은 요청 이후에 한번의 응답을 받는 형태로, 파일 업로드나 로그 취합과 같은 형태의 시스템이 이러한 형태를 띈다.

  • Many to Many(full duplex)
    다수의 요청과 다수의 응답이 오가는 형태로 전이중 방식이다. 채팅 프로그램이나 실시간 연동 시스템이나 온라인 게임이나 steaming 환경에서 분석이 이러한 형태의 통신을 한다.

참고자료

  • 서강대학교 박성용 교수님 강의자료 - 병렬 분산 컴퓨팅

원문 참고자료들

  • G. Coulouria, J. Dollimore, T. Kindberg, and G. Blair, Distributed Systems: Concepts and Design, 5 th Edition, Pearson, 2012, ISBN 978-0-273-76059-7
  • M. van Steen and A. S. Tanenbaum, Distributed Systems, 3 rd Edition, 2017
  • Martin Kleppmann, Designing Data-Intensive Applications, 1 st Edition, O’Reilly Media, 2017, ISBN 978-1491903070 (또는 2nd Edition in February 2026)
This post is licensed under CC BY 4.0 by the author.