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 |
| ④ | 소켓 버퍼 → NIC | DMA 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 = 1Kafka 설정
관련 설정
# Broker 설정
# 소켓 버퍼 크기
socket.send.buffer.bytes = 102400
socket.receive.buffer.bytes = 102400
socket.request.max.bytes = 104857600
# 전송 스레드 수
num.network.threads = 8
num.io.threads = 16Zero 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 최소 fetch3. 네트워크 튜닝
# 대역폭에 맞는 버퍼 크기
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
댓글 (0)