Firefox의 Rust 기반 고속 UDP I/O
(max-inden.de)- Firefox의 HTTP 트래픽 약 20%가 HTTP/3을 사용하며, 이는 QUIC 및 UDP 위에서 작동함
- 기존 네트워크 I/O 계층인 NSPR를 Rust 기반 quinn-udp로 대체하면서 성능과 메모리 안전성을 강화함
- 각 운영체제별로 최신 시스템 콜(multi-message, segmentation offloading 등)을 적극적으로 활용해 성능 최적화를 적용함
- Windows 및 MacOS에서는 일부 기능이 호환성, 드라이버 이슈 등으로 제한되었으나, Linux에서는 최적 성능을 확인함
- QUIC ECN 지원과 UDP I/O 관련 다양한 플랫폼별 시행착오, 버그 해결 경험이 향후 프로젝트 및 오픈소스 생태계에도 도움이 될 전망임
동기
- Firefox HTTP 트래픽의 약 20%가 HTTP/3을 사용하며, 이는 QUIC을 통해 동작하고, 다시 UDP 위에서 구현됨
- Firefox는 역사적으로 NSPR 라이브러리를 네트워크 I/O에 사용했으나, UDP I/O 관련 기능이 오래되고 제한적임 (주요 함수는
PR_SendTo
,PR_RecvFrom
) - 운영체제들은 최근에 멀티 메시지 시스템 콜(ex.
sendmmsg
,recvmmsg
)과 세그멘테이션 오프로드(GSO, GRO) 같은 네트워크 최적화를 제공함 - 이러한 기술들은 UDP I/O 성능을 크게 향상시킬 수 있음
- Firefox가 기존 UDP I/O 스택을 현대적 시스템 콜로 교체해 이점을 얻을 수 있는지 모색함
개요
- 프로젝트는 2024년 중반 시작, 목표는 Firefox의 QUIC UDP I/O 스택을 모든 지원 OS에서 현대적 시스템 콜로 재구축하는 것임
- 성능 개선과 더불어, UDP I/O에 메모리 안전성이 보장되는 Rust를 활용해 보안성 향상도 도모함
- QUIC 자체는 이미 Rust로 구현되어 있어서, Rust 기반 quinn-udp 라이브러리 위에서 개발을 진행함
- OS 간 시스템 콜 차이는 개발 난이도를 높였으나, quinn-udp 덕분에 개발 속도가 크게 개선됨
- 2025년 중반 현재, 대다수 Firefox 사용자에게 적용이 진행 중이며, 성능 벤치마크에서 최대 4Gbit/s로 대폭 상승하는 결과를 보임
UDP I/O 구조와 현대적 최적화 방식
단일 데이터그램 전송
- 기존 방식은
sendto
와recvfrom
을 사용, 한 번에 단일 UDP 데이터그램만 송수신 - 사용자 공간과 커널 공간 간의 전환 비용은 데이터그램 단위로 지불되어, 대용량 트래픽 환경에서는 비효율적임
- 예시: 1500바이트 미만의 패킷을 초당 수백 Mbit 이상으로 보내려면 상당한 오버헤드 발생
다중 데이터그램 배치 전송
-
Linux 등 일부 OS에서
sendmmsg
및recvmmsg
와 같은 멀티 메시지 시스템 콜 지원 - 여러 데이터그램을 한 번에 송수신 하여, 오버헤드를 대폭 줄일 수 있음
단일 대용량 세그먼티드 데이터그램
- GSO(송신) , GRO(수신) 등 오프로드 기술로 큰 UDP 데이터그램을 OS 또는 NIC에서 자동으로 분할해 전송
- 네트워크 인터페이스가 패킷 단위로 분리, 체크섬 계산 및 헤더 붙이기를 처리
- 이를 통해 애플리케이션 단은 단 한 번의 시스템 콜만으로 다수의 실제 패킷 처리 가능
- GSO 활성화 상태에서는 Wireshark 등 일부 네트워크 툴의 패킷 분석 지원이 미흡
Firefox의 NSPR 대체 과정
- 우선 단일 데이터그램 송수신 구조에서 quinn-udp로 NSPR를 대체
- QUIC 구현체의 데이터그램 처리 파이프라인을 배치 송수신 및 세그먼테이션을 지원하도록 리팩터링
- multi-message 콜, segmentation offload 콜 모두 상황에 맞게 활용
- 플랫폼별 예외 처리 및 다양한 I/O 개선 기능이 추가됨
플랫폼별 세부 사항
Windows
- Windows는
WSASendMsg
/WSARecvMsg
를 제공, 전통적인 MTU 사이즈 데이터그램 또는 대형 세그먼티드 데이터그램 지원 - Linux의 GSO/GRO에 대응하는 Windows의 USO(송신) / URO(수신)
- 초기에는 단일 데이터그램 콜만 사용하여 문제 없었으나, URO 활성화 시 특정 환경(예: Windows on ARM + WSL)에서 QUIC 패킷 길이 판별 불가로 인해 페이지 로딩 실패 버그 발생
- USO/송신도 사용했으나, Firefox Windows 설치 환경에서 패킷 손실 증가 및 네트워크 드라이버 크래시 등 부작용 발견
- 현재 Firefox에서는 URO/USO 비활성화 상태 유지 및 추가 디버깅 진행 중
MacOS
- MacOS에서는 기존
sendto
/recvfrom
대신sendmsg
/recvmsg
로 quinn-udp 도입 - 세그멘테이션 오프로드 기능은 활성화되어 있지 않음
- 대신 공식 문서화되지 않은
sendmsg_x
/recvmsg_x
로 배치 전송 지원, quinn-udp에 비공식적으로 적용 - 애플이 향후 해당 호출을 제거할 수 있음에 따라, 기본 활성화 없이 테스트만 진행 후 실제 릴리즈에는 미포함
Linux
- sendmmsg/recvmmsg 및 GSO/GRO 모두 지원, quinn-udp는 송신 시 GSO를 기본 우선시함
- Firefox는 연결마다 UDP 소켓을 별도로 사용해 프라이버시 강화를 도모(4-tuple 구분)
- 이 구조에서는 세그멘테이션 오프로드의 이점이 극대화, sendmmsg/recvmmsg의 교차 전송 이점은 제한적
- 네트워크 샌드박스, 런타임 GSO 지원 체크 등 소규모 변경 외 어려움 없이 도입 성공
Android
- Android는 Linux와 달리 시스템 콜 처리 경로 및 보안 필터(예: seccomp)가 다름
- x86 기반 Android 5 등 매우 오래된 플랫폼의 지원 및,
socketcall
우회, 오류 처리 등 다양한 호환성 이슈 존재 - 일부 환경에서는 ECN 비트가 활성화된 송신 호출 시 오류(EINVAL) 발생, quinn-udp에서 재시도 및 옵션 해제 전략 적용
- Quinn 커뮤니티에서의 다양한 개선 이점 덕분에 Firefox도 자동으로 개선 효과를 누릴 수 있음
ECN(명시적 혼잡 알림) 지원
- 최신 시스템 콜 도입 덕분에 ancillary data(부가 데이터) 송수신 지원, QUIC ECN 지원 가능
- 소규모 버그는 있었으나, Firefox Nightly에서 절반 이상의 QUIC 연결이 ECN outbound 경로로 동작
- L4S 등 신기술이 부각됨에 따라 ECN 지원의 중요도와 활용성 상승
결론 요약
- Firefox의 QUIC UDP I/O 계층을 quinn-udp 기반 Rust 구현체로 교체, 성능과 보안성을 동시에 확보
- dated 시스템 콜 대신 각 OS별 최신 I/O 시스템 콜을 활용하여 처리량 향상 및 ECN 지원이 가능해짐
- Windows 등 일부 최적화 기능은 호환성 이슈로 추가 개선 필요
- QUIC 사용률이 지속적으로 늘어남에 따라, 앞으로도 OS/드라이버 수준의 지원이 계속 발전할 것임
Hacker News 의견
-
글의 핵심은 중간에 숨어 있음
극단적인 경우로 CPU 바운드 벤치마크에서 < 1Gbit/s에서 4 Gbit/s로 향상됨. CPU flamegraph상 대부분의 시간은 I/O 시스템 콜과 암호화 코드에서 소모됨
네트워크 처리량이 400% 증가했다는 점은 UDP 네트워크 트래픽에서 CPU 사용량이 크게 줄어듦을 의미함
이는 특히 모바일 및 노트북 등 휴대용 클라이언트에서 전력 효율 향상 측면에서도 인상적인 부분임
이런 전환이 항상 좋은 것으로 받아들여지는 분위기가 있지만, 이 글은 실제 데이터를 통해 입증해서 신선하게 느껴짐- 언젠가 유저와 시스템 프로그램 간의 컨텍스트를 넘나드는 메시지 패싱이 하드웨어 가속으로 돌아가는 날이 올지 궁금함
-
여기서 나온 개선은 실제 초고속(100Gb/s 이상)을 위해 필수이긴 하지만, 4Gb/s는 사실 별로 빠르지 않은 속도임
500MB/s라 이건 뭔가 심각하게 느린 병목 지점이 있다는 의미임
커널 컨텍스트 스위치가 1us대라는데 사실 시스템 콜 치고는 높은 편임
하지만 패킷당 평균 약 500바이트만 되도 500MB/s, 즉 4Gb/s 달성 가능함
예전 1Gb/s는 패킷 크기가 더 작았을 때였고, UDP 패킷을 단순히 NIC 버퍼에 밀어넣는 건 메모리 복사 속도로도 충분히 가능한 수준임
암호화가 느리다 해도 실제론 그렇지 않음
예를 들어 Intel i5-6500이 1729MB/s AES-128 GCM 속도를 낸 바 있음
지금 CPU는 코어당 3-5GB/s, 즉 25-40Gb/s도 가능한데 여기서 말한 4Gb/s는 너무 낮은 수치임
(AES-128 GCM 성능 참고 링크)-
시스템 콜 대기 시간이 높다 했는데, 그 원인은 spectre & meltdown 대응 조치 때문일 수 있음
TCP는 경로 바인딩이 있지만 UDP에는 없어 경로 설정에 차이가 큼
암호화가 느리다는 건, 작은 PDU(프로토콜 데이터 단위)에서는 맞는 말임
대부분이 대용량 TCP 프레임 기준으로 최적화 및 벤치마킹되어 실제로는 작은 패킷에서 상태 설정 비용이 두드러짐
tight loop에서 마이크로벤치마크 돌리면 수치가 잘 나오지만, 실제 랜덤성 있는 환경에서는 캐시 활용 효율도 낮아지고, 1KB 이하 패킷은 효율이 뚝 떨어짐
여기에 추가적인 프레이밍 오버헤드, 바깥 밴드 데이터 유효성 검사 등 꽤 비싸게 작동함
UDP 버퍼 메모리도 기본 값이 부족해 실사용엔 문제가 많음
TCP 운영하며 버퍼 크기 계속 키웠지만 UDP는 90~00년대 보수적인 값에 머물러 있음
진짜 필요한 API는, fd 포크해서 connect(2) 및 라우트 바인딩 완전 지원, 이후 submission queue 기반(uring, rio 등) 이어야 함
암호화 측면에서, KDF 접근 방식이 상태 비용을 많이 줄일 수 있음
PSP 방식은 일부 벤더가 인정하지만 IETF 등에서 많이 거부당해서 널리 퍼지지 않음
벤더들의 대규모 동시성 테스트에선 기존 TLS류보다 월등히 높은 스케일링 수치 나옴 -
벤치마크한 CPU가 어떤 등급인지는 전혀 언급 안 됨
그리고 암호화 오버헤드는 QUIC 프로토콜 자체 처리 비용임
QUIC은 암호화 오프로드(하드웨어 처리)가 TCP 대비 미흡한데, TCP는 kTLS 오프로드로 어느 정도 NIC 처리 가능함
-
-
이런 기술 콘텐츠 정말 만족스러웠음
Mozilla의 모든 기술 자료가 이렇게 실무 엔지니어가 제대로 작성한 깊이 있는 내용이었으면 좋겠음
낙관론(alegria) 따위 없이, 읽을 가치가 높음 -
왜 Android 5를 아직도 지원하는지 모르겠음
출시된 지 10년이 넘었고, 그 기기를 쓰는 유저는 더 오래된 레거시임
요즘 웹은 너무 무거워서 이런 구형 기기로는 제대로 브라우징하기조차 힘들 텐데 굳이 지원하는 이유가 궁금함
아마 옛날 OnePlus 같은 기기를 보수해서 충전기 꼽아두고 LineageOS 같은 국민 롬도 안 올리고 대체 앱스토어에서 파이어폭스 써보는 해커들 정도임
현실적으로는 전체 개발 속도를 늦추는 비용임 -
이슈 리포터(마찬가지로 Mozilla 직원)와 수많은 시간을 왔다 갔다 하다가, 결국 문제 재현을 위해 노트북까지 똑같은 모델, 색상으로 사서 테스트했다는 부분
네트워크 재현의 미친 현실을 XKCD 2259 에서처럼 보여줘서 재밌었음
(xkcd 만화 링크)-
"The map download struggle"라는 Factorio 개발 블로그에도 이와 관련된 흥미로운 에피소드가 있으니 추천함
(관련 블로그 글) -
실제로 네트워크 문제 다뤄 본 사람이라면 미스터리한 패킷 런트(mystery packet runts) 때문에 더 공감할 내용임
대부분의 네트워크 장비들이 이런 패킷 처리 잘 못함
UDP나 QUIC 기반 트래픽은 일정한 수준 이상의 대형 클라우드 환경이 아니면 쉽게 공격에 휘둘림
이 때문에 소규모 혹은 자체 운영하는 호스팅 업체는 점점 운영이 어려워지고, 대형 트래픽 처리 역량이 있는 곳만 남게 됨
그래서 대부분의 LAN 환경에선 UDP 트래픽 대부분을 드롭하고 필요한 부분만 레이트 리밋해서 처리 중임
-
-
모질라 버그 트래커
macOS와 Fedora에서 Cloudflare가 호스팅하는 사이트를 Firefox로 접속 시 여전히 같은 현상을 겪고 있음 -
Windows와 MacOS에도 GSO/GRO(대용량 네트워크 패킷 처리) 유사 기능이 있다는 사실을 이번에 알게 됐음
다만 실제론 버그가 많다니 아쉬움- Microsoft와 Apple이 왜 자기네 네트워크 스택 품질에 좀 더 신경을 안 쓰는지 의문임
GSO/GRO만 버그가 있는 게 아닐 거라는 생각도 듦
- Microsoft와 Apple이 왜 자기네 네트워크 스택 품질에 좀 더 신경을 안 쓰는지 의문임
-
UDP GSO/GRO가 구조적으로 어떻게 동작하는지 설명 가능한 사람 있냐는 질문임
UDP는 순서가 없는 패킷인데, QUIC 한 패킷이 여러 UDP 패킷으로 쪼개질 때 헤더에 시퀀스 정보도 없는데 수신 측에서 어떻게 순서 맞춰 합치는지 궁금함- 내가 파악한 바로는, 애플리케이션 레벨에서는 GRO가 활성화돼 있어도 사실상 합쳐진 UDP 데이터그램을 받는 일이 없음
커널이 여러 데이터그램을 하나의 구조체에 담아서, 각 계층끼리 경계(예, sk_buff 내 data fragments)를 유지한 채로 넘기는 개념임
정확한 전문가는 아니지만 이 방식이 어떻게 동작하는지 찾아보다가 이 글을 참고하게 됨
- 내가 파악한 바로는, 애플리케이션 레벨에서는 GRO가 활성화돼 있어도 사실상 합쳐진 UDP 데이터그램을 받는 일이 없음
-
"우리는 Quinn 프로젝트의 UDP I/O 라이브러리인 quinn-udp 위에서 개발을 시작했으며 이 덕에 개발 속도가 훨씬 빨라졌다"고 언급했는데
그럼 혹시 Quinn 프로젝트에 후원을 진행했는지 궁금함
(Quinn 후원 링크)-
재정 지원 관련해서 직접 물어봤더니, Mozilla의 Senior Principal Software Engineer가 "Mozilla는 돈이 없어요"라고 답변해줬음
다만 코드는 엄청나게 많이 기여해줘서 정말 감사하게 생각하고 있음
(나는 Quinn 메인테이너임) -
"후원했나요?"라는 질문에, 굳이 오픈소스 후원 안 해도 CEO 연봉에 수백만 달러 더 쓰는 게 Mozilla의 방식이라는 의견 제시함
플래그십 제품(Firefox)까지도 무너져가는데 말임 -
코드 등 다른 방식으로 기여한 점이 있다면 궁금함
-
-
sendmmsg/recvmmsg가 “최신”이라 불린다는 게 놀라움
사실 꽤 오랜 시간 존재한 시스템 콜임
Linux 관련 내용에서 io_uring도 언급될 줄 알았는데 없어서 아쉬움- io_uring은 여러 UDP 다이어그램을 한 번에 처리하는 진짜 의미의 배치 기능이 없음
최선을 다해도 sendmsg, recvmsg 여러 개를 한 번에 요청하는 것 정도임
GSO/GRO가 정답임
sendmmsg/recvmmsg는 이미 매우 오래된 기술이며, 커널 개발자들 중엔 이제 없애고 싶다는 사람도 있음
(관련 GitHub 토론)
- io_uring은 여러 UDP 다이어그램을 한 번에 처리하는 진짜 의미의 배치 기능이 없음