카프카가 데이터를 저장하는 방식

이 글은 아래 링크 글을 참고해서 작성된 글 입니다.

If you have any copyright issues, please contact me by email.

  • gunjuko92@gmail.com

카프카가 어떻게 데이터를 저장하는지 아는 것은 중요하다. 특히 카프카 성능을 튜닝하거나, 각 브로커 설정이 어떠한 역할을 하는지 이해하고자 한다면 카프카가 데이터를 어떻게 저장하는지를 알아야한다.

partitions

파티션은 여러개의 브로커에 분산되어 저장할 수 없으며 심지어 여러개의 디스크에 저장하는 것도 불가능하다. 카프카를 설정할 때 파티션이 저장될 디렉토리의 위치를 log.dirs 매개변수에 지정한다.

토픽을 생성할 때 카프카는 제일 먼저 여러 브로커 간에 파티션을 할당하는 방법을 결정한다. 파티션 할당은 다음과 같이 진행된다.

  • 파티션 리플리카들을 브로커 간에 고르게 분산시킨다.
  • 각 파티션의 리플리카는 서로 다른 브로커에 할당된다.
  • 만일 브로커가 랙 정보를 갖고 있다면, 가능한 한 각 파티션의 리플리카는 서로 다른 랙에 있는 것으로 지정한다. 이렇게 하면 하나의 랙 전체가 작동하지 않더라도 모든 파티션을 사용하지 못하게 만드는 불상사가 생기지 않기 때문이다.

cleanup.policy

“compact” 혹은 “delete” 둘중 하나의 값이다. 이 설정은 로그의 retention policy를 어떻게 할지를 결정한다.

  • compact : key별로 가장 최근의 value만 저장한다. (주로 KTable에서 사용)
  • delete : retention.ms를 지나거나, retention.bytes 사이즈 제한을 넘어선 경우, 오래된 세그먼트를 삭제한다.

retention은 카프카의 중요한 개념이다. 카프카는 데이터를 영원히 보존하지 않으며, 메시지 삭제 전에 모든 컨슈머가 읽기를 기다리지도 않기 때문이다.

retention.ms

retention.ms는 토픽의 데이터를 유지하는 기간을 지정한다. 이 기간이 지난 데이터는 카프카에서 제거된다.

retention.bytes

retention.bytes는 파티션의 최대 크기를 제어한다. 만약 “delete” retention policy를 사용하고 있고, 파티션의 크기가 retention.bytes를 넘어서는 경우엔 예전 로그가 삭제하고 새로운 로그를 추가한다. 기본값은 -1이다. -1은 파티션의 크기에 제약을 주지 않는다. 이 경우엔 retention.ms 값을 얼마로 하는지에 따라 파티션의 크기가 결정된다.

segments

만약의 카프카의 데이터를 하나의 파일에만 저장한다면, 삭제해야할 오래된 데이터를 찾는건 매우 어려워진다. 이런 문제를 해결하기 위해 파티션은 세그먼트로 나눠져서 저장된다. 카프카는 파티션에 쓰기 작업을 할 때 세그먼트에 쓰기 작업을 한다. 만약 세그먼트의 크기가 한계에 도달하면 새로운 세그먼트가 생성되고 생성된 세그먼트는 액티브 세그먼트가 된다.

기본적으로 각 세그먼트는 최대 1GB의 데이터 또는 1주일 동안 데이터를 보존한다. 카프카 브로커가 파티션에 데이터를 쓸 때 세그먼트의 제한 크기나 보존 기간에 도달하면 해당 파일을 닫고 새로운 세그먼트 파일에 계속 쓴다. 메시지를 쓰기 위해 사용 중인 세그먼트를 액티브 세그먼트라고 한다.

토픽의 세그먼트 크기는 “segment.bytes” 속성으로 설정할 수 있다. 카프카는 오래된 로그를 삭제할 때 세그먼트 단위로 삭제한다. 따라서 세그먼트의 크기가 너무 크다면, 오래된 데이터가 “retention.ms”가 지났음에도 불구하고 삭제되지 않을 수도 있다. (액티브 세그먼트는 삭제를 하지 않는다.)

세그먼트 파일의 이름은 base 오프셋이 된다. base 오프셋은 세그먼트의 시작점이 되는 오프셋이라고 생각하면 된다. 만약에 세그먼트 파일 이름이 3.log이라고 한다면, 해당 세그먼트 파일에는 오프셋이 3 이상인 로그가 저장된다.

세그먼트는 인덱스 파일(.index)과 로그 파일(.log)로 구성된다.

  • 로그 파일은 실제 메시지가 저장되는 곳이다. 메시지는 value, offset, timestamp, compression code, message format 정보로 구성되어 있다.
  • 인덱스 파일은 각 오프셋 별로 로그 파일의 위치를 저장한다. 특정 오프셋의 메시지를 찾을때 인덱스 파일을 사용한다. 인덱스도 세그먼트로 분할된다. 따라서 메시지가 삭제되면 그것과 연관된 인덱스 파일도 삭제한다.

message format

각 세그먼트는 하나의 데이터 파일로 저장된다. 디스크에 저장되는 데이터 형식은 프로듀서가 브로커에게 전송하는 메시지의 형식과 동일하다. (또한 브로커가 컨슈머에게 전송하는 형식도 동일하다) 이처럼 디스크와 네트워크 모두에게 같은 메시지 형식을 사용하므로 카프카는 제로카피 기법을 사용해서 메시지 전송을 최적화 한다. 즉 컨슈머에게 메시지를 전송할 때 별도의 버퍼 메모리를 사용하지 않고 디스크에서 바로 네트워크로 전송하며, 프로듀서가 이미 압축해서 전송한 메시지의 압축 해지와 재압축을 하지 않는다.

메시지는 오프셋, 손상 여부를 검출하기 위한 체크섬 코드, 메시지 형식의 버전을 나타내는 매직 바이트, 압축 코덱, 타임 스탬프, 키, 값, 오프셋, 메시지 크기 등을 포함한다.

만일 프로듀서가 압축된 메시지를 전송한다면, 하나의 배치에 포함된 모든 메시지가 같이 압축되어 래퍼 메시지의 값으로 전송된다. 브로커는 이를 하나의 메시지로 디스크에 저장한다.

Change “retention.ms”

만약의 카프카의 디스크 공간이 부족하다면 이를 해결할 수 있는 가장 쉬운 방법 중 하나는 retention.ms 값을 낮추는 것이다. 토픽의 retention.ms 값은 카프카를 재시작하지 않고 수정할 수 있다. 아래와 같은 명령어를 사용하면 retention.ms 값을 수정할 수 있다.

bin/kafka-topics.sh --zookeeper ${zookeeper ip address} --alter --topic ${topicName} --config retention.ms=86400000

retention.ms값을 수정한 뒤에 다음과 같은 명령어를 통해 제대로 수정되었는지 확인하자

bin/kafka-topics.sh --zookeeper ${zookeeper ip address} --describe --topic ${topicName}

결과가 아래와 같이 나온다면 정상적으로 retention.ms 설정이 적용된 것이다.

Topic:test	PartitionCount:3	ReplicationFactor:1	Configs:retention.ms=86400000

Topic: test	Partition: 0	Leader: 0	Replicas: 0	Isr: 0
Topic: test	Partition: 1	Leader: 0	Replicas: 0	Isr: 0
Topic: test	Partition: 2	Leader: 0	Replicas: 0	Isr: 0

브로커의 디스크 사이즈 확인하기

브로커가 디스크를 어느정도 사용하고 있는지 확인하고 싶으면 log.dirs 디렉토리에서 아래와 같은 명령어를 사용하면 된다.

du -sh ./* | grep G

Log Compaction

cleanup.policy를 compact로 하는 경우에는 각 키의 가장 최근 값만 토픽에 저장한다. 이러한 경우 각 메시지는 키를 가지고 있어야 한다. 만약 키가 널이면 compact가 되지 않는다. compact를 사용하는 경우 로그 세그먼트는 다음의 두 부분으로 나누어 생각할 수 있다.

  • 클린 : compact된 메시지가 있는 부분. 이 부분은 각 키에 대해 하나의 값만 포함한다.
  • 더티 : 아직 compact되지 않은 메시지들이 저장된 부분이다.

만약 log.cleaner.enabled 설정이 true라면 브로커는 하나의 compact 매니저 스레드와 여러 개의 compact 스레드를 시작한다. 이 스레드들은 compact 작업을 수행하는 책임을 가진다.

가장 최근 메시지조차도 남기지 않고 시스템에서 특정 키를 완전히 삭제할 때는 애플리케이션에서 해당 키와 null 값을 포함하는 메시지를 카프카에 쓰면 된다. 그러면 compact 스레드에서 그런 메시지를 발견할 때 평상시대로 compact을 수행한 후 해당 키에 대해서는 null 값을 갖는 메시지만 남겨둘 것이다. 그리고 톰스톤이라고 하는 이런 특별한 메시지는 카프카에 설정된 기간 동안 보존될 것이다. 또한 이 기간동안 컨슈머가 톰스톤 메시지를 읽으면 값이 null이라서 삭제 되었음을 알 수 있다. 그리고 카프카에 설정된 시간이 지나면 compact 스레드에서 톰스톤 메시지를 삭제할 것이고, 해당 키의 메시지는 파티션에서 없어질 것이다.

현재 사용중인 액티브 세그먼트를 삭제하지 않듯이, 현재 사용중인 세그먼트는 compact하지 않는다. 사용 중이 아닌 세그먼트의 메시지들만 compact의 대상이 된다. 카프카 0.10.0 이하 버전에서는 토픽이 50%가 더티 레코드를 포함할 때 압축을 시작한다. 압축은 토픽의 읽고 쓰기 성능에 영향을 줄 수 있으므로 너무 자주 하지 않는 것이 좋기 때문이다.