Zero Copy

Zero Copy는 Kafka가 높은 처리량을 달성하는 핵심 기술 중 하나입니다.

전통적인 데이터 전송

일반적인 파일 전송 과정

┌─────────────────────────────────────────────────────────────────┐
│                    Traditional Data Transfer                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Application          Kernel                    Hardware        │
│                                                                 │
│  ┌─────────┐         ┌─────────┐              ┌─────────┐      │
│  │ User    │  copy   │ Kernel  │    DMA      │  Disk   │      │
│  │ Buffer  │<────────│ Buffer  │<────────────│         │      │
│  └────┬────┘    ①    └─────────┘      ②      └─────────┘      │
│       │                                                         │
│       │ copy ③                                                  │
│       ▼                                                         │
│  ┌─────────┐         ┌─────────┐              ┌─────────┐      │
│  │ Socket  │  copy   │ Socket  │    DMA      │   NIC   │      │
│  │ Buffer  │────────>│ Buffer  │────────────>│         │      │
│  └─────────┘    ④    └─────────┘      ⑤      └─────────┘      │
│                                                                 │
│  총 4번의 복사 + 4번의 컨텍스트 스위칭                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

복사 횟수 상세

단계작업복사 종류
디스크 → 커널 버퍼DMA Copy
커널 버퍼 → 사용자 버퍼CPU Copy
사용자 버퍼 → 소켓 버퍼CPU Copy
소켓 버퍼 → NICDMA Copy

문제점:

  • CPU가 데이터 복사에 사용됨
  • 컨텍스트 스위칭 오버헤드
  • 메모리 대역폭 낭비

Zero Copy 전송

sendfile() 시스템 콜

┌─────────────────────────────────────────────────────────────────┐
│                      Zero Copy Transfer                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Application          Kernel                    Hardware        │
│                                                                 │
│  ┌─────────┐         ┌─────────┐              ┌─────────┐      │
│  │sendfile │         │ Kernel  │    DMA      │  Disk   │      │
│  │  call   │────────>│ Buffer  │<────────────│         │      │
│  └─────────┘         └────┬────┘      ①      └─────────┘      │
│                           │                                     │
│                           │ (no copy, just pointer)             │
│                           ▼                                     │
│                      ┌─────────┐              ┌─────────┐      │
│                      │ Socket  │    DMA      │   NIC   │      │
│                      │ Buffer  │────────────>│         │      │
│                      └─────────┘      ②      └─────────┘      │
│                                                                 │
│  총 2번의 DMA 복사, CPU 복사 없음                                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

개선 효과

항목전통적 방식Zero Copy
CPU 복사2번0번
DMA 복사2번2번
컨텍스트 스위칭4번2번
메모리 사용높음낮음

Kafka에서의 Zero Copy

Consumer Fetch 요청

┌─────────────────────────────────────────────────────────────────┐
│                   Kafka Zero Copy Flow                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Consumer                Broker                    Storage      │
│                                                                 │
│  ┌─────────┐         ┌─────────────────┐        ┌─────────┐   │
│  │  Fetch  │         │                 │        │  .log   │   │
│  │ Request │────────>│   Broker JVM    │        │  files  │   │
│  └─────────┘         │                 │        └────┬────┘   │
│                      │  transferTo()   │             │        │
│                      │  (sendfile)     │<────────────┘        │
│                      │                 │       Page Cache      │
│                      └────────┬────────┘                       │
│                               │                                │
│                               │ Zero Copy                      │
│                               ▼                                │
│  ┌─────────┐         ┌─────────────────┐        ┌─────────┐   │
│  │ Fetch   │<────────│      NIC        │<───────│  Socket │   │
│  │Response │         │                 │        │  Buffer │   │
│  └─────────┘         └─────────────────┘        └─────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Java FileChannel.transferTo()

// Kafka 내부에서 사용하는 Zero Copy 코드
FileChannel fileChannel = new FileInputStream(logFile).getChannel();
SocketChannel socketChannel = socket.getChannel();
 
// sendfile() 시스템 콜 사용
long transferred = fileChannel.transferTo(
    position,    // 시작 위치
    count,       // 전송할 바이트 수
    socketChannel // 대상 소켓
);

transferTo vs 일반 전송

// 일반적인 전송 (복사 발생)
byte[] buffer = new byte[8192];
while ((bytesRead = inputStream.read(buffer)) != -1) {
    outputStream.write(buffer, 0, bytesRead);
}
 
// Zero Copy 전송
fileChannel.transferTo(0, fileChannel.size(), socketChannel);

Zero Copy 조건

활성화 조건

Zero Copy가 사용되는 경우:
✓ Consumer가 메시지를 Fetch할 때
✓ Follower가 Leader로부터 복제할 때
✓ SSL/TLS가 비활성화된 경우

Zero Copy가 사용되지 않는 경우:
✗ SSL/TLS 암호화 사용 시 (데이터 암호화 필요)
✗ Producer가 메시지를 전송할 때 (역직렬화/검증 필요)
✗ 압축 해제가 필요한 경우

SSL과 Zero Copy

SSL 사용 시:
┌───────────┐    ┌───────────┐    ┌───────────┐
│   Disk    │───>│  Encrypt  │───>│    NIC    │
│           │    │  (CPU)    │    │           │
└───────────┘    └───────────┘    └───────────┘
                      ↑
               암호화로 인해
               Zero Copy 불가

Kafka 2.8+: SSL 환경에서도 부분적 Zero Copy 지원

성능 비교

벤치마크 결과

테스트 환경: 1GB 파일 전송

전통적 방식:
- 전송 시간: 100ms
- CPU 사용률: 60%
- 메모리 복사: 4GB (4번 복사)

Zero Copy:
- 전송 시간: 30ms
- CPU 사용률: 10%
- 메모리 복사: 0GB (DMA만 사용)

개선율: ~70% 전송 시간 감소, ~80% CPU 사용 감소

처리량 비교

┌─────────────────────────────────────────────────────────────────┐
│                      Throughput Comparison                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  전통적 방식:  ████████████████████                100 MB/s     │
│                                                                 │
│  Zero Copy:   ████████████████████████████████████ 300 MB/s    │
│                                                                 │
│  (네트워크 대역폭이 병목이 될 때까지)                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

OS 지원

Linux

# sendfile() 시스템 콜 지원 (커널 2.2+)
# 현대 리눅스에서 기본 지원
 
# splice() - 더 효율적인 대안 (커널 2.6.17+)
# 파이프를 통한 Zero Copy

커널 파라미터

# /etc/sysctl.conf
 
# TCP 버퍼 크기 최적화
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
 
# TCP 최적화
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_timestamps = 1

Kafka 설정

관련 설정

# Broker 설정
# 소켓 버퍼 크기
socket.send.buffer.bytes = 102400
socket.receive.buffer.bytes = 102400
socket.request.max.bytes = 104857600
 
# 전송 스레드 수
num.network.threads = 8
num.io.threads = 16

Zero Copy 확인

// Kafka 로그에서 확인
// DEBUG 레벨에서 transferTo 사용 여부 확인
 
log4j.logger.kafka.network = DEBUG

관련 기술

mmap (Memory-Mapped Files)

┌─────────────────────────────────────────────────────────────────┐
│                    Memory-Mapped I/O                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Application Memory                                             │
│  ┌──────────────────────────────────────────────┐              │
│  │     Virtual Address Space                     │              │
│  │     ┌────────────────────────────────────┐   │              │
│  │     │  Mapped Region (File Content)      │   │              │
│  │     └────────────────────────────────────┘   │              │
│  └──────────────────────────────────────────────┘              │
│           ↕ (Page Fault로 자동 로드)                             │
│  ┌──────────────────────────────────────────────┐              │
│  │              Page Cache                       │              │
│  └──────────────────────────────────────────────┘              │
│           ↕ (DMA)                                               │
│  ┌──────────────────────────────────────────────┐              │
│  │                  Disk                         │              │
│  └──────────────────────────────────────────────┘              │
│                                                                 │
│  Kafka 인덱스 파일(.index, .timeindex)에 사용                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

DMA (Direct Memory Access)

CPU 개입 없이 디바이스 간 직접 메모리 전송:

┌─────────┐         ┌───────────────┐         ┌─────────┐
│  Disk   │────────>│  DMA Engine   │────────>│ Memory  │
│         │         │ (CPU bypass)  │         │         │
└─────────┘         └───────────────┘         └─────────┘

Best Practices

1. SSL 사용 최소화

# 내부 트래픽은 PLAINTEXT 사용
listeners = PLAINTEXT://internal:9092,SSL://external:9093
 
# 외부만 SSL 사용

2. 적절한 배치 크기

// Producer
props.put("batch.size", "65536");  // 64KB 배치
 
// Consumer
props.put("fetch.min.bytes", "1048576");  // 1MB 최소 fetch

3. 네트워크 튜닝

# 대역폭에 맞는 버퍼 크기
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216

관련 문서