카프카는 데이터를 저장할 때 메모리가 아니라 디스크를 활용함으로써, 별도의 설정 없이도 데이터의 영속성을 보장합니다.
따라서 서버에 장애가 나도 메세지가 디스크에 저장되어 있으므로 유실될 걱정이 없습니다.
또한 데이터가 디스크에 저장될 때 순차적으로 저장되기에 디스크 I/O 가 줄어들어 성능이 빠르다는 장점이 있습니다.
사실 기존 메시징 시스템에서 파일 시스템은, 메시지의 영속성을 위해서 성능 저하를 감수하면서도 어쩔 수 없이 사용해야하는 애물단지 같은 존재였습니다.
디스크는 원하는 데이터가 위치한 블록을 찾기 위한 시간(seek time), 블록을 메모리에 카피하는 시간 등의 오버헤드가 존재합니다. 데이터가 캐시나 메모리에 이미 존재하면 이 과정은 생략될 수 있지만, 그렇지 않을 경우 이러한 동작이 반복적으로 필요합니다. 따라서 디스크는 일반적으로 느리다고 인식됩니다.
하지만 카프카는 이런 편견을 깨고 파일 시스템을 메시지의 주 저장소로 사용하면서도, 기존의 메시징 시스템보다 뛰어난 성능을 보여주는데요. 어떻게 이러한 구현이 가능했을까요?
카프카는 3가지 방법을 활용하였습니다.
1. 디스크의 Sequential I/O (순차적인 입출력) 활용
2. 페이지 캐시 전략 활용
3. 제로 카피 기법 도입
디스크 I/O는 어떻게 사용하는지에 따라 느릴수도 있고 빠를 수도 있는데요. 아래 그림을 통해 디스크에 순차적 접근 속도는 디스크 랜덤 엑세스에 비해 150,000배 빠르고, 메모리에 랜덤 액세스하는 것보다도 속도가 빠른 것을 확인하실 수 있습니다.

디스크의 읽기 속도는 실제 데이터를 읽는 속도가 아니라, 데이터가 위치한 블록을 찾기 위한 시간 (seek time) 에 의해 지연됩니다. 카프카는 순차적 접근을 통해, seek time 을 최소화할 수 있습니다.
이는 카프카가 데이터를 로그(segments)로 저장하기 때문에 가능한 일입니다.
로그는 한 번 쓰여지면 변하지 않는 성질, 즉 immutable 한 특성을 지니고 있습니다. 특히 카프카의 데이터는 컨슈머가 읽어도 지워지지 않고 지속적으로 추가됩니다.
이로 인해 카프카의 데이터는 디스크에 조각(fragments) 으로 나뉘어 저장되지 않고, 가능한 연속적인 블록에 저장됨으로써 seek time 을 최소화 할 수 있습니다.
하지만 이 순차적 접근이 항상 보장되는 것은 아닌데요.
카프카의 데이터가 위치하는 파일 시스템을 다른 어플리케이션이 함께 사용하는 경우, 다른 어플리케이션으로 인해 디스크 단편화가 발생할 가능성은 존재합니다.
따라서 카프카의 순차적 접근을 보장하기 위해, 카프카의 데이터는 가급적 독립된 파일 시스템에 유지할 것을 권장합니다.
최신 OS들은 앞으로 필요할 것으로 예상되는 메시지를 미리 읽어들이는 read-ahead와 디스크에 실제 쓰는 작업을 비동기적으로 처리하는 write-behind 같은 기술을 통해, 순차 입출력 작업이 더 빠르게 수행되도록 지원하고 있습니다.
또한 디스크 액세스 속도를 더 개선시키기 위해 읽기/쓰기 작업은 OS 페이지 캐시를 최대한 활용한다면 디스크 접근 횟수를 줄일 수 있습니다. OS 페이지 캐시는 2번째 내용에서 조금 더 자세히 다뤄집니다.
따라서, 카프카는 순차적 I/O 의 혜택을 활용함으로써 메모리에 저장하는 MOM 시스템보다 빠른 성능을 제공할 수 있습니다.
카프카는 메모리에 별도의 캐시를 구현하지 않고 OS의 페이지 캐시에 이를 모두 위임하였습니다. 따라서, 모든 디스크의 읽기/쓰기는 OS의 페이지 캐시를 거치게 되는데요.
어플리케이션에 의해 관리되는게 아니라 OS에 의해 관리되기 때문에, 사용자 영역과 커널 영역의 중복 저장 없이 2배 정도의 캐시를 저장할 수 있습니다.
만약 애플리케이션 내부에서 캐시를 사용한다면, 같은 내용을 운영체제의 페이지 캐시와 애플리케이션 내부 캐시에 중복 저장하게 되는 것입니다.

위 그림을 보시면, 카프카는 Producer가 전송한 메시지를 JVM 힙메모리에 저장하지 않고 페이지 캐시에 저장하고 있습니다.
JVM 힙메모리에 저장하지 않음으로써 메시지가 JVM 객체로 변환되면서 크기가 커지는 것을 방지할 수 있고, 힙 내의 데이터가 증가함에 따라 점점 느려지는 GC 의 성능저하 또한 피할 수 있습니다.
또한 카프카 프로세스가 직접 캐시를 관리하지 않고 OS에 위임하기 때문에 프로세스를 재시작 하더라도 OS의 페이지 캐시는 그대로 남아있습니다. 따라서 프로세스 재시작 후 캐시를 워밍업할 필요가 없다는 장점도 있습니다. 여기서 워밍업이란 시스템이나 어플리케이션 시작 시, 또는 특정 작업 수행 전에 캐시를 미리 채워두는 작업을 의미합니다.
요약하면, 카프카는 데이터를 힙 메모리 내에 객체로 저장하는 대신 운영체제가 훌륭하게 최적화하고 있는 페이지 캐시를 활용한 디스크 저장 방식을 도입하여 성능을 높일 수 있었습니다.
zero-copy 는 디스크의 데이터를 네트워크로 전송할 때 일어나는 데이터 복사 작업을 최소화 한 데이터 전송 방식입니다.
카프카에서는 파일 시스템에 저장된 메시지를 네트워크를 통해 전송할 때 zero-copy 기법을 사용하여 데이터 전송 성능을 향상시켰습니다.
일반적으로 파일 시스템에 저장된 데이터를 네트워크로 전송할 때는 아래 그림과 같은 데이터 전달이 이루어집니다.

1. 디스크 → Read Buffer (DMA ; Direct Memory Access)
2. Read Buffer → Application Buffer (CPU)
3. Application Buffer → Socket Buffer (CPU)
4. Socket Buffer → NIC Buffer (DMA)
전통적인 데이터 복사 방식은 4번의 컨텍스트 스위칭과 4개의 메모리 복사본이 생기면서 불필요한 복사와 시스템콜이 발생하게 됩니다.
이처럼 비효율적인 동작을 개선하기 위해 소개된 기법이 zero copy 입니다.
리눅스 2.2 버전에서 처음 소개된 sendfile() 시스템 콜이 제로카피 동작을 구현했는데요.
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t * offset ", size_t" " count" );
자바에서는 nio 패키지의 transferTo(), transferFrom() 메소드로 구현되어 있으며, 이 메서드들 역시 sendfile() 시스템콜을 이용해 구현되어 있습니다.
public void transferTo(long position, long count, WritableByteChannel target);

zero copy 를 사용하면, 커널 영역의 Read Buffer 에서 Socket Buffer로 직접 복사가 가능하여 효율적으로 데이터를 전송할 수 있습니다. read(), send() 2번의 시스템콜이 transferTo() 1번의 호출로 가능해졌습니다.
따라서 transferTo() 메서드 호출시 커널모드로 1번, 종료시 유저모드로 1번, 총 2번으로 컨텍스트 스위칭 횟수가 줄었고, 데이터의 복사본이 4군데에서 3군데로 줄어들었습니다.
컨텍스트 스위칭 횟수와 복사본의 개수가 줄어든만큼 CPU 자원의 낭비가 줄어들게 되어 성능이 향상되었습니다.
리눅스 커널 2.4 이후부터는 NIC 장비가 “Gather Operation”을 지원할 경우 복사본을 더 줄일 수 있게 되었습니다.

이러한 최적화를 위해서는 Gather operation과 프로토콜의 checksum 기능이 추가적으로 필요합니다.
아래 그래프는 실제 리눅스 2.6 커널 버전에서 성능을 비교한 것인데요.
전통적인 방식의 파일 전송과 transfer() 메소드를 이용한 파일 전송 속도를 비교했을 때, 수행시간이 50% 이상 줄어들었습니다.

디스크에서 파일을 읽은 후 추가 작업 없이 바로 네트워크로 전송하는 파일 서버나 정적 파일을 전송하는 웹 서버의 경우 zero copy 기법을 사용하면 성능 개선 효과를 얻을 수 있습니다.
특히 네트워크 속도가 매우 빨라서 성능상 병목점이 CPU로 몰릴 수록 불필요한 데이터 복사를 제거하여 성능 개선을 도모할 수 있습니다.
요약하자면, 디스크에서 파일을 읽고 네트워크로 전달하는 과정에서 read(), send() 두번의 시스템 콜로 인한 컨텍스트 스위칭과 데이터 복사에 따른 성능 저하가 따랐습니다. zero copy의 transferTo() 메서드를 통한 하나의 시스템콜을 활용함으로써, 컨텍스트 스위칭과 데이터 복사에 필요한 비용을 줄여 성능 개선을 이루었습니다.
디스크의 순차적 I/O, 페이지 캐시, 제로 카피.
이 3가지 방법을 사용하여 카프카는 파일 시스템을 메시지의 주 저장소로 사용하면서도 뛰어난 처리량을 구현해낼 수 있었습니다.