UDP 프로토콜 실전 활용 | 저지연·DNS·게임·스트리밍과 QUIC 연결

이 글의 핵심

도메인별 UDP 채택 이유와 반례, QUIC·HTTP/3의 내부(연결 ID·TLS 1.3·혼잡 제어)와 TCP 대비 트레이드오프를 정리합니다.

UDP(User Datagram Protocol)는 연결 설정 없이 데이터그램을 보내는 경량 전송 프로토콜입니다. TCP가 신뢰성·순서·혼잡 제어를 대신 책임지는 대신, UDP는 “보내기만” 하고 손실·중복·순서 뒤집힘은 애플리케이션(또는 그 위의 QUIC, RTP 등)이 처리합니다. 따라서 DNS, 실시간 게임, VoIP/영상, 모니터링 에이전트처럼 지연 민감이거나 멀티캐스트가 필요한 영역에서 자주 선택됩니다. 이 글은 헤더·포트·체크섬 같은 기본을 짚은 뒤, 재전송·순서·MTU를 실무에서 어떻게 다루는지, 그리고 HTTP/3·QUIC이 UDP를 어떻게 쓰는지까지 연결합니다.


프로토콜 개요

역사 및 개발 배경

UDP는 RFC 768(1980)으로 표준화되었으며, IP 위의 멀티플렉싱(포트)과 최소한의 무결성(체크섬)을 제공하는 단순함이 철학입니다. “인터넷은 원래 불안정하다”는 전제 위에서, 실시간성이나 브로드캐스트/멀티캐스트 같은 요구는 가벼운 UDP에 맞기 쉽습니다.

OSI 7계층에서의 위치

UDP는 4계층(전송 계층)에 속합니다. IP가 호스트까지 배달하면, UDP 포트가 소켓(프로세스)으로 연결됩니다. 연결 상태는 없으며, 각 데이터그램은 독립적입니다.

핵심 특징

특징설명
비연결형송신 전 핸드셰이크가 없음(앱이 필요하면 직접 구현).
비신뢰성네트워크가 드롭·순서 변경을 일으켜도 UDP는 복구하지 않습니다.
데이터그램 경계한 번의 sendto가 한 번의 recvfrom에 그대로 대응합니다. 수신 버퍼가 데이터그램보다 작으면 나머지는 다음 recv로 넘어오지 않고 잘려서 버려집니다(Linux에서는 MSG_TRUNC로 감지).
멀티캐스트 가능성TCP와 달리 일대다 전송에 활용하기 쉬운 모델입니다.
오버헤드 작음8바이트 헤더로 TCP보다 가볍습니다.

동작 원리

UDP 헤더의 출발지 포트, 목적지 포트, 길이, 체크섬 각 16비트 필드

그림: UDP 헤더는 포트 두 개, 길이, 체크섬으로 8바이트뿐이라 신뢰성과 순서 보장은 상위 계층 몫이다.

헤더 구조(IPv4)

UDP 헤더는 8바이트입니다.

필드길이설명
Source Port16비트송신 포트(선택).
Destination Port16비트수신 포트.
Length16비트UDP 헤더+페이로드 길이.
Checksum16비트IPv4에서는 선택(0이면 미사용), IPv6에서는 의무.

실제 패킷으로 확인하면 이렇습니다. Linux 컨테이너(Python 3, 커널 7.2.12, 루프백)에서 SOCK_RAW로 UDP 패킷의 복사본을 받아 hello(5바이트)를 보낸 패킷을 열어 보니, IP 헤더 20바이트 뒤의 UDP 헤더 8바이트가 출발 포트, 도착 포트, Length = 13(헤더 8 + 데이터 5), Checksum = 0xfe20 순서로 들어 있었고 그 뒤에 hello가 이어졌습니다. 표의 Length가 페이로드 길이가 아니라 헤더를 포함한 길이라는 점이 눈으로 확인됩니다.

체크섬

IPv4에서는 체크섬이 선택이지만, IPv6 UDP에서는 의무입니다. 체크섬은 의사 헤더 + UDP + 페이로드를 포함해 계산됩니다. 애플리케이션 무결성이 더 중요하면 추가로 암호화·MAC을 씁니다.

Linux 컨테이너(Python 3, 커널 7.2.12, 루프백)에서 SOCK_RAW로 UDP 헤더를 직접 만들어 체크섬만 바꿔 보내 봤습니다. 체크섬 필드를 0으로 둔 패킷과 올바른 체크섬을 계산해 넣은 패킷은 둘 다 수신 소켓에 data가 도착했고, 틀린 값(0x1234)을 넣은 패킷은 수신 소켓에 전달되지 않고 버려졌습니다. 이때 /proc/net/snmp의 Udp: InCsumErrors가 1 늘었습니다. 체크섬 오류는 애플리케이션에 예외로 올라오지 않고 카운터로만 남으므로, 데이터가 가끔 사라질 때는 이 카운터(nstat -az UdpInCsumErrors)도 함께 봅니다. 루프백에서 만든 패킷이라 실제 NIC의 체크섬 오프로드 환경에서의 동작은 확인하지 않았습니다.

포트

0~65535 범위의 포트로 호스트 내 프로세스를 구분합니다. 잘 알려진 포트(예: DNS 53)는 IANA 레지스트리에 등록되어 있습니다.

메시지 경계: 한 번의 send는 하나의 데이터그램

TCP와 달리 UDP는 송신한 단위가 그대로 수신 단위가 됩니다. Linux 컨테이너(Python 3, 커널 7.2.12, 루프백)에서 AAAAA와 BBB를 연달아 보내면 recv가 정확히 AAAAA, BBB로 두 번 나눠 돌려줬고, 0바이트 데이터그램도 recv가 빈 바이트열을 돌려주는 정상 메시지였습니다. 반대로 수신 버퍼가 데이터그램보다 작으면 남는 부분은 버려집니다. 10바이트 데이터그램을 4바이트 버퍼로 받으면 앞 4바이트(0123)만 들어오고 나머지 6바이트는 사라졌으며, recvmsg의 플래그에서만 MSG_TRUNC로 잘렸다는 사실을 알 수 있었습니다. 일반 recv/recvfrom은 오류 없이 잘린 데이터를 돌려주므로, 프로토콜이 허용하는 최대 크기보다 작은 버퍼를 쓰지 않도록 합니다.

QUIC과의 관계(2026년 기준)

HTTP/3는 QUIC(UDP 위)을 사용합니다. QUIC은 암호화·혼잡 제어·다중 스트림·연결 마이그레이션을 UDP 데이터그램으로 캡슐화해, “UDP의 단순함”과 “TCP에 가까운 신뢰성” 사이의 현대적 절충을 제공합니다.

flowchart TB
  subgraph app [애플리케이션]
    H3[HTTP/3]
    RTP[RTP/실시간 미디어]
    DNS[DNS 클라이언트]
  end
  subgraph mid [프로토콜 스택 예]
    QUIC[QUIC + TLS]
    UDPR[UDP 소켓]
  end
  subgraph l3 [네트워크]
    IP[IP]
  end
  H3 --> QUIC
  QUIC --> UDPR
  RTP --> UDPR
  DNS --> UDPR
  UDPR --> IP

도메인별 용도와 설계 반례

UDP는 “빠르다”기보다 연결·혼잡 상태 기계를 피하고, 손실·순서·중복을 애플리케이션 정책으로 흡수할 수 있을 때 선택됩니다. 아래는 대표 도메인과 TCP가 더 나은 경우를 구분한 것입니다.

짧은 요청·응답: DNS·DHCP·NTP

DNS는 질의 한 번에 응답 한 번인 경우가 많아 연결 부담이 큰 TCP보다 UDP가 기본입니다. 응답이 크면 TCP 폴백(또는 TCP 우선)으로 전환하는 패턴이 흔합니다. DHCP·NTP도 브로드캐스트/멀티캐스트·상태 없는 트랜잭션에 가깝으며, 시간 동기는 약간의 손실보다 최신 샘플의 도착이 더 중요한 경우가 많습니다.

실시간 미디어: RTP·VoIP·게임

RTP는 보통 UDP 위에서 시퀀스·타임스탬프로 재생 시점을 맞추고, 늦게 도착한 프레임은 버리는 쪽이 낫습니다. VoIP도 유사합니다. 게임은 최신 월드 상태가 우선이므로 과거 패킷 재전송이 이미 stale인 경우가 많아, UDP + 상태 동기화가 자연스럽습니다. 손실은 인코딩·FEC·선택적 재전송으로 흡수하며, 브라우저의 WebRTC 미디어도 이 방식으로 UDP를 씁니다(WebRTC 가이드 참고). 반대로 파일 다운로드·결제·감사 로그처럼 바이트 무결성이 절대적이면 TCP(또는 QUIC)가 맞습니다.

관측·에이전트: Syslog·statsd·샘플링 메트릭

고빈도·소형 이벤트를 유실 허용하며 보낼 때 UDP가 사용됩니다. 유실률은 알림 채널과 집계 로직에서 상쇄합니다. 정확히 한 번 전달이 필요하면 TCP·메시지 큐로 옮겨야 합니다.

멀티캐스트·로컬 탐색

mDNS/Bonjour 등 로컬 링크 탐색은 UDP의 1:N 모델과 잘 맞습니다. WAN 멀티캐스트는 인프라 제약이 커서 애플리케이션 레벨 오버레이가 더 흔합니다.

반례: UDP가 “무조건” 좋지 않은 이유

NAT·방화벽 환경에서 UDP는 막히거나 타임아웃되기 쉽으며, MTU·단편화 이슈가 앱까지 그대로 올라옵니다. 대용량·공정한 대역 공유가 필요하면 혼잡 제어 없이 UDP를 쏘는 것은 인접 TCP 세션을 기아시키고 운영자에게 스로틀을 당하기 쉽습니다.

QUIC·HTTP/3와의 관계 심화

QUIC은 “UDP를 아무렇게나 쓴다”가 아니라, UDP 데이터그램에 상태 있는 연결·암호화·신뢰성·혼잡 제어를 사용자 공간+커널 UDP 조합으로 구현한 전송 프로토콜입니다. HTTP/3는 QUIC 위의 HTTP 의미론입니다.

왜 TCP가 아니라 UDP 위인가

TCP/UDP 이중 스택에서 새 전송 혁신을 넣으려면 OS 커널·중간 박스의 배포 주기가 병목이 됩니다. UDP는 이미 열려 있는 포트(예: 443/UDP)로 암호화된 QUIC 패킷을 실어 보내면, 브라우저·CDN이 빠르게 반복할 수 있는 진화 경로가 됩니다(물론 방화벽이 UDP를 막는 현장도 있어 HTTP/2 폴백이 필요합니다).

연결 ID(Connection ID)와 경로 변경

TCP는 4-tuple(src IP/port, dst IP/port)로 연결을 식별합니다. Wi‑Fi↔셀룰러 전환처럼 IP가 바뀌면 연결이 끊깁니다. QUIC은 연결 ID로 논리적 연결을 유지하려 하여 경로 마이그레이션에 유리합니다(구현·정책에 따라 제한).

TLS 1.3과의 결합

QUIC은 암호화가 프로토콜에 내장되어 있으며, TLS 1.3의 요소(예: 핸드셰이크·키 유도)를 통합합니다. “UDP라서 안전하지 않다”가 아니라, 첫 바이트부터 암호화·인증을 전제로 설계되었습니다. 세부는 TLS 프로토콜 가이드를 참고하세요.

스트림·HOL 블로킹

HTTP/2 over TCP는 한 연결에서 멀티플렉싱하지만, 패킷 손실 시 TCP의 바이트 순서 복구 때문에 다른 스트림까지 지연될 수 있는 TCP HOL 블로킹이 있습니다. QUIC은 스트림마다 순서를 따로 복구하므로, 한 패킷이 손실되면 그 패킷에 실린 스트림만 재전송을 기다리고 다른 스트림의 데이터는 계속 애플리케이션에 전달됩니다. 다만 혼잡 제어 윈도와 연결 수준 흐름 제어는 모든 스트림이 공유합니다.

혼잡 제어와 UDP “무제한 송신”의 차이

QUIC 구현체는 TCP와 유사한 혼잡 제어(예: CUBIC, BBR 계열 등)를 알고리즘으로 포함합니다. 따라서 “UDP=혼잡 무시”는 QUIC에 해당하지 않습니다. 오히려 앱이 직접 UDP 소켓을 열어 무제한 전송할 때가 문제입니다.

TCP·TLS와의 스택 비교(요약)

측면TCP+TLSQUIC(UDP)
연결 식별4-tuple연결 ID + 4-tuple 등
암호화 결합TLS가 상위에서 핸드셰이크TLS 1.3 요소 통합·기본 암호화
혼잡 제어커널 TCP 스택주로 사용자 공간 QUIC 구현체
경로 변경어려움연결 ID로 완화 시도

TCP 심화는 TCP 동작 원리와 짝을 이룹니다.


실전 프로그래밍

C++ (UDP echo 클라이언트 스케치)

// g++ -std=c++17 -O2 udp_client.cpp -o udp_client
#include <arpa/inet.h>
#include <sys/time.h>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
  const char* host = argc > 1 ? argv[1] : "127.0.0.1";
  const uint16_t port = argc > 2 ? static_cast<uint16_t>(std::stoi(argv[2])) : 5353;
  int fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (fd < 0) { perror("socket"); return 1; }
  timeval tv{3, 0};  // 응답이 없을 수 있으므로 수신 타임아웃 3초
  setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
  sockaddr_in peer{};
  peer.sin_family = AF_INET;
  peer.sin_port = htons(port);
  if (inet_pton(AF_INET, host, &peer.sin_addr) != 1) return 1;
  const std::string msg = "ping";
  if (sendto(fd, msg.data(), msg.size(), 0,
             reinterpret_cast<sockaddr*>(&peer), sizeof(peer)) < 0) {
    perror("sendto");
    close(fd);
    return 1;
  }
  char buf[2048];
  socklen_t plen = sizeof(peer);
  ssize_t n = recvfrom(fd, buf, sizeof(buf), 0,
                        reinterpret_cast<sockaddr*>(&peer), &plen);
  if (n < 0) { perror("recvfrom"); close(fd); return 1; }  // 타임아웃이면 EAGAIN
  std::cout.write(buf, n);
  std::cout << '\n';
  close(fd);
  return 0;
}

Python 3

#!/usr/bin/env python3
import socket
import sys
def main() -> None:
    host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
    port = int(sys.argv[2]) if len(sys.argv) > 2 else 5353
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(3.0)
    try:
        sock.sendto(b"ping", (host, port))
        data, addr = sock.recvfrom(4096)
        print(f"from {addr}: {data!r}")
    except socket.timeout:
        print("timeout: no response", file=sys.stderr)
        raise SystemExit(1)
    finally:
        sock.close()
if __name__ == "__main__":
    main()

UDP에서는 응답이 오지 않는 것이 정상적인 시나리오이므로, settimeout(C++ 예제에서는 SO_RCVTIMEO)으로 대기 상한을 반드시 둡니다.

이 클라이언트를 로컬 UDP 에코 서버에 붙여 보면 from ('127.0.0.1', 5400): b'pong'을 출력하고 종료 코드 0으로 끝납니다. 아무도 받지 않는 포트로 보내면 3초 뒤 timeout: no response를 출력하고 종료 코드 1로 끝납니다(Python 3.9.6, macOS 27.0.1). 기본 포트 5353은 mDNS가 쓰는 포트라, 이 맥에서는 같은 포트로 에코 서버를 띄우려 하면 Address already in use가 나서 다른 포트로 시험해야 했습니다.

JavaScript (Node.js dgram)

// node udp_client.mjs
import dgram from "node:dgram";
const host = process.argv[2] ?? "127.0.0.1";
const port = Number(process.argv[3] ?? 5353);
const socket = dgram.createSocket("udp4");
const onMessage = (msg, rinfo) => {
  console.log(`from ${rinfo.address}:${rinfo.port}: ${msg.toString()}`);
  socket.close();
};
socket.on("message", onMessage);
socket.on("error", (err) => {
  console.error(err.message);
  socket.close();
  process.exitCode = 1;
});
socket.send(Buffer.from("ping"), port, host, (err) => {
  if (err) {
    console.error(err.message);
    process.exitCode = 1;
    socket.close();
  }
});
setTimeout(() => {
  socket.close();
  console.error("timeout");
  process.exitCode = 1;
}, 3000).unref();

C++ 예제는 Ubuntu 24.04 컨테이너의 g++ 13.3으로 컴파일해 파이썬 에코 서버에 붙이면 pong을 출력하고 종료 코드 0으로 끝납니다. 아무도 받지 않는 로컬 포트로 보내면 3초 뒤 recvfrom: Resource temporarily unavailable(EAGAIN)을 출력하고 종료 코드 1입니다. connect()하지 않은 소켓이라 “포트 닫힘” ICMP 오류가 ECONNREFUSED로 올라오지 않고 타임아웃으로만 보였습니다. Node 예제도 macOS의 Node v26.8.2에서 같은 에코 서버에는 from 127.0.0.1:5401: pong을, 닫힌 포트에는 3초 뒤 timeout과 종료 코드 1을 냈습니다. (2026-10-29)

에러 처리·재전송 로직(애플리케이션 레벨)

UDP는 ACK가 없으므로, 요청-응답 패턴이면 일정 시간 내 미수신 시 재전송, 요청 ID로 중복 제거, 지수 백오프를 앱에서 구현합니다. QUIC/RTP는 이런 역할을 프로토콜이 대신합니다.

UDP 소켓에서도 오류가 전달되는 경우가 있습니다. Linux 컨테이너(Python 3, 커널 7.2.12, 루프백)에서 아무도 듣지 않는 포트로 보냈을 때, connect()를 하지 않은 소켓은 sendto 뒤 recvfrom이 타임아웃만 나고 아무 오류도 알려 주지 않았습니다. 반대로 connect()로 상대를 지정한 UDP 소켓은 첫 send 뒤 도착한 ICMP port unreachable이 다음 send와 recv에서 ECONNREFUSED(Connection refused)로 보고됐습니다. UDP에 연결 상태가 생기는 것은 아니고, 커널이 도착한 ICMP 오류를 그 소켓에 붙여 줄 뿐입니다. 오류는 한 번 보고되면 사라져서, 같은 소켓으로 계속 보내 보니 send가 ECONNREFUSED, 성공, ECONNREFUSED, 성공 순서로 번갈아 나왔습니다(성공한 send가 다시 ICMP를 불러 다음 send에서 보고됨). 상대가 죽었는지 빨리 알고 싶다면 connect()한 UDP 소켓과 오류 처리를 쓰고, 오류가 없다는 것을 상대가 살아 있다는 증거로 받아들이지 않습니다.


성능 특성

지연 시간(Latency)

핸드셰이크가 없어 첫 데이터그램을 즉시 보낼 수 있습니다(방화벽·NAT 제외). 실시간 게임·음성에서 “첫 패킷까지의 시간”이 중요할 때 유리합니다.

처리량(Throughput)

UDP 자체는 윈도 기반 혼잡 제어가 없어 무제한으로 쏠 수 있지만, 그렇게 하면 네트워크와 다른 사용자에게 피해를 줍니다. 대용량 전송은 애플리케이션 혼잡 제어 또는 QUIC을 고려해야 합니다.

오버헤드

8바이트 UDP 헤더로 TCP보다 작습니다. 다만 애플리케이션 헤더·암호화가 붙으면 차이는 줄어듭니다.

벤치마크 참고

루프백에서 측정한 pps는 커널과 CPU의 한계만 보여 줄 뿐이고, 인터넷 구간에서는 MTU·손실·QoS 때문에 처리량보다 패킷 손실률이 서비스 품질을 가릅니다. 게임/음성은 pps(초당 패킷 수)와 지터가 중요합니다.


최적화 팁

패킷 크기(MTU)

이더넷 MTU 1500 기준으로 IP+UDP+페이로드가 경로 MTU를 넘지 않게 설계합니다. IP 단편화가 일어나면 조각 하나만 잃어도 데이터그램 전체가 버려지고, 단편을 걸러 내는 방화벽도 있어 피하는 편이 좋습니다.

UDP 페이로드의 이론적 상한은 65,535(IP 전체 길이) − 20(IP 헤더) − 8(UDP 헤더) = 65,507바이트입니다. Linux 컨테이너(Python 3, 커널 7.2.12, 루프백)에서 루프백으로 65,507바이트는 sendto가 성공했고 65,508바이트는 EMSGSIZE(Message too large)로 실패했습니다. 다만 상한까지 보낼 수 있다는 것과 안전하다는 것은 다릅니다. 루프백 MTU는 크지만 인터넷 경로에서는 이더넷 MTU 1,500 기준 IP 단편화가 일어나므로, 위 설명대로 경로 MTU에 맞춰 설계합니다. 단편화 동작 자체는 이 실험에서 확인하지 않았습니다.

재전송·순서

  • 재전송: RTT 측정, 지터 버퍼, 중복 시퀀스 무시를 함께 설계합니다.
  • 순서: 시퀀스 번호를 두며, 늦게 도착한 패킷은 버리거나 버퍼링할지 정책을 명확히 합니다.

대역폭 공정성

UDP로 무제한 flood하면 ISP·데이터센터에서 스로틀되거나 인접 TCP 세션을 기아 상태로 만들 수 있습니다. 레이트 리밋과 혼잡 제어는 시민 의식 수준을 넘어 실제 서비스 안정성 문제입니다.


흔한 문제와 해결

흔한 실수와 해결

실수결과해결
UDP에 TCP처럼 재전송이 있다고 가정앱이 손실을 복구하지 않음요청-응답이면 타임아웃·재시도·ID 중복 제거 설계
타임아웃 없이 recv 대기스레드·이벤트 루프가 영구 대기settimeout 등으로 상한 설정
큰 페이로드를 한 번에 전송경로 MTU·단편화 이슈앱 단위 분할과 수신 쪽 재조립
UDP만 열어두고 HTTP/3가 안 됨방화벽이 UDP 443 차단인프라 정책과 폴백(HTTP/2) 확인

패킷 손실

Wi‑Fi·모바일·혼잡 링크에서 흔합니다. FEC, 낮은 비트레이트, I-frame 주기 조정, NACK/재전송(지연 허용 시) 등을 선택합니다.

순서 뒤바뀜

멀티패스·라우팅 변화로 발생할 수 있습니다. 시퀀스 번호와 재정렬 버퍼로 해결하거나, 순서 무관 프로토콜로 설계합니다.

NAT·방화벽

UDP는 연결 상태가 없어 NAT가 타임아웃으로 매핑을 지우면 외부에서 역방향 패킷이 실패합니다. 주기적 keepalive, STUN/TURN(WebRTC), QUIC의 연결 유지가 관련됩니다.


프로덕션 소켓·커널·관측 (Linux 중심)

송수신 버퍼

고속 패킷 처리 시 커널 UDP 소켓 버퍼가 부족하면 recv 경로에서 드롭이 늘고, 애플리케이션은 간헐적 손실만 보게 됩니다. sysctl의 net.core.rmem_max·net.core.wmem_max와 소켓별 SO_RCVBUF/SO_SNDBUF를 워크로드에 맞춰 조정합니다. 변경은 부하 테스트와 함께 적용하며, 컨테이너에서는 호스트·네임스페이스 한도를 함께 봅니다.

소켓 버퍼 크기는 요청한 값 그대로 적용되지 않습니다. Linux 컨테이너(Python 3, 커널 7.2.12, 루프백)(rmem_default 229,376, rmem_max 7,500,000)에서 SO_RCVBUF를 65,536으로 요청하면 getsockopt가 131,072를, 500,000으로 요청하면 1,000,000을 돌려줬습니다. 리눅스는 관리용 오버헤드를 감안해 요청값의 두 배를 잡기 때문입니다. 10,000,000을 요청했을 때는 rmem_max(7,500,000)로 제한된 뒤 두 배가 되어 15,000,000이 나왔으므로, 큰 버퍼를 원하면 소켓 옵션만이 아니라 net.core.rmem_max도 함께 올려야 합니다. getsockopt로 실제 값을 읽어 확인하는 습관이 안전합니다.

SO_REUSEPORT와 수평 확장

동일 포트에 여러 프로세스/스레드를 붙일 때 커널이 패킷을 분배해 주는 기능으로, 멀티코어에서 수신 병목을 줄이는 데 유용합니다. 다만 연결 추적(stateful) 설계와는 다른 개념이므로, DNS·게임·메트릭 에이전트처럼 무상태 데이터그램에 맞는지 검토합니다.

관측 지표

  • netstat -su / nstat: UDP 오류·버퍼 스냅샷
  • dropwatch / perf: 커널 드롭 경로
  • 애플리케이션: 시퀀스 갭·지터·RTT 샘플을 메트릭으로 노출

수신 버퍼 드롭이 이 카운터에 어떻게 잡히는지 컨테이너(Ubuntu 24.04, OrbStack 커널)에서 확인했습니다. 수신 소켓에 SO_RCVBUF=4096을 주면 getsockopt로 읽은 값은 8192였고(커널이 요청값의 두 배로 잡습니다), 읽지 않는 이 소켓에 1000바이트 데이터그램 3000개를 보내자 3개만 받을 수 있었습니다. 그 사이 nstat의 UdpRcvbufErrors와 UdpInErrors가 모두 0에서 2997로 올랐고, netstat -su에는 2997 receive buffer errors와 2997 packet receive errors로 나왔습니다. 이 컨테이너의 net.core.rmem_max·wmem_max는 7500000이었는데, 호스트·배포판마다 기본값이 다르므로 이 값을 기준으로 삼지 말고 직접 sysctl로 확인해야 합니다.

이 지표는 “UDP가 빠르다”가 아니라 “시스템이 감당 가능한 pps/바이트”를 정의하는 데 도움이 됩니다.

참고 자료



같이 보면 좋은 글

  • FTP 프로토콜 실전 활용 | Active·Passive·FTPS·SFTP와 파일 전송 운영