Intel DPDK

dpdk.png
홈페이지

1 개요

Intel을 중심으로 고성능 네트워크 장비를 개발하는 몇몇 회사들(WindRiver, 6Wind 등)이 함께 오픈소스(BSD 라이선스)로 개발하고 있는 고성능 네트워크 패킷 처리를 위한 I/O 드라이버 및 라이브러리 모음집이다.
일반적인 네트워크 드라이버는 운영체제에서 요구하는 이더넷 인터페이스에 맞추어 만들어지는데, DPDK는 운영체제의 도움을 받지 않고 오버헤드 없이 사용자 애플리케이션이 직접 하드웨어를 독점 제어할 수 있는 자체 인터페이스를 제공한다. Intel에서 만든 1/10/40 Gbps 네트워크 카드들과 Mellanox에서 만든 40/56 Gbps 네트워크 카드들, 그리고 여러 가상환경(AWS, Xen, VMWare 등)용 드라이버들이 기본으로 제공된다. 지원 NIC 목록 참조.
드라이버뿐만 아니라, 고속 패킷 처리를 구현할 때 유용하게 쓸 수 있는 x86 계열 CPU에 최적화된 메모리 관리 및 데이터구조 라이브러리들도 다수 포함하고 있다.

이렇게 만들어서 얻은 장점은 성능이다. 현재 알려진 여러 패킷 I/O 라이브러리 중 가장 뛰어난 성능을 보여주고 있다.
실제로, 리눅스나 윈도 시스템에서 10 Gbps 이상을 지원하는 네트워크 카드를 꽂고 기본 드라이버로 네트워크 애플리케이션을 돌려보면, 패킷 크기가 가장 크고 메모리에 이미 로드된 데이터를 스트리밍하는 최적 조건에서만 거의 최대 대역폭이 나오고 실제로는 10 Gbps에 훨씬 못 미치는 성능이 나올 때가 많다. 이런 문제를 해결하기 위해 개발된 것이니 당연히 성능이 잘 나올 수밖에 없다.
비슷한 목적과 구조를 채택한 주요 경쟁자로는 PF RING ZC (Zero-Copy), netmap, PFQ i/o 등이 있다. (다른 라이브러리 발견 시 추가 바람)

단점으로는 여러 사용자 애플리케이션(프로세스)가 하나의 네트워크 인터페이스를 서로 공유하기 어렵고, 기존의 소켓 API를 사용하는 네트워크 애플리케이션들은 모두 DPDK의 자체 API를 사용하도록 코드를 변경하고 TCP/IP에 해당하는 기능을 직접 만들어 써야 그 성능 이득을 누릴 수 있다는 점이다. DPDK 자체적으로 패킷 처리 파이프라인을 보다 쉽게 작성할 수 있도록 추가적인 API를 제공하고자 하는 시도(API 문서의 "packet framework" 항목 참조)가 있긴 하다. 또한, 역사가 있는 기존 운영체제에 비해 드라이버 종류가 적어 사용할 수 있는 네트워크 카드 모델이 제한된다는 것도 고려해야 한다.

2 특징

2.1 최적화된 데이터구조

DPDK 내부에는 여러 CPU 코어를 쓰는 프로그램을 위한 최적화된 데이터구조를 포함하고 있다. 특히, 다음의 3가지 컴포넌트가 가장 핵심이 되며, 다른 컴포넌트들은 이 컴포넌트들을 내부적으로 활용하여 작성된 경우(예: mbuf)가 많다.

  • memseg, memzone: 연속된 메모리 영역을 미리 사용자 프로세스에 할당·고정하고 그 영역을 다시 쪼갤 수 있는 형태로 만들어준다. DPDK 내부 구현에서 주로 사용하고, DPDK를 사용하는 입장에서는 별로 쓸 일이 없다.
  • lock-free ring: producer와 consumer가 동시에 데이터를 추가하거나 꺼내갈 수 있는 원형 큐(circular queue).
  • memory pool: 임의 크기의 메모리를 할당하는 대신, 미리 큰 영역의 메모리를 할당해놓고 고정된 단위(element) 크기로 잘라서 쓰는 메모리 관리자. 고속 패킷 처리 과정에서는 malloc의 범용성이 오히려 성능 저하를 가져오기 때문에 사용한다. 대표적인 사용처는 네트워크 카드로부터 받은 패킷을 저장하는 것으로 최대 패킷 크기 x 시스템이 한번에 처리할 수 있는 최대 패킷 개수 같은 방식으로 메모리를 할당해두고 사용한다.

DPDK의 메모리 관리자 API들은 모두 명시적으로 NUMA node 번호를 지정할 수 있는데 그 이유는 하나의 메인보드에 여러 개의 CPU를 꽂는 서버급 시스템들에서 사용자가 메모리 접근을 최적화할 수 있도록 도와주기 위한 것이다. 그런 시스템들에서 각 CPU와 각 CPU마다 할당된 RAM 슬롯들은 하나의 node라고 부르는데, 어느 한 CPU가 다른 node의 메모리에 접근하려고 하면 그 node의 주인 CPU를 거쳐야 하기 때문에 추가적인 지연시간이 발생하게 된다.[1] 따라서 각 CPU에서 사용하는 메모리 영역을 명시적으로 제한할 필요가 생기는데 메모리 할당 과정에서 이때 node 번호를 직접 지정할 수 있게 한 것이다.[2]

2.2 사용자 공간 드라이버

DPDK는 사용자 애플리케이션이 운영체제를 거치지 않고 직접 하드웨어를 제어할 수 있게 해준다. 이를 위해 PCI 장치들을 나열하고 지원되는 네트워크 카드가 있는지 검사해주는 작은 커널모듈(혹은 드라이버)을 탑재하였고, 실제 PCI 장치를 제어하는 드라이버는 모두 사용자 공간에서 동작하는 라이브러리로 되어 있다. Linux 환경에서는 이미 개발되어 있는 userspace I/O (UIO) 기술을 활용한다.

DPDK의 드라이버 모델은 표준 PCI 장치를 가정하고 있는데, 이는 100 Mps / 1 Gbps급 이상의 네트워크 카드들이 거의 모두 PCI 인터페이스를 이용하기 때문이다. PCI 인터페이스의 DMA 기능(memory-mapped I/O)을 사용하면, 미리 인터페이스와 매핑된 특정 메모리 영역을 read/write를 함으로써 네트워크 카드를 제어하거나 패킷 입출력을 할 수 있다. 특히, 커널을 거치지 않기 때문에 커널 공간과 사용자 공간 경계를 넘나들 때 메모리 복사를 할 필요가 없어 높은 대역폭을 내는 데 더 유리하다.[3]

DPDK 드라이버들은 기본적으로 poll-mode로 동작하여 매번 패킷이 들어왔는지 확인하는 busy-wait 루프를 돌도록 되어 있다. 일반적인 프로그래밍을 할 땐 할일이 없을 때도 CPU를 과다 사용하므로 피하는 방법이지만, 고속 패킷 처리 프로그램에서는 지연시간을 마이크로초 단위로 줄여야 하기 때문에 CPU를 더 써서라도 이렇게 하는 경우가 많다. 하지만 DPDK도 2.1 버전부터는 interrupt-mode를 추가하여 사용자가 필요에 따라 적절한 방식을 택할 수 있게 하였다(v2.1 release note).

3 응용

PC와 같은 네트워크 말단보다는, 네트워크 중간에서 패킷을 열어보는 장비들(라우터, 스위치, 방화벽 등)을 소프트웨어로 구현하기 위해 사용한다. 특히 이런 장비들은 네트워크 속도가 10G/40G 이상으로 올라가면서 초당 수백만~수천만 개의 패킷을 처리해야 하는데, 이를 위해 극단적으로 낮은 지연시간을 구현해야 하고 따라서 하드웨어를 직접 제어하는 DPDK와 같은 드라이버·라이브러리 패키지들을 사용하는 것이다.

원래는 이런 성능 요구사항 때문에 네트워크 장비들을 전용 ASIC 칩이나 FPGA 같은 특수한 하드웨어를 사용해서 구현했지만, 점점 하드웨어 기술이 좋아지면서 x86 같은 범용 CPU로도 이론 상 수십 Gbps급의 네트워크 패킷 처리가 가능하게 되었다. 즉, 더 싼 하드웨어로 비용을 낮추면서 범용 소프트웨어(예: Verilog 대신 Linux와 C/C++ 환경)를 이용해 개발을 더 쉽게 할 수 있게 된 것이다.

대표적인 응용 분야는 다음과 같다.

  • 로드밸런서 : 다중화 등 장애 대응을 유연하게 하고 장비 추가나 제거를 쉽게 하기 위해서 로드밸런서 또한 소프트웨어 시스템을 이용하는 경우가 많다.
  • white-box 장비 제조사들 : Cisco와 같이 자체 개발한 칩이 아니라, 상용으로 판매되는 통신칩에 자체 개발한 Linux 기반 OS를 얹은 값싼 네트워크 장비들을 만드는 곳에서 사용하기도 한다.
  • 방화벽 개발 : 방화벽은 네트워크 프로그램 중에서도 특히 탐지 규칙의 복잡성이나 실시간 업데이트 등의 요구사항이 중요하기 때문에 하드웨어보다는 소프트웨어로 개발하는 경우가 많다.
  • 네트워크 모니터링 시스템 : 패킷들을 열어보고 사용 중인 프로토콜이나 애플리케이션의 통계를 내서 중앙 서버로 전달하는 장비도 소프트웨어로 만들 때가 많다.
  • 금융권 : high frequency trading. 거래소와의 지연시간이 돈을 버는 데 직접적인 영향력을 행사하기 때문에, 전용선을 까는 것도 모자라 아예 네트워크 스택 자체를 DPDK로 최적화한 소프트웨어를 쓰기도 한다.

4 한계

4.1 네트워크 프로토콜 지원 미비

DPDK는 말 그대로 "패킷"을 직접 입출력하기 때문에, 그 패킷을 가지고 무엇을 할 것인가는 온전히 사용자의 몫이다. 즉, TCP/IP와 같은 상위 레이어들에서 제공하는 여러 기능들(패킷 손실 발생 시 재전송, 헤더의 포트 번호를 보고 특정 핸들러에게 전달하기, 체크섬 보고 패킷 내용 검증, 쪼개진 패킷들 다시 합치기 등등)을 모두 손으로 구현해야 한다. DPDK에서 이런 기능을 쉽게 구현할 수 있게 도와주려고 IP/TCP/UDP 헤더 정의 정도는 제공해주고 있지만, 이건 운영체제 독립적으로 코드를 작성할 수 있게 하기 위한 것에 가깝고 실질적인 기능은 다 직접 만들어야 한다.
다행히, 정말로 처음부터 다 혼자 만들 필요는 없도록 라이브러리화한 구현체를 공개한 곳들이 있으니 잘 찾아보자. Rump TCP/IP stack

4.2 표준 하드웨어 인터페이스의 부재

하드디스크나 SSD와 같은 "저장 장치"의 경우에는 IDE/SATA/NVMe와 같이 이미 잘 표준화된 하드웨어 규격이 존재하기 때문에 해당 규격을 구현한 표준 드라이버만 있으면 제조사에 관계 없이 모든 모델을 바로 플러그앤플레이하여 사용할 수 있다. (독자들도, OS가 깔리지 않은 여분의 하드디스크를 SSD로 새로 바꾸거나, 다른 제조사의 하드디스크로 교체했다고 해서 갑자기 운영체제에 새로운 드라이버를 설치해야 하는 경우는 못봤을 것이다.)

하지만 네트워크 카드는 이런 표준이 없기 때문에 각각의 모델별로 완전히 다른 구현을 가진 전용 드라이버가 필요하다. 단지 우리가 이걸 잘 인식하지 못하는 것은 리눅스, 맥, 윈도와 같은 범용 운영체제에서 일반 PC에 들어가는 네트워크 카드들에 대한 드라이버를 이미 대부분 탑재하고 있기 때문이다. (예: 리눅스에는 기본적으로 약 100종의 드라이버가 내장됨)

그러다보니, DPDK와 같은 새로운 드라이버 모델이 나오면 각 네트워크 카드 별로 드라이버를 새로 만들어야 한다. (이는 사실 DPDK뿐만 문제가 아니라, netmap이나 PF RING 등 유사 라이브러리들도 모두 가지고 있는 문제이기도 하다.) 따라서 DPDK는 주로 Intel 및 몇몇 제조사들의 드라이버만 지원하고 있고, 만약에 자신이 사용하는 네트워크 카드가 DPDK에서 지원되지 않는다면 DPDK의 빠른 성능을 누릴 수 없다.
  1. 그래서 NUMA의 약자는 non-uniform memory access이다. 같은 포인터라도 자신이 어느 CPU냐에 따라 메모리 접근 성능이 달라질 수 있다는 뜻.
  2. 일반 응용프로그램에서는 운영체제에서 그 프로그램의 쓰레드가 어느 CPU에서 돌고 있느냐에 따라 그 프로그램이 새로 할당하는 메모리를 자동으로 해당 node의 것으로 정해주는데, 메모리를 할당·초기화하는 시점과 그 메모리를 사용하는 시점이 다른 (보통 메모리를 미리 다 할당·초기화해놓고 재사용만 하는) 고속 패킷 처리 프로그램에서는 이렇게 직접 지정할 수 있는 기능이 필수적이다.
  3. 네트워크 드라이버에서 보통 zero-copy라고 홍보하는 경우가 이걸 말하는 것이다.