Differential Dataflow는 데이터의 변화를 차이(Difference)의 축적으로 표현하고, 변경분만 전파하여 증분 계산을 수행합니다. 이 증분 계산이 단일 머신에서 동작할 때는 공유 메모리를 통해 워커(Worker) 간 데이터를 교환하면 됩니다. 하지만 데이터가 수십억 건으로 늘어나고, 워커를 수십 대의 머신에 분산 배치해야 하는 시점이 오면, 네트워크가 개입합니다. 델타 (data, time, diff) 튜플이 머신 경계를 넘어야 하고, 각 워커의 진행 상태(Frontier)가 클러스터 전체에 전파되어야 합니다.

Timely Dataflow[1]는 이 분산 실행을 가능하게 하는 데이터플로우 런타임이고, Differential Dataflow[2]는 그 위에서 증분 계산의 논리를 담당합니다. 문제는 이 두 시스템의 네트워크 계층입니다. Rust 구현체는 워커 간 통신에 TCP를 사용하는데, TCP 경로에서 메시지 하나당 7번의 메모리 복사가 발생하고, 그 중 4번은 본질적으로 불필요합니다[3]. 수신 경로(ProcessBinary::pre_work)에서 전체 메모리 할당의 99.6%가 집중되며, CPU 프로파일의 상위를 _platform_memmove가 차지합니다. 데이터플로우의 계산 논리가 아무리 정교해도, 전송 계층이 병목이 되면 시스템 전체의 지연 시간은 전송 계층이 결정합니다.

그렇다면 이미 존재하는 고성능 전송 프로토콜을 가져다 쓰면 되지 않을까요? SRT, QUIC, Aeron, KCP — 모두 UDP 기반의 저지연 프로토콜입니다. 하나씩 살펴보았습니다.

기존 프로토콜이 맞지 않는 이유

SRT: 라이브 영상 전송의 정석

SRT(Secure Reliable Transport)는 Haivision이 공개한 라이브 영상 전송 프로토콜입니다[4]. UDT에서 파생되어, 불안정한 공용 인터넷 위에서도 MPEG-TS 스트림을 안정적으로 전달하는 데 탁월한 성능을 보여줍니다. NACK 기반 ARQ와 지연 한계 내의 재전송, AES 암호화까지 — 라이브 미디어 전송이라는 도메인에서 SRT의 설계는 매우 정교합니다.

그러나 SRT가 풀고자 하는 문제와 Differential Dataflow의 문제는 성격이 다릅니다. SRT의 핵심 메커니즘인 TLPKTDROP은 지연 한계(기본 120ms)를 초과한 패킷을 폐기합니다. 라이브 방송에서 늦은 프레임은 의미가 없으므로 이것은 올바른 트레이드오프입니다. 반면 Differential Dataflow의 델타 튜플 (data, time, diff)모든 변경분의 완전한 전달을 전제로 합니다. 가중 집합(Z-set)의 정확성이 이에 달려 있기 때문입니다. “늦더라도 반드시 도착해야 하는” 데이터와 “늦으면 버려야 하는” 데이터 — 이 두 요구는 근본적으로 양립하기 어렵습니다.

또한 SRT의 페이로드는 MPEG-TS에 맞춘 1,316바이트인 반면, Differential Dataflow의 델타 레코드는 평균 50~500바이트 수준입니다. CBR(상수 비트레이트) 페이싱 역시 입력 배치와 반복 경계에 따라 폭발적으로 발생하는 델타 트래픽과는 다른 리듬입니다. SRT의 강점이 빛나는 영역과 Differential Dataflow가 필요로 하는 영역이 겹치지 않는 것입니다.

QUIC: 웹을 위한 프로토콜

QUIC는 HTTP/3의 전송 계층을 목표로 탄생한 프로토콜입니다[5]. 스트림 멀티플렉싱, TLS 1.3 통합, 헤드오브라인 블로킹 해소 — 브라우저와 CDN 사이의 요청-응답 패턴에서 탁월한 성능을 보여줍니다.

하지만 데이터센터 내부의 워커 메시(Worker Mesh)에 적용하려 하면, QUIC의 설계 전제가 이 환경과 맞지 않는다는 점이 드러납니다. QUIC는 TLS 1.3을 프로토콜에 내장하고 있으며(RFC 9001), 이를 비활성화할 수 없습니다. 신뢰할 수 있는 클러스터 내부 통신에서 레코드당 22바이트의 AEAD 오버헤드와 AES-GCM 셋업 비용(레코드당 ~200ns)은 불필요한 비용이 됩니다.

혼잡 제어 역시 인터넷 경로를 전제로 두고 있습니다(RFC 9002). 초기 RTT 추정치가 333ms인데, 데이터센터 내부 RTT는 0.1ms, FPGA PCIe 경로는 0.001ms입니다. 3,330배에서 333,000배의 과대 추정이며, 새 연결마다 처음 333ms 동안 혼잡 제어가 현실을 반영하지 못합니다.

스트림 관리의 비용도 무시하기 어렵습니다. 64개 워커 × 20개 오퍼레이터 구성에서는 80,640개의 동시 스트림이 필요하고, 스트림당 약 4KB의 상태를 유지하면 전송 계층만으로 315MB의 메모리를 소비합니다. QUIC가 해결하고자 한 웹 트래픽의 문제와, 데이터플로우 워커 메시의 문제는 규모와 방향이 다릅니다.

Aeron: 메시징을 위한 프로토콜

Aeron은 금융 거래 시스템을 위한 고성능 메시징 프레임워크입니다. 미디어 드라이버(Media Driver)라는 별도 프로세스가 공유 메모리 링 버퍼를 통해 애플리케이션과 통신하며, Sender·Receiver·Conductor 세 스레드가 busy-spinning으로 동작합니다.

성능은 인상적입니다. IPC 경로에서 1μs 미만의 지연을 달성합니다. 하지만 Differential Dataflow의 관점에서 보면 구조적 불일치가 있습니다.

Aeron은 발행-구독(Pub/Sub) 모델입니다. Differential Dataflow가 필요로 하는 것은 포인트-투-포인트 델타 스트리밍과 프론티어(Frontier) 전파입니다. Timely Dataflow에서 프론티어란, 더 이상 특정 시점 이전의 메시지가 도착하지 않을 것임을 알려주는 진행 신호입니다[1]. 이 신호는 데이터 메시지보다 우선순위가 높아야 합니다 — 프론티어가 지연되면 파이프라인 뒤쪽의 모든 연산이 대기 상태에 빠지기 때문입니다. Aeron에는 이런 우선순위 레인이 없습니다.

또한 미디어 드라이버는 인스턴스당 3개의 전용 CPU 코어를 소비합니다. 16개 워커를 운영하면 전송 인프라만으로 48코어가 필요합니다. 이것은 프로토콜이 아니라 미들웨어의 비용입니다.

KCP: 게임을 위한 프로토콜

KCP는 순수 C 2,000줄 미만으로 구현한 경량 ARQ 프로토콜입니다. 공격적인 재전송으로 게임 및 실시간 애플리케이션에서 낮은 지연을 달성합니다. C 구현이라는 점에서 처음에는 가장 가능성 있어 보였습니다.

하지만 KCP는 단일 스트림 프로토콜입니다. Differential Dataflow의 K개 데이터 채널에 K개의 독립적인 ikcpcb 인스턴스가 필요하며, K=1,000일 때 ikcp_update() 호출만으로 초당 200ms의 CPU 시간을 소비합니다. 메시지당 3~4회의 메모리 복사가 발생하고, 이중 링크드 리스트 탐색과 동적 메모리 할당은 FPGA 파이프라인으로 합성할 수 없는 구조입니다.

공통적으로 빠진 것

네 프로토콜 모두에서 공통적으로 부재한 것은 부분 순서 타임스탬프(Partially Ordered Timestamp)에 대한 인식입니다.

Timely Dataflow에서 ‘시간’이란 0, 1, 2로 증가하는 단순한 번호가 아닙니다. (epoch, iteration_0, iteration_1, ...)처럼 여러 차원이 중첩된 격자(Lattice) 구조이고, 이 격자 위에서 “어느 시점까지 처리가 끝났는가”를 추적합니다. 그런데 SRT든 QUIC든 Aeron이든 KCP든, 이들이 아는 시간이란 단조 증가하는 시퀀스 번호가 전부입니다. 전송 계층이 부분 순서 타임스탬프와 프론티어를 모르면, 결국 애플리케이션 계층에서 이 논리를 전부 다시 구현해야 합니다. 기존 프로토콜 위에 사실상 새로운 프로토콜을 하나 더 올리는 꼴입니다.

요구사항 SRT QUIC Aeron KCP 필요
정확한 전달 (Exactly-once) TLPKTDROP 위반 O O O 필수
부분 순서 타임스탬프 X X X X 필수
프론티어 우선 전파 X X X X 필수
적응적 배칭 CBR 페이싱 X X X 필수
제로카피 DMA X X IPC만 X 권장
FPGA 합성 가능 X X X X 권장
멀티캐스트 X X O X 권장
클러스터 내 혼잡 제어 인터넷 인터넷 제한적 없음 권장

구현 언어는 왜 C인가

새로운 프로토콜을 구현한다면, 그 언어는 C여야 합니다. Differential Dataflow의 정식 구현체가 Rust인 점을 고려하면 다소 역설적으로 들릴 수 있습니다. 하지만 이 프로토콜은 애플리케이션 로직이 아니라 전송 인프라이고, 전송 인프라의 최종 목표는 FPGA 가속입니다.

커널 모드가 아닌 유저 모드

FPGA 가속의 전제는 커널 우회(Kernel Bypass)입니다. 리눅스 커널 네트워크 스택은 패킷당 두 번의 컨텍스트 스위치, 스케줄러 지터, 캐시 오염을 수반합니다. FPGA가 400ns 만에 패킷을 처리해도, 커널 경유 시 20μs의 왕복 지연이 가속의 이점을 전부 상쇄합니다.

네트워크 스택 p50 지연1 커널 TCP 대비
커널 TCP (syscall) 20,000 ns 기준
커널 UDP (syscall) 15,000 ns −25%
io_uring (배치) 8,000 ns −60%
AF_XDP (제로카피) 3,500 ns −82.5%
DPDK (폴 모드) 1,200 ns −94%
DPDK + FPGA SmartNIC 400 ns −98%
순수 FPGA (NIC 위) 100 ns −99.5%

DPDK의 폴 모드 드라이버(PMD)는 커널을 완전히 우회하여 유저 모드에서 패킷을 처리합니다. 여기에 FPGA SmartNIC을 결합하면 p50 400ns를 달성합니다. 이 모든 것이 유저 모드에서 동작해야 하는 이유입니다.

C와 FPGA의 공생

그리고 유저 모드 네트워킹에서 C가 유일한 선택인 이유는 HLS(High-Level Synthesis) 때문입니다.

FPGA 벤더의 HLS 툴체인 — Xilinx Vivado/Vitis HLS, Intel HLS Compiler — 은 C 코드를 직접 하드웨어(RTL)로 합성합니다. 동일한 C 함수를 소프트웨어에서 실행할 수도 있고, #pragma HLS PIPELINE 한 줄을 추가하면 FPGA 로직으로 변환할 수도 있습니다.

// 소프트웨어에서 실행 가능하고, 동시에 FPGA로 합성 가능한 패킷 파서
void parse_delta_header(const uint8_t *buf, delta_hdr_t *hdr) {
    #pragma HLS PIPELINE II=1
    hdr->epoch     = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
    hdr->iteration = (buf[4] << 8)  | buf[5];
    hdr->diff_type = buf[6];
    hdr->count     = buf[7];
}

C++은 HLS 툴체인이 부분적으로만 지원하고(가상 함수, RTTI, 예외 불가), Rust는 어떤 HLS 툴체인도 지원하지 않습니다. Go는 가비지 컬렉터가 DMA 핀 메모리를 재배치할 수 있어 원천적으로 불가능합니다.

기준 C C++ Rust Go
HLS 합성 완전 지원 부분 지원 미지원 미지원
ABI 안정성 표준 (System V) 불안정 불안정 불안정
DMA 메모리 레이아웃 packed struct vtable 간섭 fat pointer GC 재배치
DPDK/libibverbs 통합 네이티브 FFI FFI CGo
런타임 의존성 없음 언와인더 언와인더 GC

DPDK, SPDK, libibverbs, libfabric, XDMA, QDMA, OpenNIC — FPGA/네트워크 생태계의 주요 라이브러리 10개 모두 C로 구현하고 있습니다. 이것은 관성이 아니라, ABI 안정성과 HLS 합성이라는 구조적 요구가 만들어낸 결과입니다.

핵심은 이것입니다: 동일한 C 소스 파일이 (1) 소프트웨어 레퍼런스 구현, (2) HLS 합성 입력, (3) DPDK PMD 통합 계층, (4) FPGA와 공유하는 DMA 디스크립터 정의를 동시에 수행합니다. 이 네 가지 역할을 하나의 언어로 충족할 수 있는 것은 C뿐입니다.

레이어 분할 설계

잠깐 스케치해 본 레이어 분할 설계 아이디어입니다. 5개 레이어로 나누고, 각 레이어를 독립적으로 테스트할 수 있게 하면서 하위 레이어부터 점진적으로 FPGA로 오프로드하는 구조를 생각하고 있습니다.

L5: Dataflow Binding Layer Rust FFI ↔ C 라이브러리 바인딩 · timely-dataflow 통합 L4: Delta Batch Layer 델타 직렬화/역직렬화 · 적응적 배칭 · 제로카피 DMA 매핑 L3: Progress Layer 프론티어 추적 · 포인트스탬프 브로드캐스트 · 타임스탬프 인코딩 L2: Channel Layer 멀티플렉싱 · 배압(Backpressure) · 진행 채널 우선순위 L1: Wire Layer UDP 송수신 · 패킷 프레이밍 · DPDK PMD / FPGA SmartNIC FPGA offload →

L1: Wire Layer — 선(線) 위의 바이트

가장 낮은 계층입니다. UDP 소켓 또는 DPDK PMD를 통해 원시 패킷을 송수신합니다. 이 계층의 핵심 설계 원칙은 드라이버 교체 가능성입니다.

typedef struct {
    int (*send)(void *ctx, const uint8_t *buf, size_t len);
    int (*recv)(void *ctx, uint8_t *buf, size_t max_len);
    int (*flush)(void *ctx);
} wire_ops_t;

소프트웨어 개발 초기에는 sendmsg()/recvmsg() 기반의 커널 UDP 드라이버를 사용하고, 성능이 필요한 시점에서 DPDK PMD 드라이버로 교체하며, 최종적으로 FPGA SmartNIC(Xilinx QDMA, Intel IPU)의 DMA 링으로 전환합니다. 상위 계층의 코드는 한 줄도 바꿀 필요가 없습니다.

패킷 프레이밍은 16바이트 고정 헤더를 사용합니다. 64바이트 델타 레코드 기준으로 20%의 오버헤드이며, 이것은 SRT(38.5%)나 Aeron(38.5%), KCP(33.3%)보다 효율적입니다. 다만 암호화는 아직 고려하지 못한 부분입니다. SRT의 AES-CTR이나 QUIC의 TLS 1.3처럼 전송 계층 수준의 암호화를 어떻게 통합할지는 별도의 설계가 필요합니다.

typedef struct __attribute__((packed)) {
    uint32_t epoch;         // 외부 시점
    uint16_t iteration;     // 내부 반복 인덱스
    uint8_t  channel_id;    // 멀티플렉싱 채널
    uint8_t  flags;         // PROGRESS | DATA | ACK | COMPACT
    uint32_t seq_num;       // 채널별 시퀀스 번호
    uint16_t payload_len;   // 페이로드 길이
    uint16_t checksum;      // CRC-16 (FPGA 오프로드 대상)
} wire_hdr_t;               // 16 bytes, FPGA 레지스터 맵과 1:1 대응

__attribute__((packed))로 선언한 이 구조체는 FPGA의 레지스터 맵과 바이트 단위로 정확히 일치합니다. HLS로 합성하면 클럭 사이클당 하나의 헤더를 처리하는 패킷 파서 파이프라인을 얻습니다.

L2: Channel Layer — 채널의 분리

데이터플로우 그래프에서 오퍼레이터 사이의 각 에지(Edge)가 하나의 논리 채널이 됩니다. 이 계층은 하나의 물리적 연결 위에서 다수의 논리 채널을 멀티플렉싱하고, 진행 채널(Progress Channel)이 데이터 채널보다 항상 우선하도록 스케줄링합니다.

이것이 Aeron이나 QUIC의 스트림 멀티플렉싱과 다른 점입니다. 일반적인 멀티플렉싱은 공정성(Fairness)을 추구하지만, Timely Dataflow에서 프론티어 메시지가 늦으면 파이프라인 뒤쪽의 모든 연산이 멈춥니다. 따라서 진행 채널은 엄격한 우선순위(Strict Priority)를 가져야 합니다.

배압(Backpressure)도 타임스탬프 단위로 작동합니다. 수신 측이 “시점 $t$의 델타는 아직 처리 중이니 잠깐 멈춰 달라”고 요청하면, 전송 측은 해당 시점의 델타만 보류합니다. 나머지 시점의 데이터는 영향 없이 계속 흐릅니다. TCP처럼 바이트 수로 흐름을 조절하는 것이 아니라, 데이터의 의미를 기준으로 흐름을 조절하는 셈입니다.

L3: Progress Layer — 진행의 언어

이 계층이 기존 프로토콜과의 가장 본질적인 차별점입니다. 부분 순서 타임스탬프를 전송 프로토콜의 일급 시민(First-class Citizen)으로 다룹니다.

Naiad 논문[1]의 진행 추적 프로토콜에서, 워커는 포인트스탬프(Pointstamp)의 발생 카운트(Occurrence Count)와 선행 카운트(Precursor Count)를 관리하며, 변경 사항을 모든 피어에게 브로드캐스트합니다. 64개 워커 기준으로 한 번의 프론티어 갱신이 63개의 브로드캐스트 메시지를 발생시킵니다 — $O(N^2)$의 통신 복잡도입니다.

이 계층에서 프론티어 계산 자체를 FPGA로 오프로드하면 극적인 효과가 있습니다. 250MHz FPGA에 16개의 병렬 비교기를 배치하면 초당 약 40억 회의 포인트스탬프 비교가 가능합니다[6]. 일반적인 워크로드가 초당 약 100만 회의 비교를 요구하는 점을 고려하면, 4,000배의 여유가 생깁니다. CPU는 프론티어 계산에서 완전히 해방되어 본래의 작업 — 오퍼레이터 로직 실행 — 에 집중할 수 있습니다.

L4: Delta Batch Layer — 델타의 포장

Differential Dataflow의 델타 배치는 이중 모드(Bimodal) 크기 분포를 보입니다. 초기 적재 시에는 수백만 개의 (data, time, diff) 트리플을 한꺼번에 만들어내지만, 증분 업데이트 시에는 1~100개 수준에 그칩니다.

이 계층의 핵심은 적응적 배칭(Adaptive Batching)입니다. 부하가 낮을 때는 델타를 즉시 전달하여 지연을 최소화하고, 부하가 높을 때는 델타를 합쳐서(Coalesce) 처리량을 극대화합니다. 결정 기준은 두 가지입니다: (1) 큐 깊이가 임계값을 초과하는가, (2) 마지막 전송 이후 경과 시간이 타이머를 초과하는가.

컴팩션(Consolidation) — 동일 키에 대한 diff를 합산하고, 합이 0인 엔트리를 제거하는 연산 — 도 이 계층에서 수행합니다. 일반적인 워크로드에서 컴팩션은 엔트리의 30~80%를 제거합니다. 이 연산은 스트리밍 병합 정렬과 누산(Accumulation)의 조합으로, 데이터 의존적 분기가 없는 고정된 데이터플로우 파이프라인입니다 — FPGA 합성에 이상적인 패턴입니다.

직렬화는 Timely Dataflow가 사용하는 Abomonation[7] 스타일의 제로카피 방식을 따릅니다. 구조체의 메모리 레이아웃이 곧 와이어 포맷이 되도록 packed struct를 설계하면, 직렬화 비용은 0에 수렴합니다. C의 __attribute__((packed))가 필요한 이유가 바로 여기에 있습니다.

L5: Dataflow Binding Layer — 두 세계의 접점

최상위 계층은 C로 작성된 전송 라이브러리를 Rust의 timely-dataflow 크레이트와 연결합니다. Rust FFI(extern "C")를 통해 C 함수를 호출하고, C ABI의 안정성 덕분에 양쪽이 독립적으로 버전을 올릴 수 있습니다.

// Rust 측: timely-dataflow의 Communication 트레이트 구현
extern "C" {
    fn tdd_channel_open(peer: u32, channel: u8) -> *mut TddChannel;
    fn tdd_channel_send(ch: *mut TddChannel, buf: *const u8, len: usize) -> i32;
    fn tdd_frontier_advance(ch: *mut TddChannel, epoch: u32, iter: u16) -> i32;
}

이 바인딩 계층만 Rust로 작성하고, 나머지 L1~L4는 모두 C로 구현합니다. 소프트웨어 모드에서 충분히 검증한 뒤 L1(패킷 파싱, 체크섬)부터 L3(프론티어 계산), L4(컴팩션)까지 점진적으로 FPGA로 내려보내는 상향식 하드웨어 오프로드 전략을 취합니다.

FPGA 오프로드 로드맵 time → Phase 1 — L1 오프로드 패킷 파싱 CRC 검증 시퀀스 번호 처리 → CPU가 원시 패킷을 볼 필요 없음 Phase 2 — L3 오프로드 포인트스탬프 비교 프론티어 안티체인 계산 멀티캐스트 → CPU가 진행 추적에서 완전 해방 Phase 3 — L4 오프로드 스트리밍 컴팩션 (병합 정렬 + 누산 + 영(0) 제거) → 델타가 이미 압축된 상태로 호스트에 도착 L1 L3 L4 CPU 부하 감소 패킷 처리 + 진행 추적 + 컴팩션 각 Phase는 독립 배포 가능 · 이전 Phase 없이도 소프트웨어 폴백 동작

마치며

SRT는 영상을 위해, QUIC는 웹을 위해, Aeron은 메시징을 위해, KCP는 게임을 위해 만든 프로토콜입니다. 각각의 도메인에서는 훌륭한 프로토콜입니다. 하지만 Differential Dataflow가 요구하는 것 — 부분 순서 타임스탬프, 프론티어 우선 전파, 적응적 델타 배칭, 의미론적 배압 — 은 이 프로토콜들의 설계 공간(Design Space)에 존재하지 않습니다.

기존 프로토콜 위에 이 기능들을 쌓으려면 결국 전송 계층 대부분을 다시 구현하게 됩니다. 그렇다면 처음부터 Differential Dataflow의 의미론에 맞춰 설계하는 편이 더 깨끗합니다. 그리고 그 구현이 C여야 하는 이유는 감상적인 것이 아닙니다. FPGA HLS 툴체인이 합성할 수 있는 유일한 범용 언어가 C이고, 유저 모드 네트워킹 생태계 전체가 C ABI 위에 서 있기 때문입니다.

Differential Dataflow는 “무엇이 바뀌었는가”를 계산의 출발점으로 삼습니다. 이 프로토콜은 같은 질문을 네트워크에도 던집니다. 프론티어가 어디까지 진행했는지, 타임스탬프의 순서가 어떻게 구성되어 있는지를 전송 계층이 직접 이해하면, 애플리케이션이 매번 바이트 배열로 변환하고 다시 해석하는 과정이 필요 없어집니다.


참고 문헌

  1. D. G. Murray, F. McSherry, R. Isaacs, M. Isard, P. Barham, and M. Abadi, “Naiad: A Timely Dataflow System,” in Proceedings of the 24th ACM Symposium on Operating Systems Principles (SOSP ’13), ACM, 2013, pp. 439–455. Available at: https://sigops.org/s/conferences/sosp/2013/papers/p439-murray.pdf</span>
  2. F. McSherry, D. G. Murray, R. Isaacs, and M. Isard, “Differential Dataflow,” in Proceedings of the 6th Biennial Conference on Innovative Data Systems Research (CIDR ’13), CIDR, Jan. 2013.</span> [Link]
  3. F. McSherry, “Minimize copies – GitHub Issue #111.” 2019. Available at: https://github.com/TimelyDataflow/timely-dataflow/issues/111</span>
  4. M. P. Sharabayko, M. A. Sharabayko, J. Dube, and J. Kim, “SRT: The Secure Reliable Transport Protocol,” IETF, Internet-Draft draft-sharabayko-srt-01, 2020. Available at: https://datatracker.ietf.org/doc/draft-sharabayko-srt/</span>
  5. J. Iyengar and M. Thomson, “QUIC: A UDP-Based Multiplexed and Secure Transport,” RFC Editor, RFC 9000, May 2021. Available at: https://www.rfc-editor.org/rfc/rfc9000</span>
  6. B. Li et al., “ClickNP: Highly Flexible and High Performance Network Processing with Reconfigurable Hardware,” in Proceedings of the ACM SIGCOMM 2016 Conference, ACM, 2016, pp. 1–14.</span>
  7. F. McSherry, “Abomonation: A mortifying serialization library for Rust.” 2015. Available at: https://github.com/TimelyDataflow/abomonation</span>

관련 글

  1. p50은 전체 측정값을 오름차순으로 정렬했을 때 50번째 백분위수(중앙값)에 해당하는 지연 시간입니다. 예를 들어 p50이 1,200ns라면, 요청의 절반은 1,200ns 이내에 처리된다는 뜻입니다.