티스토리 뷰

들어가기에 앞서 

일반적인 서버의 목적은 클라이언트 요청에 대해 적절한 응답을 빠르고 효율적으로 반환하는 것입니다.
즉, 대부분의 서버는 다음과 같은 단순한 I/O 루틴을 반복합니다.

"입력(요청)을 받고 → 출력(응답)을 보낸다."

 
예를 들어, 웹 서버는 클라이언트의 요청에 따라 정적 파일(HTML, 이미지 등)을 전송하고,
WAS(Web Application Server)는 DB에서 읽어온 사용자 데이터를 API 응답으로 보내며,
FTP 서버는 파일을 반환합니다.
 
이러한 동작들은 모두 단순한 코드로 표현할 수 있습니다.

read(fd, buffer, size);
send(socket, buffer, size);

하지만 이 단순한 동작 이면에서는
여러 번의 데이터 복사사용자 공간(user space)과 커널 공간(kernel space)을 오가는 컨텍스트 스위치가 반복되며, CPU는 의미 없는 복사 작업에 소모되고 있습니다.

따라서 이 글에서는 다음 내용을 정리했습니다.

1. 이 단순한 read() + send() 호출이 내부적으로 어떻게 작동하는지
2. 왜 이 방식이 비효율적인지
3. 그리고 어떻게 이를 개선할 수 있는지 (zero-copy)

목차

1. 기존 방식의 데이터 전송 흐름
2. zero-copy 방식의 데이터 전송 흐름
3. Java 코드를 통한 성능 비교 및 분석
4. zero-copy 실제 예시: Kafka 코드 분석
5. 요약
6. 참고 자료


1. 기존 방식의 데이터 전송 흐름

좌: 기존 데이터 복사 방식 / 우: 기존 데이터 전송 컨텍스트 스위칭


서버는 클라이언트에게 응답을 보내기 위해 "파일에서 읽은 데이터를 네트워크로 전송"합니다.
이 단순한 작업처럼 보이는 과정은 다음과 같은 데이터 복사 과정을 거칩니다.

디스크 → 커널(Read buffer) → 사용자 (Application buffer) → 커널(Socket buffer) → 네트워크(NIC buffer)

즉, 한 파일을 건네줄 때 네 번이나 각각의 버퍼에 복사를 하는 것입니다. (CPU 개입은 최소 2번, 최대 4번:DMA copy가 안되는 경우)

 
NIC
Network Interface Card. 데이터를 실제 네트워크로 전송하는 하드웨어입니다.
 
DMA (Direct Memory Access)
CPU의 개입 없이 주변 장치(NIC, 프린트 등)와 메모리 사이의 데이터를 전송해주는 하드웨어 장치입니다.
디스크에서 데이터를 읽을 때 CPU 대신 DMA가 데이터를 복사하므로 효율적입니다.
예를 들어, 디스크에서 읽은 데이터를 DMA가 커널 버퍼에 직접 적재하거나, NIC가 커널 버퍼에서 직접 읽어 전송할 수 있습니다.
 

기존 방식의 지원 시스템 콜 및 메서드

  • UNIX/Linux: read(), send()
  • Java NIO: FileChannel.read(), SocketChannel.write()

기존 방식의 데이터 전송 흐름

read() 또는 send()가 호출되면, 커널은 다음과 같은 방식으로 동작합니다.
 

1. read() 호출 → 사용자 → 커널 모드 전환

    1.1. 사용자가 read() 호출 → 운영체제가 커널 모드로 진입 (1차 컨텍스트 스위치)
    1.2. 내부적으로 sys_read() 시스템 콜 수행
    1.3. DMA 엔진이 디스크 → Read Buffer (커널 공간)로 데이터 복사 (1차 복사)
 

2. 커널 버퍼 → 사용자 버퍼로 복사 

    2.1. 커널은 Read Buffer의 내용을 Application Buffer (사용자 공간) 으로 복사 (2차 복사)
    2.2. read()가 반환되며 커널 → 사용자 모드 복귀 (2차 컨텍스트 스위치)

 
3. send() 호출 → 사용자 → 커널 모드 전환

    3.1. 사용자가 send() 호출 → 운영체제가 다시 커널 모드로 진입 (3차 컨텍스트 스위치)
    3.2. 내부적으로 sys_sendto() 또는 sys_write() 시스템 콜 수행
    3.3. Application Buffer의 데이터를 Socket Buffer (커널 공간)로 복사 (3차 복사)
 

4. send() 반환 → 커널 → 사용자 모드 복귀

    4.1. DMA 엔진이 Socket Buffer → NIC Buffer (네트워크 카드)로 데이터 복사 (4차 복사)
    4.2. send()가 반환되며 커널 → 사용자 모드 복귀 (4차 컨텍스트 스위치)

기존 방식의 데이터 전송 흐름 정리

구분복사 주체복사 위치설명
1차 복사 / 컨텍스트 스위칭DMA (디스크 → 메모리)디스크 → 커널 Read Bufferread() 내부 DMA 복사
2차 복사 / 컨텍스트 스위칭CPU커널 Read Buffer → 사용자 Buffer사용자 접근 가능하게
3차 복사 / 컨텍스트 스위칭CPU사용자 Buffer → 커널 Socket Buffer네트워크 전송 준비용
4차 복사 / 컨텍스트 스위칭DMA (메모리 → NIC)커널 Socket Buffer → NIC 전송 버퍼실제 네트워크 전송 단계

 

중간 커널 버퍼(Read / Socket buffer), 왜 있는 걸까?

처음 보면, “그냥 디스크에서 바로 사용자 공간 혹은 네트워크로 보내면 더 빠른 거 아닌가?”라는 의문이 생깁니다.
이 질문은 절반은 맞고, 절반은 틀립니다.
중간 버퍼는 단순한 중간 저장소가 아니라, 다양한 상황을 고려한 성능 최적화 장치입니다.

  • 예를 들어, 사용자가 1KB만 읽으려 했는데, 디스크에서는 4KB 단위로 읽는 게 더 빠른 경우가 많습니다.
    이때 커널은 남은 3KB를 미리 읽어 Read Buffer에 저장해두고, 다음 요청에서 빠르게 꺼내쓸 수 있습니다.
     이를 read-ahead cache라고 합니다.
  • 또, send()가 호출되었을 때, 네트워크 상태가 좋지 않다면 즉시 전송이 어려울 수 있습니다.
    이때 커널은 데이터를 Socket Buffer에 담아두고, 네트워크가 준비되면 백그라운드에서 전송을 진행합니다.
    즉, 사용자는 send() 호출이 될 때 까지 기다릴 필요 없이 다른 작업을 수행할 수 있습니다.
    이를 Non-blocking I/O라고 합니다.

이처럼 커널이 중간 버퍼를 사용하는 이유는 단순한 낭비가 아닙니다.

  • Read Buffer는 read-ahead cache로 사용되어, 요청량보다 더 많은 데이터를 미리 읽어  디스크 I/O 효율을 높이고
  • Socket Buffer는 데이터를 비동기적(Non-blocking I/O)으로 전송할 수 있게 하여, 애플리케이션의 응답성을 높입니다.
    즉, 중간 커널 버퍼는 시스템 전체 I/O 성능을 높이기 위한 설계입니다.
     

하지만 문제는,

처리해야 할 데이터가 커질수록 이 설계가 오히려 병목의 원인이 된다는 점입니다.

  • 디스크 → 커널 → 사용자 → 커널 → 네트워크로 이어지는 복사 과정에서 CPU는 매 단계마다 데이터를 복사해야 합니다.
  • 그 과정에서 사용자 모드 ↔ 커널 모드 간의 컨텍스트 스위치가 누적되고, 이는 곧 성능 저하로 이어집니다.

이 때 등장하는 해결책이 바로 Zero-Copy 입니다.


2. Zero-Copy 방식의 데이터 전송 흐름

기존 방식의 데이터 전송 한계

디스크에서 데이터를 읽고 네트워크로 전송하는 일반적인 I/O 방식은 다음과 같은 과정을 거칩니다.

  1. 디스크에서 커널 공간(Read Buffer)으로 데이터 복사
  2. 커널 공간 → 사용자 공간으로 복사
  3. 사용자 공간 → 커널(Socket Buffer)로 복사
  4. 커널(Socket Buffer) → NIC(네트워크 카드)로 전송

이처럼 최대 4번의 메모리 복사4회의 컨텍스트 스위치가 발생합니다. 특히, 이 중 2회는 CPU가 직접 메모리를 복사해야 하므로 대용량 전송 시 CPU 사용률이 급증하고, 전체 시스템의 I/O 성능이 병목에 직면하게 됩니다.

이러한 병목을 해결하기 위해 도입된 방식이 바로 zero-copy입니다. 이름 그대로 복사(사용자 공간에)를 하지 않는 I/O, 즉 CPU가 데이터를 직접 복사하지 않고 DMA 엔진과 커널 내부 포인터 전송만으로 처리하는 방식입니다.
 

zero-copy 방식

 
 
Zero-Copy 핵심 아이디어: 사용자 애플리케이션은 실제로 데이터를 가공하지 않고, 디스크에서 읽어 네트워크로 넘길 뿐이라면, 굳이 사용자 공간을 거칠 필요가 없다.

Zero-Copy 지원 시스템 콜 및 메서드

  • UNIX/Linux: sendfile(out_fd, in_fd, offset, count)
  • Java NIO: FileChannel.transferTo(position, count, targetChannel)

이 시스템 콜은 내부적으로 커널의 sendfile() 기능을 호출하며, 데이터를 사용자 공간으로 복사하지 않고 커널 내에서 직접 전송하도록 합니다.

Zero-Copy 데이터 전송 흐름

sendfile() 또는 transferTo()가 호출되면, 커널은 다음과 같은 방식으로 동작합니다.

💡 참고
기존 방식에서 설명한 read buffer와 socket buffer는 커널 공간에 존재하는 각각의 I/O 버퍼입니다.
반면, zero-copy 방식에서는 read buffer 대신 커널의 page cache를 활용하며, socket buffer를 생략하고 NIC가 직접 page cache를 참조하도록 설계됩니다.
즉, zero-copy는 중간 복사 버퍼 없이, 커널 내부의 페이지 캐시에서 바로 네트워크 카드로 데이터가 전달됩니다.

 

1. sendfile() 호출 → 커널 모드 진입

    1.1. 사용자가 sendfile() 호출 → 운영체제가 커널 모드로 진입 (1차 컨텍스트 스위치)
    1.2. 내부적으로 sys_sendfile() 시스템 콜 수행
    1.3. DMA 엔진이 디스크 → Page Cache (커널 공간)로 데이터 복사 (1차 복사)

기존 방식과 동일하게, 디스크에서 데이터를 읽는 과정은 DMA가 수행합니다.
하지만 이후 사용자 공간으로 복사하지 않고, 커널 내부에서 처리가 진행됩니다.

 

2. 커널 내부에서 전송 포인터 설정

    2.1. 커널은 페이지 캐시의 데이터 주소를 소켓 전송 경로에 포인터 형태로 등록
    2.2. 데이터 자체는 복사하지 않으며, NIC가 직접 접근할 수 있도록 메모리 매핑 처리

이 과정은 커널 내부에서만 동작하며, CPU나 사용자 공간은 관여하지 않습니다.

 

3. NIC로 DMA 전송
 

    3.1. NIC(네트워크 카드)는 포인터를 따라 메모리에서 데이터를 직접 읽음
    3.2. DMA 엔진이 커널 → NIC로 데이터 전송 (2차 복사)

이 과정도 DMA가 수행하며, CPU는 메모리 복사에 개입하지 않습니다.

 

4. sendfile() 반환 → 커널 → 사용자 모드 복귀

    4.1. 전송 완료 후 sendfile()이 반환되고, 커널 → 사용자 모드로 복귀 (2차 컨텍스트 스위치)

Zero-Copy 데이터 전송 흐름 정리

구분복사 주체복사 위치설명
1차 복사 / 컨텍스트 스위치DMA디스크 → 커널 페이지 캐시디스크로부터 데이터 읽기
2차 복사 / 컨텍스트 스위치DMA커널 페이지 캐시 → NIC사용자 공간을 거치지 않고, 포인터 참조를 통한 직접 전송


Zero-Copy 이점

1. CPU 부하 최소화

  • 기존 방식: CPU가 사용자 공간 ↔ 커널 공간 사이의 복사 작업을 직접 수행
  • Zero-Copy: DMA와 포인터 전달만으로 처리 → CPU는 복사 작업에서 완전히 배제됨

결과적으로, 전송량이 많아질수록 CPU 부하 차이가 크게 발생함


2. 메모리 대역폭 절감

  • 기존 방식: 총 4회 복사 (디스크 → 커널 → 사용자 → 커널 → NIC)
  • Zero-Copy: 2회 복사만 (디스크 → 커널 → NIC), (DMA만 사용)

메모리 대역폭을 덜 사용하고, 캐시 미스 비용도 줄일 수 있음

 
3. 컨텍스트 스위치 감소

  • 기존 방식: read/send 호출마다 최대 4회 컨텍스트 스위치
  • Zero-Copy: 2회만 발생 (sendfile 호출 진입/복귀)

컨텍스트 전환이 줄어들면 시스템 호출 오버헤드도 줄어듦

 
4. 네트워크 처리 성능 향상

  • 파일을 수정하지 않고 그대로 전송하는 상황이라면, 최소한의 리소스로 처리 가능
  • 특히 정적 파일 서버, 미디어 스트리밍, 대규모 WAS에서 눈에 띄는 성능 향상

 

Zero-Copy가 항상 사용되지 않는 이유

1. 용도 제한 (데이터 처리 X, 전송만 O)

Zero-Copy는 “가공 없이 전송만 하는” 경우에만 이점이 있습니다. 즉, 데이터를 중간에서 분석, 압축, 암호화, 변환하는 경우에는 어차피 사용자 공간으로 가져와야 하므로 Zero-Copy를 쓸 수 없습니다.

예: WAS에서 요청 본문을 읽어 JSON 파싱하거나 DB 쿼리를 수행하는 경우 → Zero-Copy 불가


2. API 제약 및 적용 어려움

  • sendfile() 같은 시스템 콜은 입출력 대상이 디스크와 소켓이어야만 동작합니다. 즉, 사용자 코드에서 데이터 내용을 직접 다루는 경우(예: BufferedReader.readLine()), 커널이 데이터를 사용자 공간으로 전달해야만 하므로 Zero-Copy를 쓸 수 없습니다.
  • Java의 transferTo()도 내부적으로 sendfile()을 쓰지만, JVM 구현/OS에 따라 Zero-Copy가 실제 적용되지 않을 수도 있습니다.

3. 운영체제 및 하드웨어 의존성

  • 일부 커널 버전에서는 버그나 제한이 있어 기대한 성능이 나오지 않을 수 있습니다.
  • NIC(Network Interface Card) 가 Zero-Copy를 지원하지 않거나, 특정 DMA 기능이 제한되어 있을 수 있습니다.

4. 메모리 매핑 실패 및 리소스 제약

  • 커널이 파일 페이지를 소켓 전송 버퍼에 바로 연결하려면 page-aligned된 메모리가 필요합니다.
  • 조건이 맞지 않으면 fallback(기존 복사 방식)으로 돌아가게 되며, 이 과정에서 예상치 못한 성능 저하가 발생할 수 있습니다.

5. 디버깅 및 로깅 어려움

Zero-Copy는 사용자 공간을 거치지 않으므로,

  • 중간에 데이터를 들여다보거나 수정하는 게 어렵습니다.
  • 로깅/디버깅 목적으로 데이터를 확인하려면 사용자 공간에 복사해서 따로 처리해야 합니다.

기존 방식 vs zero-copy 방식 비교 정리

항목기존 방식zero-copy 방식
데이터 복사 횟수최대 4회2회 (모두 DMA)
사용자 공간 복사O (CPU 관여)✕ (생략됨)
컨텍스트 스위치4회2회
CPU 부하높음낮음
용도소량 처리, 로직 개입대용량 전송, 정적 파일 처리 등

 
Zero-Copy는 단순히 메모리 복사를 줄이는 기법이 아닙니다.
그 핵심은 CPU 개입을 최소화하고, 메모리 대역폭 사용을 줄이며, 컨텍스트 스위치 비용까지 줄이는 시스템 자원 최적화 전략입니다.
특히 다음과 같은 상황에서 뛰어난 성능을 발휘합니다.

  • 수정 없이 그대로 전송되는 대용량 정적 파일
  • 동영상 등 실시간 스트리밍 서버
  • 정적 리소스가 많은 웹 서버 및 웹 애플리케이션 서버(WAS)

이처럼 파일을 읽고 전송만 하면 되는 구조에서는 기존 read/send 방식보다 CPU 사용률과 전송 지연을 현저히 낮출 수 있습니다


3. Java 코드를 통한 성능 비교 및 분석

코드 링크

파일 크기기존 방식 (ms)Zero-Copy 최초 (ms)Zero-Copy 캐시 후 (ms)성능 향상캐싱 성능 향상
10MB13138차이 미미약 38% 향상
50MB322513약 22% 향상약 59% 향상
100MB604622약 23% 향상약 63% 향상
250MB13710042약 27% 향상약 69% 향상
500MB33523472약 30% 향상약 78% 향상
1GB770526113약 32% 향상약 85% 향상
 

분석 1. 파일 크기가 커질수록 Zero-Copy의 장점이 커진다

파일 크기가 커질수록 기존 방식과 Zero-Copy 방식의 성능 차이는 점점 벌어집니다. 특히 500MB 이상의 파일에서는 기존 방식 대비 최대 30% 이상 빠른 전송 성능을 보여줍니다. 이는 기존 방식이 사용자 공간을 거쳐 데이터를 복사하기 때문에 복사 비용 + 컨텍스트 스위치 비용이 누적되지만, Zero-Copy는 커널 내에서 복사 없이 소켓으로 직접 전송하기 때문입니다.

 

분석 2. 반복 전송 시, 페이지 캐시가 적용된다.

Zero-Copy는 최초 전송 시에는 디스크 I/O 비용이 발생하지만, 이후 동일한 파일을 다시 전송할 때는 페이지 캐시에 적재된 데이터를 활용해 디스크 접근 없이 바로 전송합니다.

즉, 전송 속도는 디스크 속도가 아니라 메모리 속도 + 네트워크 대역폭에 의해 결정됩니다.

 

1GB 파일의 경우,

  • 기존 방식: 770 ms
  • Zero-Copy 최초 전송: 234 ms
  • Zero-Copy 반복 전송: 72ms (페이지 캐시)
    CPU 복사 없이, 디스크 접근도 없이 빠르게 처리됨을 알 수 있습니다.

Zero-Copy에서 페이지 캐싱이 효과적인 이유

  1. 최초 요청 시: 디스크에서 데이터를 읽어 메모리(페이지 캐시)에 적재 → 느림
  2. 반복 요청 시: 이미 캐시에 올라와 있는 데이터를 그대로 사용 → 빠름
  3. Zero-Copy는 커널 페이지 캐시에서 직접 네트워크로 전송 가능
    → 사용자 공간으로 복사하지 않기 때문에 CPU 자원 소모 없이 고속 전송 가능

 
대부분의 서버 I/O는 단순해 보이지만, 그 이면에서는 수많은 복사와 전송이 CPU 자원을 소모합니다.
Zero-Copy는 다음과 같이 불필요한 오버헤드를 줄여, 더 적은 CPU 자원으로 더 많은 네트워크 전송량을 처리할 수 있게 해줍니다.

  • CPU 사용량 감소
  • 디스크 접근 최소화
  • 파일 크기 증가에도 높은 전송 효율 유지

실제로 Nginx, Apache 등 고성능 서버는 이미 sendfile()을 통해 Zero-Copy를 적극적으로 활용하고 있습니다.


4. zero-copy 실제 예시: Kafka 코드 분석

Kafka는 초당 수십만 건의 메시지를 처리할 수 있는 고성능 분산 로그 시스템입니다.
이런 성능의 핵심에는 Zero-Copy 기반의 파일 I/O 최적화가 숨어 있습니다.
 

Kafka의 저장 구조: 로그 기반 메시지 저장

Kafka는 데이터를 디스크에 로그 파일(Segment) 형태로 저장합니다.
생산자(Producer)로부터 받은 메시지를 디스크에 append-only 방식으로 기록하고,
소비자(Consumer)는 이 로그 파일에서 데이터를 읽어 갑니다.


이때 Kafka는 단순히 파일을 읽어 소비자에게 전달하지 않습니다.
운영체제의 sendfile()을 활용하여 디스크에서 커널 페이지 캐시를 거쳐, 네트워크 소켓으로 직접 전송합니다.
 
카프카 구현 코드를 깃허브 코드에서 직접 확인할 수 있습니다.

Kafka Github 링크

Kafka 핵심 구성 요소

FileRecords – 실제 로그 파일을 관리하는 클래스

Kafka가 디스크에 저장한 메시지를 전송할 때 사용하는 주요 클래스입니다.
여기서 writeTo() 메서드는 데이터를 전송 대상으로 보내기 위한 사전 처리(offsets 계산, ...)를 수행합니다.

public class FileRecords extends AbstractRecords implements Closeable {
    private final int start;
    private final int end;

    private final FileChannel channel;

    @Override
    public int writeTo(TransferableChannel destChannel, int offset, int length) throws IOException {
        long position = start + offset;
        int count = Math.min(length, oldSize - offset);
        return (int) destChannel.transferFrom(channel, position, count);
    }
}
  • FileChannel channel은 Java NIO의 추상 클래스입니다.
  • destChannel은 Kafka 내부의 TransferableChannel 인터페이스 구현체입니다.
  • 핵심 호출: destChannel.transferFrom(...)
    → 실제 전송 로직은 TransferableChannel 구현체에 따라 달라집니다.
TransferableChannel 구현체 종류


DefaultRecordsSend - 실제 전송 수행 클래스

public class DefaultRecordsSend<T extends TransferableRecords> extends RecordsSend<T> {
    @Override
    protected int writeTo(TransferableChannel channel, int previouslyWritten, int remaining) throws IOException {
        return records().writeTo(channel, previouslyWritten, remaining);
    }
}
  • Kafka 네트워크 계층에서 실제로 호출되는 전송 시작점입니다.
  • 내부적으로 FileRecords.writeTo(...) → TransferableChannel.transferFrom(...) 호출로 연결됩니다.
  • 즉, 실제 Zero-Copy 사용 여부는 전송 채널이 무엇이냐에 따라 결정됩니다.

TransferableChannel의 구현체 중 ByteBufferChannel, SslTransportLayer에 대해 살펴보겠습니다.
 

TransferableChannel 구현체

1. ByteBufferChannel

public class ByteBufferChannel implements TransferableChannel {
    @Override
    public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
        return fileChannel.transferTo(position, count, this); // Java NIO의 sendfile 기반 Zero-Copy
    }
}

ByteBufferChannel은 내부적으로 Java NIO의 transferTo를 호출하여 커널 페이지 캐시에서 소켓으로 직접 전송하는 zero-copy 기법을 활용하는 사실을 확인할 수 있습니다.

 

2. SslTransportLayer - 사용자 공간 복사 + 암호화

public interface TransportLayer extends ScatteringByteChannel, TransferableChannel {
	// ...
}

public class SslTransportLayer implements TransportLayer {

    @Override
    public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
        ...
        while (totalBytesWritten < totalBytesToWrite) {
            if (!fileChannelBuffer.hasRemaining()) {
                fileChannelBuffer.clear();
                fileChannel.read(fileChannelBuffer, pos);  // 디스크에서 사용자 공간으로 읽기
                fileChannelBuffer.flip();
            }
            int networkBytesWritten = write(fileChannelBuffer); // 아래 write()에서 암호화 + 전송
            ...
        }
    }
    
    @Override
    public int write(ByteBuffer src) throws IOException {
        ...
        SSLEngineResult wrapResult = sslEngine.wrap(src, netWriteBuffer);  // 사용자 공간에서 암호화
        socketChannel.write(netWriteBuffer);  // 암호화된 데이터를 전송
        ...
    }
}

SslTransportLayer에서는 zero-copy를 사용할 수 없습니다.
왜냐하면, SSL 암호화 과정은 반드시 사용자 공간으로 데이터를 가져온 후, SSLEngine.wrap() 호출로 암호화 후 다시 소켓에 기록해야 하기 때문입니다.

SslTransportLayer 데이터 흐름

디스크(FileChannel)
   ↓   read()
사용자 공간 (fileChannelBuffer)
   ↓   SSLEngine.wrap()
암호화된 사용자 공간 버퍼 (netWriteBuffer)
   ↓   socketChannel.write()
네트워크 소켓

 

요약

Kafka는 같은 FileRecords 객체를 전송하더라도, TransferableChannel 구현체에 따라 전송 전략이 달라집니다.


5. 요약

기존 데이터 전송 방식 흐름

  • 디스크 → 커널(Read Buffer) → 사용자 공간 → 커널(Socket Buffer) → NIC
  • 최대 4번의 메모리 복사4회의 컨텍스트 스위치
  • 특히 CPU가 사용자 공간 ↔ 커널 공간 복사에 직접 개입 → 부하 발생

Zero-Copy 데이터 전송 방식의 흐름

  • sendfile() 또는 Java NIO의 FileChannel.transferTo() 사용 시
  • 디스크 → 커널 페이지 캐시 → NIC 로 바로 전송
  • 사용자 공간을 완전히 생략, 복사는 오직 DMA에 의해 2번만 수행
  • CPU 부하, 메모리 대역폭, 컨텍스트 스위치 모두 감소

Zero-Copy의 이점

  • CPU 부하 감소
    → 복사 작업을 DMA가 대신 수행하여 CPU 개입 최소화
  • 메모리 복사 횟수 절감
    → 기존 방식: 4회 복사 (디스크 → 커널 → 사용자 → 커널 → NIC)
    → Zero-Copy: 2회 복사 (디스크 → 커널 → NIC)
  • 컨텍스트 스위치 최소화
    → 기존 방식: 최대 4회 컨텍스트 스위치
    → Zero-Copy: 2회 컨텍스트 스위치
  • 전송 성능 향상
    → 정적 파일 서버, 대용량 로그, 미디어 스트리밍 환경에서 효과적

 

Zero-Copy의 한계

  • 가공/분석이 필요한 데이터엔 부적합
    → 압축, 암호화, JSON 파싱 등에는 사용자 공간이 필요함
  • 사용 가능한 API 제한
    → sendfile()은 입출력 대상이 디스크 → 소켓(파일 디스크럽터)만 가능
    → 또한, 일부 JVM/OS에선 미지원
  • 운영체제/하드웨어 의존성
    → NIC나 DMA 기능, 커널 버전 따라 성능 차이 발생
  • 정렬 제약으로 fallback 가능성
    → 메모리가 page-aligned되지 않으면 Zero-Copy가 자동 비활성화됨
  • 디버깅/로깅 어려움
    → 사용자 공간을 거치지 않아 중간 데이터 확인이 어려움

Java 코드 성능 비교 및 분석

  • 파일 크기가 커질수록 Zero-Copy의 이점이 두드러짐
  • 특히 반복 전송 시, 페이지 캐시 + Zero-Copy의 조합으로 성능 향상 두드러짐
  • 페이지 캐싱된 파일은 디스크 I/O 없이 바로 전송 가능

Kafka 코드 분석

  • Kafka는 전송 추상화를 위해 TransferableChannel 인터페이스를 사용
  • TransferableChannel 구현체인 ByteBufferChannel은 Zero-Copy(transferTo) 기반 전송을 수행
  • TransferableChannel 구현체인 SslTransportLayer는 보안을 위해 사용자 공간 복사 + 암호화로 인해 Zero-Copy 불가
  • 즉, Kafka는 TransferableChannel 구현체에 따라 전송 방식을 선택

6. 참고 자료

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함