TL;DR

  • Postgres 확장 기능인 Kafgres 0.2.0이 i9 서버에서 256KiB 레코드 기준 초당 약 700MB, 초당 60만 건의 안정적인 쓰기 처리량을 달성함.
  • Kafgres는 Postgres 내부에 단일 브로커를 포함하고, 대부분의 토픽 데이터를 Postgres 내부 처리 과정을 거치지 않고 디스크에 직접 기록함.
  • 주요 병목은 디스크가 아니라 CPU의 구문 분석·분석·계획 수립과 커밋 처리였으며, 캐시형 SPI 계획과 완화된 커밋 설정으로 개선함.
  • 가장 큰 처리량 향상은 5밀리초 간격의 대기 루프를 Postgres의 WaitEventSet 기반 소켓 준비 상태 감지로 교체한 데서 비롯됨.
  • Kafgres는 대다수 사용 사례에 충분할 가능성이 크지만, 다중 가용 영역 복제와 장애 대응에서는 3개 영역 Kafka 클러스터보다 제약이 있음.

작동 방식

  • Kafgres는 Postgres 확장 기능으로 데이터베이스 안에 설치되며, Postgres 확장 API를 통해 운영체제와 상호작용하는 단일 브로커임.
  • 메타데이터용 쓰기 미리 기록 로그(WAL)와 리더·팔로어 장애 조치에는 Postgres의 기능을 활용하지만, 나머지 데이터베이스와는 대체로 독립적으로 작동함.
  • 대부분의 작업은 Postgres가 실행하는 별도의 백그라운드 워커가 처리하며, 기본 설정에서는 토픽 데이터를 디스크에 직접 기록해 Postgres 내부 처리 과정과 오버헤드를 대부분 건너뜀.
  • 이전 Kafgres 소개 글에서는 초당 10만 건 이상의 이벤트를 처리해야 하는 아키텍처에 Kafka가 적합하다고 언급했지만, 같은 처리량의 Kafka 클러스터보다 수행 작업이 적은 만큼 Kafgres도 부하를 쉽게 감당할 수 있을 것으로 예상함.
  • Hetzner 서버 경매에서 시간당 약 0.25달러에 대여한 PLP 지원 NVMe 드라이브 탑재 i9 서버에서 초당 약 700MB, 초당 60만 건의 안정적인 쓰기 처리량을 달성함.

병목 지점 확인

  • Kafgres 0.1.0에 여러 프로듀서를 연결해 i9 서버와 일부 테스트의 저렴한 i7 서버에서 측정한 결과, 초당 100~200MB 처리량에서 지연 시간이 급증하며 엔진이 정체되기 시작함.
  • Kafgres 전용 CPU 코어 사용률이 거의 100%에 이르러, 정체의 원인이 디스크 대기가 아니라 CPU 시간임을 시사함.
  • 서버 드라이브의 측정 처리량은 초당 약 1,911MB, 동기화 속도는 초당 약 6만 8,000회였음. 브로커의 동기화 횟수는 초당 600회 미만으로, 드라이브 쓰기 여유가 충분함을 확인함.

프로파일링 결과

  • 포화 상태의 Kafgres 브로커를 프로파일링한 결과, CPU 사용 시간 중 40.7%가 구문 분석·분석·실행 계획 수립에 쓰임. 테이블에는 메타데이터만 저장되고 토픽 데이터는 디스크에 직접 기록되는 점을 고려하면 상당한 비중임.
  • CPU 외 대기 시간 중 대략 3분의 1은 커밋에서 발생함. 전원 손실 보호 기능이 없는 드라이브를 탑재한 저성능 서버에서는 문제가 됐지만, i9 서버에서는 영향이 작았음.

처리량 개선

  • 반복되는 캐시 가능 작업에 CPU 시간의 거의 절반이 쓰이는 문제를 줄이기 위해 메타데이터 쿼리에 SPI_keepplan을 적용함. Postgres가 일반 계획을 유지하고 재사용하도록 해 실행할 때마다 계획을 다시 세우는 과정을 줄임.
  • 당시 테스트 설정에서 비트랜잭션 프로듀스는 확인 응답 시점까지 실제 토픽 데이터가 드라이브에 기록됐음을 보장하지 않았지만, 오프셋은 동기식으로 커밋하도록 설정돼 있었음.
  • 오프셋 커밋에도 완화된 내구성 설정을 적용하면 디스크에 기록하기 전에 WAL에 더 많은 메타데이터를 모을 수 있음. 이를 켜고 끄는 relaxed_produce_commit GUC를 추가해 처리량을 높임.
  • 처리량 자체를 높이는 목적이 아닌, 일반적인 Postgres 사용에 미치는 영향을 줄이기 위해 토픽 데이터 기록 디렉터리를 설정하는 필드를 추가함.
  • i9 서버의 두 NVMe 드라이브 중 데이터베이스와 다른 드라이브를 토픽 데이터에 사용하면, 초당 100MB 처리량에서도 같은 데이터베이스를 대상으로 하는 pgbench 성능 저하는 거의 없으며 1% 미만임.
  • 위 변경과 자세히 다루지 않은 몇 가지 개선으로 처리량이 초당 113MB에서 197MB로 증가함.
  • 이후 Docker 프록시를 통하는 벤치마크와 호스트 네트워킹으로 Kafgres 소켓에 직접 연결하는 벤치마크 사이의 성능 차이를 확인함.
  • Kafgres 0.1.0의 단순한 방식은 5밀리초 간격으로 반복 실행되는 루프였음. 이 방식은 대기 시간 동안 바이트를 쌓은 뒤 일정에 따라 백그라운드 워커를 깨워 처리함.
  • 이를 Postgres 자체의 WaitEventSet으로 교체함. 일정 시간이 지날 때까지 대기하는 대신, 작업을 시작할 수 있는 소켓이 준비될 때까지 대기하며 5밀리초 간격은 대기 상한으로만 사용함.
  • 호스트 네트워킹을 사용할 때 이 소켓 준비 상태 감지 변경이 처리량 개선의 대부분을 차지함.
  • 레코드 크기별 처리량은 다음과 같음.
  • 변경 전: 1KiB 기준 초당 118.8MB, 256KiB 기준 초당 113.3MB
  • 캐시형 SPI 계획 적용: 각각 초당 139.9MB, 135.6MB
  • 완화된 커밋 및 몇 가지 조정 적용: 각각 초당 169.7MB, 197.3MB
  • 소켓 준비 상태 감지 적용: 각각 초당 597.3MB, 703.4MB
  • 레코드 크기 256KiB에서 지연 시간이 낮고 안정적인 초당 700MB 프로듀스 처리량을 달성함.

비교

  • 주요 변경과 추가 성능 조정으로 Kafgres는 상용 Kafka와 경쟁할 만한 수준에 도달함.
  • 여러 서비스형 소프트웨어(SaaS) Kafka 업체의 추산에 따르면 Kafka 작업량의 56%는 초당 1MB 이하임. 초당 수백 MB까지 확장하는 작업은 대체로 대기업에서 발생하며, 이들은 Kafka 관리 전담 팀을 두거나 업체에 관리 비용을 지불할 수 있음.
  • Amazon MSK에서 초당 약 700MB를 처리할 수 있는 일반적인 3노드 Kafka 클러스터를 구성하려면 최소 구성으로 여겨지는 kafka.m5.8xlarge 노드가 필요할 가능성이 큼. 노드당 시간당 3.36달러로, 브로커 컴퓨팅 비용만 월 7,357달러임.
  • 스토리지와 데이터 전송 비용은 별도이며, 일반적인 AWS 요금을 적용하면 수만 달러가 추가됨. 이 데이터량을 24시간만 보관해도 스토리지 비용은 월 18,000달러임.
  • MSK와 다른 업체의 강점은 복제임. Kafgres는 Postgres 데이터베이스 자체처럼 보조 데이터베이스로 장애 조치할 수 있지만, 3개 가용 영역 Kafka 클러스터처럼 특정 가용 영역의 장애를 매끄럽게 견딜 수는 없음.
  • Postgres를 클라우드에 호스팅하더라도 MSK에서 발생하는 스토리지와 네트워킹 비용 중 상당 부분은 Postgres 인스턴스에서도 발생함.
  • 이러한 조건에서도 대다수 사용 사례에는 Postgres 내 Kafgres로 충분할 가능성이 큼. Kafgres의 한계에 도달할 무렵에는 이에 상응하는 업체 제공 ‘실제 Kafka’ 배포 비용이 연간 수십만 달러에 이를 수 있음.
  • Kafgres 0.2.0이 출시됐으며, ‘실제’ Kafka 4.0과 Kafgres 사이의 격차를 크게 줄임.