AEGLOSLAB ROS 2 Jazzy 대전

제7부 센서·인지 파이프라인

7.8 대용량 데이터 파이프라인 설계

7.8.1 이 장에서 배우는 것 / 선수 지식

제7부에서 개별 센서를 다뤘다. 이제 그것들을 하나의 시스템으로 조립한다. 카메라 두 대, 라이다, 깊이 센서, IMU가 동시에 동작하고, 각각이 처리 파이프라인을 거쳐 항법·매니퓰레이션으로 들어간다.

이 조립에서 부딪히는 벽은 셋이다 — 대역·CPU·지연. 그리고 이 셋은 서로 맞물려 있다. 압축하면 대역은 줄지만 CPU가 늘고, 처리를 나누면 병렬화되지만 지연이 늘고, 한 프로세스에 몰면 빠르지만 격리를 잃는다.

이 장은 그 트레이드오프를 다루는 설계 원칙과 패턴을 정리한다. 개별 기술은 앞 장들에서 다뤘으므로, 여기서는 어떻게 조합할 것인가에 집중한다.

배우는 것:

  • 예산 수립 — 대역·CPU·지연을 숫자로.
  • 핵심 원칙 다섯 — 어디서 줄이고 무엇을 옮기지 않을 것인가.
  • 배치 설계 — 프로세스·컨테이너·머신 경계.
  • 역압(backpressure) — 처리보다 입력이 빠를 때.
  • 선택적 전송 — 로컬과 원격을 다르게.
  • 측정 — 병목을 찾는 법.
  • 패턴 카탈로그 — 검증된 구조들.

선수 지식: 제7부 전체(7.1~7.7), 1.13(컴포지션), 2.7(intra-process), 1.6(QoS), 1.3.9(대역 계산), 3.9(네트워크).

정직: 이 장은 원칙과 패턴을 다루며 구체 수치는 예시다. 실제 값은 하드웨어·센서·알고리즘에 따라 자릿수가 다르므로 측정으로 정해야 한다(3.6.1의 방침). GPU 가속 파이프라인은 자매서 nvidia가 다룬다(BK-06).(→ 172차 확인: topic_tools throttle의 실재와 실제 동작을 실측했고(예상 밖의 정량화 발견 포함), 7.8.5의 드롭-if-busy 원자적 교환 패턴을 실제 동시성 하에서 검증했다 — 아래 실행 확인(172차) 참고.)

7.8.2 예산 수립

설계의 출발점은 숫자다. 추측으로 시작하면 나중에 되돌아온다.

① 대역 예산표를 만든다(1.6.6의 토픽 계약 표를 확장):

토픽크기주기대역소비자경로
/cam_front/image_raw6.2 MB30 Hz186 MB/s검출프로세스 내
/cam_front/image_raw/compressed0.4 MB5 Hz2 MB/s원격 뷰어무선
/lidar/points3.7 MB10 Hz37 MB/s필터프로세스 내
/lidar/points_filtered0.3 MB10 Hz3 MB/s코스트맵프로세스 간
/scan6 KB10 Hz0.06 MB/sSLAM·Nav2전역
/detections2 KB10 Hz0.02 MB/s판단전역

이 표가 즉시 보여주는 것:

관찰결론
원본 이미지·구름이 지배적프로세스 밖으로 내보내면 안 된다
필터 후 크기가 급감소스 가까이서 줄여야
결과 메시지는 작다전역 발행 무해

② CPU 예산:

작업추정 부하실측 필요
디베이어·정류중간✅
JPEG 인코딩높음(7.2.8)✅
포인트클라우드 필터크기 비례✅
tf 변환(구름)점 수 비례(7.4.8)✅
검출·인식알고리즘 의존✅
직렬화(프로세스 간)크기 비례(1.3)✅

③ 지연 예산(7.7.8):

요구: 장애물 인지 → 정지 명령까지 300 ms

  • 센서 측정·전송: 30 ms
  • 전처리(필터): 40 ms
  • 검출: 120 ms
  • 동기화 대기: 30 ms
  • 판단·명령: 20 ms
  • 제어 루프 반영: 20 ms
  • 여유: 40 ms

견해: 이 세 표를 프로젝트 초기에 만들고 유지하라. 센서를 추가하거나 해상도를 높일 때 그 영향이 즉시 보이고, "왜 갑자기 느려졌나"를 추측하지 않게 된다. 표가 없으면 최적화가 추측이 된다.

7.8.3 핵심 원칙 다섯

7.8.3.1 ① 소스 가까이서 줄인다

❌ 원본을 멀리 보내지 않는다 센서 원본 즉시 축소 크롭 · 다운샘플 · ROI 처리 결과(작음) 원격 · 다른 프로세스

7.4.6의 필터 순서 원칙을 시스템 규모로 확장한 것이다. 줄이는 단계가 앞에 있을수록 이후 모든 비용이 비례해 감소한다.

축소 수단효과
ROI·크롭관심 없는 영역 제거
해상도 축소가로세로 절반이면 4배
VoxelGrid·다운샘플점 수 감소(7.4.6)
주기 감소비례 감소
베이어 유지3배 절약(7.2.2)

7.8.3.2 ② 옮기지 말고 처리하라

데이터를 이동시키는 비용이 처리 비용을 넘는 경우가 많다. 6 MB 이미지를 직렬화·전송·역직렬화하는 것보다, 그 자리에서 처리하고 작은 결과만 보내는 것이 비용이 낮다.

배치비용
같은 프로세스 + intra-process포인터 전달(2.7)
같은 호스트, 다른 프로세스직렬화 + 공유메모리·루프백
다른 호스트직렬화 + 네트워크
무선 너머재앙(3.9.3)

7.8.3.3 ③ 왕복을 피하라

❌ 왕복 ✅ 단방향 노드 A 노드 B 노드 A 노드 C 노드 A 노드 B 노드 C

7.4.5의 ROS↔PCL 왕복과 같은 문제다. 데이터가 경계를 넘을 때마다 비용이 발생하므로, 한 방향으로 흐르게 설계한다.

7.8.3.4 ④ 필요한 것만 구독하라

1.4.2의 lazy publisher와 7.2.4의 lazy 구독이 여기서 시스템 원칙이 된다.

상황조치
관측용 뷰어압축·저주기 전송만
디버그 토픽기본 비활성, 필요 시 활성화
여러 소비자가 같은 처리한 번 처리해 공유
조건부 처리구독자 수 확인(1.4.2)

7.8.3.5 ⑤ 측정하고 결정하라

추측으로 최적화하지 말라. 병목은 대개 예상과 다른 곳에 있다(7.8.7).

7.8.4 배치 설계

어디에 경계를 그을 것인가.

별도 프로세스 인지 컨테이너 (intra-process) 라이다 컨테이너 항법 컨테이너 검출 결과(작음) /scan(작음) 안전 감시 기록·진단 카메라 드라이버 디베이어·정류 검출 라이다 드라이버 필터·디스큐 구름→스캔 코스트맵 플래너·컨트롤러

경계를 긋는 기준(1.13.8의 판단을 센서 파이프라인에 적용):

함께 묶을 것분리할 것
대용량이 오가는 인접 단계안전 관련 노드(1.13.7)
같은 센서의 전처리 사슬실시간 제어(6.6)
안정적인 코드실험적·불안정 코드
같은 언어(C++)Python 노드(1.13.7)
같은 주기·수명자주 재시작하는 것

견해: 센서별로 컨테이너를 하나씩 두는 것이 좋은 출발점이다. 카메라 파이프라인 하나, 라이다 파이프라인 하나. 센서 안에서는 대용량이 포인터로 오가고, 센서 사이에는 작은 결과만 오간다. 한 센서가 고장 나도 다른 센서는 계속 동작한다.

머신 경계:

상황배치
GPU가 필요한 인지GPU 머신에
실시간 제어전용 머신·전용 코어(12.2)
센서 대역이 큼센서와 같은 머신에서 처리
원격 관제요약만 전송

함정: 머신 경계를 넘는 대용량 전송은 설계 실패다. 카메라가 A 머신에 있고 검출이 B 머신에 있으면 6 MB가 계속 네트워크를 경유한다. 처리를 데이터 쪽으로 옮기라 — 이것이 분산 시스템의 일반 원칙이기도 하다.

7.8.5 역압과 큐 설계

입력이 처리보다 빠르면 무슨 일이 생기는가.

예 최신 유지 아니오 전부 유지 센서 30 Hz 처리 시간 > 33 ms? 정상 큐 증가 큐 정책 ✅ 오래된 것 버림 지연 유지 ❌ 지연 누적 메모리 증가

ROS 2에는 진정한 역압이 없다. 발행자는 구독자의 처리 속도를 모른다(1.4.2). 그래서 큐 정책으로 관리해야 한다.

정책설정결과
최신 유지KEEP_LAST(1)(1.6.3)오래된 것 폐기, 지연 일정
작은 버퍼KEEP_LAST(2~5)약간의 버스트 흡수
큰 버퍼KEEP_LAST(30)지연 누적

견해: 인지 파이프라인에서는 KEEP_LAST(1~2)가 대개 맞다. 오래된 이미지를 처리하는 것은 의미가 없다 — 로봇은 이미 다른 곳에 있다. "최신만 처리하고 나머지는 버린다"가 실시간 인지의 올바른 정책이며, 1.6.4에서 "의심스러우면 depth를 작게"라고 한 것과 같은 논리다.

명시적 스로틀:

// 처리 능력에 맞춰 스스로 제한
void on_image(Image::ConstSharedPtr msg) {
  if (processing_.exchange(true)) {
    ++dropped_;                     // 이미 처리 중 — 이번 프레임 폐기
    return;
  }
  // 별도 스레드나 즉시 처리
  process(msg);
  processing_ = false;
}

견해: 폐기 수를 진단으로 내보내라(6.9). "30 Hz 중 20 Hz만 처리하고 있다"를 아는 것과 모르는 것은 다르다. 폐기율이 높으면 해상도·주기·알고리즘을 조정할 근거가 된다.

실행 확인(172차) — 위 processing_.exchange(true) 패턴을 실제 동시성 하에서 검증했다. 100Hz(10ms 간격)로 도착하는 프레임을 각각 별도 스레드(실제 동시 콜백을 흉내)로 처리하되 각 처리에 33ms(30Hz 카메라가 못 따라가는 상황을 흉내)가 걸리게 했다 — 처리 25건, 폐기 75건, 처리+폐기=100건(총 프레임 수와 정확히 일치), 실제 동시 접근 아래서도 프레임 유실·이중 처리·집계 오류가 없었다. 원본: tools/gap_harness/gap172/drop_test/(drop_test.cpp·drop_test_result.log).

입력 자체를 줄이는 것이 최선:

방법설명
센서 주기 낮추기드라이버 설정 — 근본적
다운샘플 노드topic_tools throttle 계열(확인 필요)(→ 172차 확인: 실재하고 정상 동작 — 다만 예상 밖의 정량화(quantization) 발견, 아래 실행 확인(172차) 참고)
조건부 처리필요할 때만 활성화

실행 확인(172차, Jazzy) — topic_tools throttle의 실재를 확인하고 실제 end-to-end 속도를 측정했다. ros2 pkg executables topic_tools로 throttle이 delay·demux·drop·mux·relay·relay_field·transform과 함께 실재함을 확인했다. 정확한 호출 문법은 --ros-args -p가 아니라 위치 인자(ros2 run topic_tools throttle messages <토픽> <Hz>)임을 실측으로 확인했다.

핵심 발견 — 요청한 속도가 실제 속도가 아니었다. 5Hz로 발행 중인 토픽을 throttle messages ... 2.0(2Hz 요청)으로 처리하니, 실제 측정된 출력 속도는 요청한 2.0Hz가 아니라 1.667~1.670Hz(간격 0.597~0.600초)였다 — 독립된 두 번의 반복 측정(단일 프로세스로 격리 재확인 포함)에서 일관됐다. 이는 5Hz(0.2초 간격) 입력과 정확히 나누어떨어지지 않는 목표 속도 조합에서, throttle이 "마지막 통과 후 요청 간격 이상 지난 다음 도착 메시지를 통과"시키는 방식으로 동작해 실제 달성 속도가 입력의 이산적 도착 시각에 양자화(quantize)됨을 뜻한다(5Hz÷3=1.667Hz — 정확히 3번째 메시지마다 통과). 1.0Hz 요청(이론상 정확히 나누어떨어지는 조합)에서도 간격이 1.001~1.203초로 완전히 안정적이지는 않았는데, 실제 발행 타이밍의 미세한 지터가 경계 판정에 영향을 준 것으로 보인다. "요청한 속도가 곧 실제 속도"라는 순진한 가정은 틀렸다 — 7.8.3.5("측정하고 결정하라")를 그대로 재확인하는 사례다.

컨테이너 gap172j. 원본: tools/gap_harness/gap172/throttle_test/(counter_pub.py·hz_input.log·hz_throttled_2hz_final.log·hz_throttled_1hz_clean.log·throttle_executables.log).

7.8.6 선택적 전송

같은 데이터를 용도에 따라 다르게 내보낸다(2.7.6의 토픽 분리를 일반화).

소비자형태경로
로컬 처리원본intra-process
로컬 기록원본 또는 압축같은 호스트
원격 관측압축·저해상도·저주기무선
원격 분석요약·이벤트무선
  • /camera/image_raw ← 컨테이너 내부 전용
  • /camera/image_raw/compressed ← 원격(자동, lazy — 7.2.4) ※ 플러그인이 설치된 환경에서만 생긴다(7.2.12)
  • /camera/thumbnail ← 저해상도 상시(선택)
  • /detections ← 결과, 전역

견해: "원격에서 전체 그래프를 본다"는 발상을 버리라(3.9.3의 반복). 원격 운영자가 필요한 것은 로봇의 원본 센서 데이터가 아니라 상태 요약과 필요 시의 저품질 영상이다. 이 구분을 설계에 반영하면 무선 문제의 대부분이 사라진다.

기록 전략(11.3에서 확장):

대상방식
상시작은 토픽(결과·진단·tf·/rosout)
이벤트 시원본을 순환 버퍼에서 저장
디버그 세션전체 기록(용량 감수)

7.8.7 측정 — 병목 찾기

"어디가 느린가"를 추측하지 말고 측정한다.

① 종단 지연 — 가장 중요:

// 원본 스탬프를 끝까지 전파(7.2.5)하고 마지막에서 측정
const auto age = now() - rclcpp::Time(result->header.stamp, clock_type);
stats_.record(age.seconds() * 1000.0);
// p50 · p99 · max 로 보고(2.5.6)

② 단계별 지연:

// 각 노드가 자기 처리 시간을 진단으로(6.9)
const auto t0 = std::chrono::steady_clock::now();
process(msg);
const auto dt = std::chrono::steady_clock::now() - t0;
proc_time_ms_.store(std::chrono::duration<double, std::milli>(dt).count());

③ 시스템 지표:

# 프로세스·스레드별 CPU
top -H -p $(pgrep -f perception_container)

# 대역 (프로세스 밖으로 나가는 것만)
ros2 topic bw /lidar/points
ros2 topic bw /detections

# 실제 주기
ros2 topic hz /detections --window 100

# 네트워크
ip -s link show eth0

④ 트레이싱(11.6):

ros2_tracing이 콜백 단위 시각을 커널 수준에서 기록한다. 측정 코드가 대상을 방해하지 않는 것이 장점이며, 파이프라인 전체의 시간 흐름을 볼 수 있다.

병목 판별:

관찰병목
특정 노드의 CPU가 100%그 노드의 알고리즘
CPU는 여유, 지연이 큼큐 대기·동기화(7.7.4)
대역이 링크 용량 근처전송
주기가 입력보다 낮음처리 능력
p99가 p50의 수 배간헐적 요인(GC·할당·스케줄링)

견해: "CPU는 여유인데 지연이 크다"가 흔하고 진단이 어렵다. 대개 큐에서 기다리거나(depth가 큼 — 1.6.4), 동기화를 기다리거나(slop·큐 — 7.7.4), executor에서 지연된다(2.2.9). 이 셋을 순서대로 확인하라.

7.8.8 패턴 카탈로그

검증된 구조들이다.

7.8.8.1 패턴 ①: 센서별 컨테이너

한 컨테이너, intra-process 작은 결과 센서 드라이버 전처리 특징 추출 전역 그래프

가장 기본이며 대부분의 경우 출발점이다.

7.8.8.2 패턴 ②: 조기 축소

드라이버 ROI·다운샘플 고부하 처리 결과 여기서 90% 줄인다

7.4.6의 필터 순서를 시스템에 적용.

7.8.8.3 패턴 ③: 이중 해상도

드라이버 고해상도 정밀 처리(저주기) 저해상도 빠른 처리(고주기)

용도별로 다른 해상도를 씀. 장애물 감지는 저해상도 고주기, 물체 인식은 고해상도 저주기.

7.8.8.4 패턴 ④: 요약 후 전송

무선, 작음 로봇: 센서 → 처리 → 요약 관제: 표시·판단

무선 시스템의 기본 구조(3.9.3).

7.8.8.5 패턴 ⑤: 순환 버퍼 + 이벤트 저장

이벤트 발생 시 센서 메모리 순환 버퍼(최근 30초) 디스크에 저장

상시 기록의 용량 문제를 해결(11.3).

7.8.8.6 패턴 ⑥: 계층적 처리

전체 데이터 빠른 사전 필터 관심 영역만 정밀 처리

대부분의 데이터를 낮은 비용으로 걸러내고 소수만 높은 비용으로 처리.

견해: 패턴 ②와 ⑥이 가장 자주 값을 한다. "전부에 대해 정밀하게"는 대개 불필요하며, 비용이 낮은 단계로 후보를 좁힌 뒤 비용이 큰 단계를 적용하면 자릿수 차이가 난다.

7.8.9 설계 절차

아니오 예 1. 요구 정의 지연 · 정확도 · 주기 2. 예산표 작성 대역 · CPU · 지연 3. 축소 지점 결정 무엇을 어디서 줄이나 4. 배치 결정 컨테이너 · 머신 경계 5. 큐·QoS 설계 최신 유지 정책 6. 구현 7. 측정 종단 지연 · 병목 요구 만족? 병목 식별 → 해당 단계로 진단 상시화

견해: 1·2번을 건너뛰지 말라. 요구가 없으면 언제 멈출지 모르고, 예산표가 없으면 어디를 고칠지 모른다. 이 두 단계에 반나절을 쓰는 것이 나중에 며칠을 아낀다 — 6.6.8의 실시간 구축 순서와 같은 논리다.

흔한 실패 순서:

잘못된 순서결과
구현 먼저, 최적화 나중구조를 갈아엎어야 함
배치를 늦게 결정프로세스 경계 재설계
측정 없이 최적화잘못된 곳을 고침
요구 없이 성능 추구끝이 없음

7.8.10 함정 카탈로그

#함정증상대처
1예산표 없이 설계나중에 구조 변경초기 작성
2원본을 프로세스 밖으로대역·CPU 폭증컨테이너 배치(1.13)
3축소를 파이프라인 뒤에불필요한 비용소스 가까이(7.8.3)
4머신 경계로 대용량 전송네트워크 포화처리를 데이터 쪽으로
5큐 depth를 크게지연 누적KEEP_LAST(1~2)
6폐기율을 모름처리 부족을 못 알아챔진단(6.9)
7원격에 원본 전송무선 붕괴(3.9.3)요약·압축
8헤더 미전파종단 지연 측정 불가헤더 복사(7.2.5)
9측정 없이 최적화잘못된 곳병목 판별(7.8.7)
10뷰어를 켠 채 측정결과 왜곡닫고 측정(2.7.6)
11안전 노드를 인지 컨테이너에크래시 전파(1.13.7)분리
12intra-process 미활성이득 없음use_intra_process_comms(2.7)
13전부 정밀 처리불필요한 비용계층적 처리

7.8.11 체크포인트

  • 대역·CPU·지연 예산표를 각각 어떻게 만드는가. 예산표가 즉시 보여 주는 것 세 가지는?
  • 핵심 원칙 다섯 가지를 말할 수 있는가. "옮기지 말고 처리하라"의 근거는?
  • 배치 경계를 긋는 기준(함께 묶을 것·분리할 것)을 각각 다섯 가지 들 수 있는가.
  • 센서별 컨테이너가 좋은 출발점인 이유는?
  • 머신 경계를 넘는 대용량 전송이 왜 설계 실패인가.
  • ROS 2에 진정한 역압이 없다는 사실이 큐 설계에 어떤 함의를 갖는가.
  • 인지 파이프라인에서 KEEP_LAST(1~2)가 맞는 이유를 설명할 수 있는가.
  • 폐기율을 진단으로 내보내야 하는 이유는?
  • 선택적 전송에서 소비자별 형태와 경로를 표로 말할 수 있는가.
  • 병목 판별표의 다섯 관찰과 각각의 원인을 말할 수 있는가. "CPU는 여유인데 지연이 크다"의 세 가지 확인 순서는?
  • 패턴 여섯 가지를 들고, 가장 자주 값을 하는 둘이 무엇인지 말할 수 있는가.
  • 설계 절차 일곱 단계와, 1·2번을 건너뛰면 안 되는 이유는?

여기까지가 제7부 센서·인지 파이프라인이다. 규약(7.1), 이미지(7.2)와 카메라 모델(7.3), 포인트클라우드(7.4)와 라이다(7.5), 관성 융합(7.6), 시간 정렬(7.7), 그리고 파이프라인 설계(7.8).

제7부가 반복한 것은 셋이다.

  1. 규약을 지키고 검증하라. 단위·좌표·프레임·스탬프·공분산이 틀리면 조용히 잘못된 결과가 나온다(7.1.8). "앞에 벽을 두고 RViz2로 확인" 이 가장 빠른 검증이다(7.1.9).
  2. 시간이 데이터의 일부다. 스탬프는 측정 시각이어야 하고(1.10.6), 스캔은 순간이 아니며(7.5.4), 센서마다 시각이 다르다(7.7). 시간을 다루지 않으면 움직이는 로봇에서 모든 것이 어긋난다.
  3. 줄이는 것이 최적화다. 소스 가까이서 줄이고(7.8.3), 옮기지 말고 처리하고(1.13·2.7), 최신만 처리한다(7.8.5). 대용량 데이터는 다루는 것보다 만들지 않는 것이 낫다.

이제 로봇은 움직이고 본다. 남은 것은 어디로 갈지 결정하는 것이다. 지도를 만들고, 자기 위치를 알고, 경로를 세우고, 장애물을 피하고, 실패에서 복구하는 일 — 제8부 항법(8.1~8.8) 의 주제다. Nav2 아키텍처 전경부터 시작한다.

7.8.12 제7부를 마치며 — 센서·인지 파이프라인

이 장은 제7부(7.1~7.8)의 마지막이므로, 부 전체를 하나의 관점으로 묶는다.

제7부가 반복해 말한 세 가지:

  1. 센서 데이터는 규약이 반이다. 프레임·타임스탬프·좌표 관례를 지키지 않으면 아래 스택 전체가 틀린 답을 낸다(7.1).
  2. 대역은 유한하다. 압축·다운샘플링·선별이 튜닝이 아니라 설계다(7.8) — 무선에서는 크기가 곧 도달률이다(3.9.13.3).
  3. 동기화에도 비용이 든다. 여러 센서를 맞추는 비용을 설계에 넣지 않으면 나중에 지연으로 대가를 치른다(7.7).

다음 부에서 무엇이 달라지나: 제7부까지가 인식이었다면 제8부는 그 위에서 움직이는 결정 — 항법이다. Nav2라는 큰 스택을 다루되, 이 책의 관심은 그 안에서 무엇이 어떤 주기로 실행되고 어디가 끊기는가다.

제8부는 항법(8.1~8.8)다.