AEGLOSLAB ROS 2 Jazzy 대전

제3부 미들웨어: DDS·RTPS·RMW

3.8 Cyclone DDS 튜닝

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

Cyclone DDS는 Fast DDS와 설정 철학이 다르다. Fast DDS가 "손잡이를 많이 제공한다"면 Cyclone은 "기본값이 견고하고 손잡이는 적게"를 지향한다. 그래서 설정 파일이 짧고, 대신 세밀한 제어는 덜하다.

이 장의 목표는 셋이다. ① Cyclone을 튜닝하는 법. ② Fast DDS 설정을 옮길 때 무엇이 대응하고 무엇이 대응하지 않는지(3.5.6의 "XML 재작성" 비용의 실체). ③ 두 벤더의 철학 차이가 실무에 무엇을 의미하는지.

배우는 것:

  • 설정 방식 — CYCLONEDDS_URI와 XML 구조.
  • 네트워크 인터페이스 선택 — 다중 NIC에서 가장 자주 필요한 설정.
  • 버퍼·워터마크 — 대용량·고빈도 튜닝.
  • 디스커버리 설정 — 피어·멀티캐스트.
  • 추적(Tracing) — Cyclone의 강점 중 하나.
  • Fast DDS 설정 대응표 — 이전 시 무엇이 문제인가.
  • 적용 확인.
  • Fast DDS와의 대비 실측(3.8.12) — 설정 오류 반응이 정반대이고, 기본 내구성도 다르다.

선수 지식: 3.7(Fast DDS 튜닝 — 대조하며 읽는다), 3.2·3.3(RTPS·디스커버리), 1.5(QoS).

정직: Cyclone DDS 설정 요소 이름·구조·기본값은 버전에 따라 다르다. 아래는 Eclipse Cyclone DDS 공개 문서 기준이며 이 환경에서 실행 검증하지 않았다(목차 3). Cyclone은 설정 스키마를 문서화해 두었으므로(//CycloneDDS/Domain/... 경로 형태), 해당 버전의 설정 레퍼런스가 정본이다. 요소 이름을 추측해 쓰면 조용히 무시되므로 반드시 확인하라.

검증 상태(추가): 이 장의 서술 가운데 1곳과 3.8.12 전체는 이후 회차에서 Jazzy 실물과 대조해 확인했다 — 본문의 (실행 확인) 표기가 그것이다. 표기가 없는 서술은 위 고지가 그대로 적용된다(목차 3의 검증 범위 표).

3.8.2 설정 방식 — CYCLONEDDS_URI

Fast DDS와 다르게, 파일 경로뿐 아니라 XML 문자열을 직접 넣을 수 있다.

# ① 파일 경로
export CYCLONEDDS_URI=file:///etc/robot/cyclonedds.xml

# ② XML을 직접 (짧은 설정에 편리)
export CYCLONEDDS_URI='<CycloneDDS><Domain><General>
  <Interfaces><NetworkInterface name="eth0"/></Interfaces>
</General></Domain></CycloneDDS>'

# ③ 여러 소스 결합 (쉼표 구분)
export CYCLONEDDS_URI="file:///etc/robot/base.xml,file:///etc/robot/override.xml"

②가 실무에서 의외로 유용하다. "이 터미널만 인터페이스를 바꿔 시험" 같은 일회성 조정에 파일을 만들 필요가 없다.

기본 구조:

<?xml version="1.0" encoding="UTF-8" ?>
<CycloneDDS xmlns="https://cdds.io/config">
  <Domain id="any">
    <General>
      <Interfaces>
        <NetworkInterface autodetermine="true" priority="default" multicast="default"/>
      </Interfaces>
      <AllowMulticast>default</AllowMulticast>
      <MaxMessageSize>65500B</MaxMessageSize>
    </General>
    <Discovery>
      <ParticipantIndex>auto</ParticipantIndex>
    </Discovery>
    <Internal>
      <!-- 고급 설정 -->
    </Internal>
    <Tracing>
      <Verbosity>warning</Verbosity>
      <OutputFile>/var/log/cyclonedds.log</OutputFile>
    </Tracing>
  </Domain>
</CycloneDDS>

<Domain id="any">가 중요하다 — 모든 도메인에 적용한다는 뜻이다. 특정 도메인만 설정하려면 숫자를 쓴다.

Fast DDS와의 구조적 차이:

축Fast DDSCyclone DDS
단위프로파일(participant·writer·reader별)도메인 전역 중심
엔티티별 세부 설정풍부(토픽별 프로파일)제한적
설정 파일 길이길어지기 쉬움짧은 편
지정 방식파일만파일 또는 인라인 XML
학습 곡선가파름완만

견해: 이 차이가 벤더 선택에 실제로 영향을 준다(3.6.4의 축 ⑦). 토픽마다 다른 전송·메모리 정책이 필요하면 Fast DDS가 유리하고, "전체적으로 잘 동작하면 된다"면 Cyclone의 간결함이 유지보수에 유리하다. 설정 파일도 코드처럼 유지보수 대상이라는 점을 잊기 쉽다.

3.8.3 네트워크 인터페이스 선택 — 가장 자주 필요한 설정

다중 NIC 머신에서 DDS가 잘못된 인터페이스를 고르는 문제는 매우 흔하다(3.9). 로봇은 대개 유선·무선·USB 이더넷을 함께 갖는다.

<General>
  <Interfaces>
    <!-- 이름으로 지정 -->
    <NetworkInterface name="eth0" priority="10" multicast="true"/>
    <!-- 주소로 지정 -->
    <NetworkInterface address="192.168.1.5" priority="5"/>
  </Interfaces>
</General>
# 인라인으로 빠르게
export CYCLONEDDS_URI='<CycloneDDS><Domain><General><Interfaces>
  <NetworkInterface name="eth0"/>
</Interfaces></General></Domain></CycloneDDS>'
속성의미
name인터페이스 이름(eth0·wlan0)
addressIP 주소로 선택
priority여러 개일 때 선호 순위
multicast이 인터페이스에서 멀티캐스트 사용 여부
autodetermine자동 선택

멀티캐스트 제어:

<General>
  <AllowMulticast>spdp</AllowMulticast>   <!-- 디스커버리만 멀티캐스트, 데이터는 유니캐스트 -->
</General>
값의미
true·default멀티캐스트 사용
false멀티캐스트 완전 비활성
spdp디스커버리만 멀티캐스트

견해: spdp가 무선 환경에서 유용한 절충이다. 많은 Wi-Fi AP가 멀티캐스트를 최저 속도로 전송하거나 억제하는데(3.9), 디스커버리는 작고 드물어 견딜 만하지만 데이터 스트림은 그렇지 않다. 데이터를 유니캐스트로 전환하면 대역은 구독자 수에 비례해 늘지만 링크 품질은 안정된다.

함정: 인터페이스를 고정하면 그 인터페이스가 없거나 다운됐을 때 통신이 전혀 안 된다. 로봇이 유선·무선을 오가는 환경이라면 priority로 선호만 주고 자동 선택을 남겨 두는 편이 안전할 수 있다. 고정 여부는 환경의 안정성에 달렸다.

3.8.4 버퍼와 워터마크

<Internal>
  <!-- 소켓 버퍼 -->
  <SocketReceiveBufferSize min="10MB"/>
  <SocketSendBufferSize min="1MB"/>

  <!-- 신뢰성 전송 워터마크 -->
  <Watermarks>
    <WhcHigh>2MB</WhcHigh>      <!-- 쓰기 히스토리 캐시 상한 -->
    <WhcLow>512kB</WhcLow>
  </Watermarks>

  <!-- 전송 속도 제한 -->
  <MaxParticipants>0</MaxParticipants>
</Internal>

<General>
  <MaxMessageSize>65500B</MaxMessageSize>
  <FragmentSize>4000B</FragmentSize>
</General>
설정대응 문제관련 장
SocketReceiveBufferSize대용량 수신 유실1.4.4
SocketSendBufferSize송신 버스트3.2.7
WhcHigh·WhcLow신뢰성 전송의 흐름 제어3.2.5
MaxMessageSize·FragmentSize조각화 크기3.2.7

WhcHigh가 Fast DDS의 흐름 제어에 가장 가까운 개념이다. 쓰기 히스토리 캐시가 이 크기를 넘으면 발행이 억제된다 — 즉 publish()가 블로킹될 수 있다(1.4.2). 대용량 토픽에서 이 값이 너무 작으면 발행 측이 자주 막히고, 너무 크면 메모리를 쓴다.

OS 버퍼가 여전히 먼저다(3.7.4):

sudo sysctl -w net.core.rmem_max=10485760

함정: Cyclone은 소켓 버퍼가 요청보다 작으면 경고를 낼 수 있다(확인 필요). 그 경고를 무시하지 말 것 — sysctl을 상향하지 않은 상태라는 신호다.

FragmentSize는 MTU와 맞춰야 한다. 이더넷 MTU 1500이면 IP 헤더·UDP 헤더·RTPS 헤더를 뺀 값 이하로 두어야 IP 조각화(3.2.7)를 피한다. 위 예의 4000B는 점보 프레임(MTU 9000) 환경을 가정한 값이다 — 일반 MTU에서는 1400 안팎이 안전하다.

3.8.5 디스커버리 설정

<Discovery>
  <!-- 멀티캐스트 없이 지정 피어와 (3.3.7) -->
  <Peers>
    <Peer address="192.168.1.10"/>
    <Peer address="192.168.1.11"/>
  </Peers>

  <!-- 참가자 인덱스 — 포트 계산에 영향 (3.2.6) -->
  <ParticipantIndex>auto</ParticipantIndex>
  <MaxAutoParticipantIndex>60</MaxAutoParticipantIndex>

  <!-- 알림 주기 -->
  <SPDPInterval>5s</SPDPInterval>
  <LeaseDuration>20s</LeaseDuration>
</Discovery>
설정효과대가
Peers멀티캐스트 불필요수동 관리
SPDPInterval ↑상시 트래픽 감소발견 지연
LeaseDuration ↑트래픽 감소유령 노드 장기 잔존
MaxAutoParticipantIndex한 호스트의 참가자 수 상한포트 소비(3.2.6)

정직: Cyclone에는 Fast DDS의 Discovery Server(3.4)에 해당하는 기능이 표준으로 제공되지 않는다(확인 필요: 최신 버전에서 유사 기능이 추가됐을 수 있음). 대규모 그래프에서 Cyclone을 쓴다면 Peers 기반 정적 구성이나 도메인 분리(1.1.7), 또는 zenoh(3.10)로 접근하게 된다. 이것이 3.6에서 "벤더 전용 기능 의존"을 문서화하라고 한 이유의 구체적 사례다.

MaxAutoParticipantIndex가 실무적으로 중요하다. 한 호스트에서 참가자를 많이 만들면(노드=참가자 구현 — 3.3.5) 이 값에 걸려 새 참가자 생성이 실패할 수 있다. 컴포지션으로 참가자를 줄이거나 이 값을 높인다 — 단, 포트 소비가 늘어난다(3.2.6).

3.8.6 Tracing — Cyclone의 강점

Cyclone은 상세한 내부 추적 로그를 제공한다.

<Tracing>
  <Verbosity>config</Verbosity>
  <OutputFile>/var/log/cyclonedds/${CYCLONEDDS_PID}.log</OutputFile>
  <AppendToFile>false</AppendToFile>
</Tracing>
Verbosity내용
severe·warning문제만
info주요 이벤트
config적용된 설정 전체를 출력
fine·finer·finest상세 내부 동작

견해: config 수준이 튜닝 검증에 결정적이다. 적용된 설정 값이 로그로 나오므로, "XML이 적용됐나"를 추측하지 않고 확인할 수 있다. 3.7.9에서 Fast DDS 설정 확인이 까다로웠던 것과 대조된다 — Cyclone에서는 이것이 상대적으로 쉽다. 새 설정을 적용할 때는 일단 config로 실행해 로그를 확인하고, 확인 후 warning으로 낮추는 절차를 권한다.

finest는 디스커버리·재전송 문제 진단에 쓴다. 다만 로그가 엄청나게 많이 나오므로 재현 구간에만 켠다. 패킷 캡처(3.2.8)와 함께 보면 강력하다.

3.8.7 Fast DDS ↔ Cyclone 대응표

3.5.6에서 "XML 전체가 무효"라고 한 것의 실체다.

목적Fast DDSCyclone DDS
설정 파일 지정FASTRTPS_DEFAULT_PROFILES_FILECYCLONEDDS_URI
설정 단위프로파일(엔티티별)도메인 전역
토픽별 QoS<data_writer profile_name="rt/…">직접 대응 없음(확인 필요)
인터페이스 선택transport descriptor<Interfaces>
소켓 버퍼sendBufferSize·receiveBufferSizeSocketSendBufferSize·SocketReceiveBufferSize
조각 크기maxMessageSizeMaxMessageSize·FragmentSize
흐름 제어flow_controller_descriptorWatermarks(개념이 다름)
메모리 정책historyMemoryPolicy직접 대응 없음
공유메모리<type>SHM</type>iceoryx 연동(별도 구성)(확인 필요)
정적 피어initialPeersList<Peers>
Discovery ServerdiscoveryProtocol=CLIENT없음(확인 필요)
SPDP 주기leaseAnnouncementSPDPInterval
leaseleaseDurationLeaseDuration
로그XML <log><consumer> (환경변수 없음, 실행 확인)<Tracing><Verbosity>

"직접 대응 없음"이 세 개나 있다. 이것이 재작성 비용의 핵심이다 — 문법을 번역하는 것이 아니라 다른 수단으로 같은 목적을 달성해야 한다.

잃는 것대안
토픽별 세밀 설정코드에서 QoS 명시, 또는 도메인 전역 설정으로 타협
메모리 사전 할당 정책애플리케이션 쪽 무할당에 더 의존(2.6)
Discovery ServerPeers·도메인 분리·zenoh

견해: 이 표를 보면 "나중에 바꾸면 된다"가 왜 위험한지 구체적으로 드러난다. Fast DDS의 토픽별 프로파일과 메모리 정책에 의존해 실시간 시스템을 튜닝했다면, Cyclone으로 옮길 때 그 튜닝 전략 자체를 다시 설계해야 한다. 반대 방향(Cyclone → Fast DDS)은 상대적으로 쉽다 — 손잡이가 더 많은 쪽으로 가는 것이므로.

3.8.8 실전 레시피

각 레시피는 출발점이며 반드시 측정으로 조정한다.

3.8.8.1 다중 NIC 로봇 (가장 흔한 상황)

<CycloneDDS><Domain id="any">
  <General>
    <Interfaces>
      <NetworkInterface name="eth0" priority="10"/>
    </Interfaces>
    <AllowMulticast>default</AllowMulticast>
  </General>
</Domain></CycloneDDS>

3.8.8.2 무선 (1.6.5)

<CycloneDDS><Domain id="any">
  <General>
    <Interfaces><NetworkInterface name="wlan0"/></Interfaces>
    <AllowMulticast>spdp</AllowMulticast>          <!-- 데이터는 유니캐스트 -->
    <FragmentSize>1344B</FragmentSize>              <!-- MTU 안에 -->
  </General>
  <Discovery>
    <SPDPInterval>10s</SPDPInterval>
  </Discovery>
  <Internal>
    <Watermarks><WhcHigh>512kB</WhcHigh></Watermarks>
  </Internal>
</Domain></CycloneDDS>
  • QoS는 코드에서 BEST_EFFORT로(1.6.3.1).
  • spdp로 데이터 멀티캐스트 회피.
  • FragmentSize를 MTU 안으로.

3.8.8.3 대용량 (같은 호스트 아님)

<Internal>
  <SocketReceiveBufferSize min="16MB"/>
  <SocketSendBufferSize min="4MB"/>
  <Watermarks><WhcHigh>8MB</WhcHigh></Watermarks>
</Internal>
  • sysctl을 먼저 상향한다.
  • 같은 호스트면 컴포지션 + intra-process(2.7)가 근본 해법.

3.8.8.4 멀티캐스트 불가 환경

<General>
  <AllowMulticast>false</AllowMulticast>
</General>
<Discovery>
  <Peers>
    <Peer address="192.168.1.10"/>
    <Peer address="192.168.1.11"/>
  </Peers>
</Discovery>

3.8.9 적용 확인

# 1. URI 확인
echo $CYCLONEDDS_URI

# 2. 파일 문법
xmllint --noout /etc/robot/cyclonedds.xml

# 3. 설정이 적용됐는지 — Cyclone의 강점
export CYCLONEDDS_URI='<CycloneDDS><Domain id="any"><Tracing>
  <Verbosity>config</Verbosity><OutputFile>stderr</OutputFile>
</Tracing></Domain></CycloneDDS>'
ros2 run demo_nodes_cpp talker      # 적용된 설정이 로그로 출력된다

# 4. QoS 실제 적용 (RMW 공통)
ros2 topic info -v /chatter

# 5. 어느 인터페이스를 쓰는지
ss -ulnp | grep -E '74[0-9][0-9]'
sudo tcpdump -i eth0 -c 5 'udp portrange 7400-7700'

3번이 Fast DDS 대비 명확한 이점이다. 설정 로딩 결과를 직접 볼 수 있으므로 "적용됐나 적용되지 않았나"를 추측하지 않는다.

함정: CYCLONEDDS_URI에 인라인 XML을 쓸 때 따옴표·개행 처리에 주의하라. 셸이 해석하면서 깨지면 조용히 무시되거나 파싱 오류가 난다. 긴 설정은 파일로 두는 편이 안전하다.

3.8.12 Cyclone에 같은 실험을 했다 — 실측

3.7.12에서 Fast DDS의 XML을 실제로 적용해 봤다. 같은 네 가지 실패를 Cyclone에 그대로 적용했다(실행 확인, Jazzy — cyclonedds 0.10.5 · rmw_cyclonedds_cpp 2.2.3). 결과가 정반대다.

3.8.12.1 설정 오류에 대한 반응이 벤더마다 정반대다

시나리오Fast DDSCyclone DDS
정상 설정적용 · 오류 0적용 · 오류 0
없는 파일 경로조용히 무시 · 오류 0노드 생성 실패(종료 134)
깨진 XML조용히 무시 · 오류 0실패 + 줄 번호
모르는 요소조용히 무시 · 오류 0실패 + 요소 경로
잘못된 값 타입조용히 무시 · 오류 0실패 + 값 이름

Cyclone은 네 가지 모두에서 프로세스를 세운다. 그리고 메시지가 정확하다.

xml: can't open configuration file file:///tmp/nope.xml
xml: config: //CycloneDDS/Domain/General/Interfaces: parser error
     open/close tag mismatch (Domain) at line 4
xml: config: //CycloneDDS/Domain/General: TotallyMadeUpElement: unknown element
xml: config: //CycloneDDS/Domain/Internal/MaxQueuedRexmitBytes/#text:
     NOT_A_NUMBER: invalid value
[ERROR] [rmw_cyclonedds_cpp]: rmw_create_node: failed to create domain

설정 경로·줄 번호·요소 이름·값까지 출력해 준다. 3.8.9(적용 확인)가 왜 Fast DDS보다 짧아도 되는지가 여기서 설명된다 — 틀리면 안 뜨므로 확인할 것이 적다.

어느 쪽이 낫다고 단정하지 않는다 — 설계 선택이 다르다.

Cyclone(fail-fast)Fast DDS(fail-soft)
설정 오류를즉시 안다모른 채 운영한다
오타 하나면로봇이 기동되지 않는다기동되지만 튜닝이 적용되지 않는다
현장에서부팅 실패 = 명백한 장애성능 저하 = 원인 불명

로봇에서는 어느 쪽도 편리하지 않다. Cyclone이면 배포 전에 설정을 반드시 검증해야 하고(4.10의 CI에 넣기 좋다), Fast DDS면 런타임에 적용 여부를 확인해야 한다(3.7.12.3). 벤더를 바꾸면 이 절차도 바꿔야 한다 — 3.5.9가 말한 "재작성 예산"에 이것이 포함된다.

3.8.12.2 더 조용한 차이 — SystemDefaultsQoS()의 기본 내구성이 다르다

설정 파일 없이, 같은 코드로 두 벤더를 측정해 봤다(각 3회, 값 동일).

SystemDefaultsQoS()코드 QoS(10).reliable()
Fast DDSRELIABLE · TRANSIENT_LOCAL · depth 1RELIABLE · VOLATILE · depth 10
CycloneRELIABLE · VOLATILE · depth 1RELIABLE · VOLATILE · depth 10

코드가 명시한 QoS는 두 벤더가 같다. 그러나 SystemDefaultsQoS()는 내구성이 다르다 — Fast DDS는 래치되고, Cyclone은 안 된다.

이것이 벤더 교체의 조용한 함정이다. SystemDefaultsQoS()를 쓴 토픽은 Fast DDS에서 늦게 참여한 구독자가 마지막 값을 받는다. Cyclone으로 바꾸면 그 값이 안 온다 — 코드는 한 줄도 안 바뀌었는데 동작이 달라진다.

증상은 "바꾸고 나니 초기화가 가끔 실패한다" 쪽으로 나타난다. 1.4.7의 늦은 구독자 문제가 벤더에 따라 있다 없다 한다는 뜻이다. 3.6의 벤더 비교표에 이 축을 더해 읽으라.

대처는 단순하다 — "현장 튜닝을 위해 SystemDefaultsQoS()를 쓴다"(3.7.2)면 내구성만은 코드에 명시하라. 그러면 벤더가 바뀌어도 그 정책은 고정된다.

3.8.12.3 확인하지 못한 것

  • Cyclone의 XML로 QoS를 바꾸는 경로는 시험하지 않았다 — Cyclone의 설정은 전송·디스커버리·버퍼 중심이고 Fast DDS 같은 DataWriter QoS 프로파일 구조가 아니다(확인 필요: QoS 설정 경로 유무).
  • 3.8.7의 대응표 항목별 동작은 확인하지 않았다 — 이 실험은 오류 처리 방식만 봤다.
  • zenoh(3.10)의 설정 오류 처리는 측정하지 않았다(확인 필요).

3.8.10 함정 카탈로그

#함정증상대처
1인터페이스 미지정(다중 NIC)잘못된 NIC 사용·통신 실패<Interfaces> 지정
2인터페이스 고정 후 그 NIC 다운통신 전무priority로 선호만
3OS 버퍼 미조정요청값으로 설정되지 않음·경고sysctl 먼저
4FragmentSize > MTUIP 조각화·유실 증가MTU 안으로(3.2.7)
5WhcHigh 과소publish() 블로킹대용량이면 증가
6MaxAutoParticipantIndex 초과참가자 생성 실패컴포지션·값 증가
7Discovery Server 기대기능 없음Peers·도메인·zenoh
8인라인 XML 셸 인용 오류조용히 무시파일 사용
9config 로그 확인 생략미적용을 못 알아챔적용 확인(3.8.9)
10Fast DDS 설정을 문법만 번역대응 없는 항목 누락대응표 확인(3.8.7)
11finest 로그를 상시 활성성능 저하·디스크재현 구간만
12무선에서 데이터 멀티캐스트링크 품질 저하AllowMulticast=spdp
13Cyclone에서 설정 오타노드가 전혀 기동되지 않는다(종료 134)배포 전 검증을 CI에(3.8.12.1)
14벤더를 바꾼 뒤 늦은 구독자가 값을 못 받음SystemDefaultsQoS()의 내구성이 다르다내구성은 코드에 명시(3.8.12.2)

3.8.11 체크포인트

  • CYCLONEDDS_URI의 세 가지 지정 방식을 말할 수 있는가. 인라인 XML이 유용한 상황은?
  • Fast DDS와 Cyclone의 설정 단위 차이는 무엇이며, 그것이 벤더 선택에 어떤 영향을 주는가.
  • 다중 NIC 로봇에서 인터페이스를 지정하는 방법과, 고정할 때의 위험을 말할 수 있는가.
  • AllowMulticast=spdp가 무선에서 좋은 절충인 이유를 설명할 수 있는가. 그 대가는?
  • WhcHigh가 대응하는 문제와, 값이 너무 작을 때·클 때 각각 무슨 일이 생기는가.
  • FragmentSize를 MTU에 맞춰야 하는 이유는?
  • MaxAutoParticipantIndex에 걸리는 상황과 두 가지 대처를 말할 수 있는가.
  • Cyclone의 Tracing config 수준이 튜닝 검증에서 갖는 가치는? Fast DDS와 어떻게 대조되는가.
  • Fast DDS ↔ Cyclone 대응표에서 "직접 대응 없음"인 세 항목은 무엇이며, 각각의 대안은?
  • Fast DDS → Cyclone 이전이 반대 방향보다 어려운 이유를 설명할 수 있는가.
  • 설정 파일에 오타가 있을 때 Cyclone과 Fast DDS는 각각 어떻게 반응하는가. 로봇에서 어느 쪽이 더 위험한가(3.8.12.1).
  • 벤더를 바꿨을 때 SystemDefaultsQoS() 토픽의 동작이 왜 달라지는가(3.8.12.2).

다음 장 3.9는 벤더 설정을 벗어나 네트워크 자체를 다룬다 — 멀티캐스트·무선·Docker·다중 NIC·방화벽·MTU. 지금까지의 모든 튜닝이 무의미해지는 지점이 어디인지, 그리고 "유선에서는 되는데 현장에서 안 된다"를 어떻게 진단하는지.