hello, robot

노드 하나를 만드는 법을 익히고 나면 다음 질문이 와요. 실제 로봇은 노드를 몇 개로 쪼개야 할까요.

두 극단이 있어요. 큰 노드 하나가 센싱부터 제어까지 다 하는 방식은 쉬워요. 함수 호출로 데이터가 오가니 통신 비용이 없고 코드가 한 곳에 있죠. 대신 재사용도 교체도 부분 재시작도 안 되고, 한 부분이 죽으면 전체가 죽어요.

기능별로 잘게 나눠 조립하는 방식은 반대예요. 재사용과 독립 개발이 되고, 하나가 죽어도 나머지가 살아요. 대신 노드 사이가 프로세스 경계라 통신 비용이 들어요.

노드 하나가 프로세스 하나예요

이 비용의 정체를 알아야 판단이 서요.

노드는 기본적으로 각각 독립된 프로세스로 떠요. 프로세스가 다르면 메모리를 공유하지 않으니, 노드 A가 노드 B에게 데이터를 주려면 바이트 열로 바꿔 보내고 받는 쪽에서 다시 되돌려야 해요.

카메라 프레임 하나가 수 메가바이트인데 초당 서른 장씩 흐른다고 생각해 보면, 이 변환과 복사가 그냥 비용이 아니라 병목이 돼요. 미들웨어가 느리다는 말은 대개 이 오버헤드를 가리켜요.

단계를 이어 붙이는 구조에서는 이게 누적돼요. 카메라에서 전처리, 검출, 추적으로 이어지는 사슬이면 단계 수만큼 복사가 쌓여요. 각 단계가 노드라 교체는 쉬운데, 그 대가가 매 경계마다 붙는 거죠.

개발은 나누고 배치할 때 합쳐요

여기서 나오는 절충이 좋아요. 모듈성은 지키고 비용만 없애는 방법이 있어요.

코드는 노드 단위로 쪼개 놓되, 실행할 때 여러 노드를 한 프로세스에 함께 올려요. 같은 프로세스 안이면 메모리를 공유하니 변환도 복사도 없이 포인터만 넘겨요.

핵심은 이게 실행 시점의 선택이라는 거예요. 노드로 짜 두면 나중에 따로 띄울지 한 프로세스로 합칠지를 고를 수 있어요. 개발할 때는 레고처럼 나눠서 하고, 배치할 때 무거운 데이터가 오가는 노드들만 묶어 복사 비용을 지우는 거죠.

그래서 설계 판단이 “나눌까 합칠까”가 아니게 돼요. 나눠 놓고, 어디를 묶을지는 나중에 정해요. 반대 방향(합쳐 놓고 나중에 나누기)은 훨씬 비싸거든요.

넷 중에 무엇을 쓸까요

노드끼리 데이터를 주고받는 방법이 네 가지인데, 선택 기준은 명확해요.

방식모양언제
발행·구독단방향 스트림, 다대다, 비동기연속 데이터 — 센서값, 상태 알림
요청·응답1대1, 응답까지 기다림즉답 — “지금 상태 줘”, “이 설정 바꿔”
목표·피드백진행률과 취소가 붙음오래 걸리는 작업 — “저기까지 이동”
설정값실행 중 조회·변경튜닝 — 최대 속도, 제어 게인

첫 번째의 사용 빈도가 압도적인 이유가 있어요. 로봇 안 대부분의 데이터가 센서 스트림이거든요. 연속적이고, 받는 쪽이 여럿일 수 있고, 보내는 쪽이 받는 쪽의 처리를 기다리면 안 되는 성질이라 정확히 맞아요.

발행자는 구독자가 있든 없든 그냥 쏴요. 0명이어도 에러가 아니에요. 이 성질이 로봇에서 중요한데, 화면에 띄우는 노드를 껐다고 센서 노드가 멈추면 안 되니까요.

나머지 셋은 첫 번째로 표현하기 어색한 것들을 메워요. 한 번의 질의응답이면 두 번째, 오래 걸리고 중간 취소가 필요하면 세 번째, 재시작 없이 바꿀 값이면 네 번째예요. 세 번째는 두 번째의 장시간 버전이라고 보면 관계가 잡혀요.

터미널 열 개를 열 수는 없어요

나누다 보면 노드가 금세 다섯 개, 열 개가 돼요. 각각 따로 실행하는 건 현실적이지 않죠.

그래서 어떤 노드들을 어떤 설정으로 띄울지 적어 둔 정의서를 쓰고, 그걸 한 번에 실행해요. 정의서 안에서 다른 정의서를 불러올 수도 있어서 계층으로 조립돼요.

게임 엔진의 씬 로딩과 감각이 비슷한데, 결정적으로 다른 점이 하나 있어요. 씬 안의 오브젝트들은 한 프로세스 안에 다 들어 있지만, 정의서로 뜬 노드들은 각각 독립 프로세스예요.

그래서 두 가지가 따라와요. 노드 하나가 죽어도 나머지는 계속 돌아요. 카메라 노드가 죽어도 모터 제어는 살아 있어야 하는 로봇에서는 필수 성질이죠. 그리고 다른 컴퓨터의 노드끼리도 같은 네트워크면 통신해요. 센서는 작은 보드에서, 무거운 추론은 데스크톱에서 돌리면서 하나의 시스템으로 묶을 수 있어요.

어디까지가 이 세계인가

마지막으로 경계 하나예요. 로봇 소프트웨어는 한 층이 아니고, 실시간성이 다른 두 세계가 선 하나로 이어져 있어요.

마이크로초 단위 타이밍이 필요한 일은 아래층 담당이에요. 모터 구동, 엔코더 읽기, 빠르게 도는 제어 루프요. 범용 운영체제는 마이크로초 타이밍을 보장하지 못하거든요. 킬로헤르츠로 도는 루프를 일반 프로세스에 맡기면 스케줄러가 다른 일을 하느라 주기가 흔들려요.

위층은 그 위에서 명령과 상태를 주고받는 조율 층이에요. 수십에서 수백 헤르츠로 “목표 속도 이만큼”을 내려보내고 “현재 값”을 올려받죠. 순간순간의 전류 제어는 아래층이 자기 안에서 처리해요.

둘을 잇는 다리가 시리얼 통신이에요. 그래서 로봇 시스템을 볼 때 어느 층의 문제인지부터 가르는 게 진단의 첫 단계예요. 주기가 흔들리는 문제를 위층에서 아무리 고쳐도 안 되는 이유가 여기 있어요.

아래층에서 타이밍을 지키는 이야기는 인터럽트 — 로봇이 자기 위치를 잊는 순간에, 두 층을 잇는 선의 프로토콜은 C++ 시리얼 IO — termios와 프레임 재조립에 있어요. 노드끼리 연결이 조용히 실패하는 이야기는 ROS2 QoS — 조용히 연결이 안 돼요에 있고요.