Kafka 메시지 압축을 통한 대용량 이미지 전송 최적화

김기현·2024년 11월 25일
1

최근 프로젝트에서 우리는 솔루션 인터페이스에 인코딩된 이미지를 메시지에 포함시켜 Kafka를 통해 전송해야 하는 과제에 직면했습니다. 그러나 이미지 데이터의 특성상 메시지 크기가 너무 커져 전송 효율성과 시스템 성능에 문제가 발생했습니다. 이 글에서는 이 문제를 어떻게 정의하고 해결했는지, 그리고 각 압축 방식에 대한 비교를 통해 최적의 솔루션을 선택한 과정을 공유하고자 합니다.

문제 정의

대용량 메시지로 인한 성능 저하

  • 메시지 크기 증가: 인코딩된 이미지를 그대로 메시지에 포함시키면서 메시지 크기가 급격히 증가했습니다.
  • 네트워크 대역폭 문제: 큰 메시지는 네트워크 대역폭을 많이 차지하여 다른 서비스의 성능에도 영향을 미쳤습니다.
  • Kafka 클러스터 부담: 메시지 크기가 커지면서 Kafka 브로커와 컨슈머의 메모리 사용량이 증가했고, 이는 시스템 안정성에 위험을 초래했습니다.

문제 해결 과정

1. 문제 분석

우선 현재 시스템에서 메시지가 어떻게 전송되고 있는지, 그리고 그로 인해 어떤 문제가 발생하는지 상세히 분석했습니다.

  • 메시지 포맷 확인: 이미지가 Base64로 인코딩되어 메시지에 포함되고 있음을 확인했습니다.
  • 메시지 크기 측정: 평균적인 메시지 크기가 Kafka의 권장 최대 메시지 크기를 초과하고 있었습니다.
  • 성능 지표 수집: 네트워크 지연, 스루풋 감소 등의 성능 저하 지표를 수집했습니다.

2. 가능한 솔루션 탐색

문제 해결을 위해 여러 가지 접근 방안을 고려했습니다.

  • 이미지 압축 적용: 이미지 자체를 압축하여 크기를 줄이는 방법.
  • 전송 포맷 변경: 이미지 인코딩 방식을 변경하여 효율성을 높이는 방법.
  • Kafka의 메시지 압축 기능 활용: Kafka가 제공하는 메시지 압축 옵션을 사용하는 방법.
  • 외부 스토리지 사용: 이미지를 외부 스토리지에 저장하고 링크만 전송하는 방법.

3. Kafka 메시지 압축 기능 적용

사실 외부 스토리지 사용을 하고 싶지만, 솔루션에서 제공하는 스펙이라 맞출 수 밖에 없었습니다.
따라서 Kafka의 메시지 압축 기능을 활용하였습니다.
부가적으로 얻을 수 있는 장점은..

  • 구현 용이성: 코드 변경이 최소화되고, 기존 인프라를 그대로 사용할 수 있습니다.
  • 압축 효율성: Kafka는 다양한 압축 알고리즘(LZ4, Snappy, GZIP, Zstd)을 지원하여 높은 압축률을 기대할 수 있습니다.
  • 시스템 영향 최소화: 클라이언트와 브로커 간 설정만으로 적용 가능하며, 다른 서비스에 미치는 영향을 최소화합니다.

4. 압축 방식 비교 및 선택

Kafka가 지원하는 압축 방식에 대한 성능과 압축률을 비교하여 최적의 옵션을 선택하고자 했습니다.

압축 방식 비교 테이블

압축 방식압축률압축 속도해제 속도CPU 사용량특징
GZIP높음느림느림높음높은 압축률, CPU 부하 큼
Snappy낮음매우 빠름매우 빠름낮음빠른 속도, 낮은 압축률
LZ4중간빠름빠름낮음속도와 압축률의 균형
Zstd매우 높음빠름빠름중간높은 압축률과 속도

각 방식의 사례 조사

  • GZIP: 높은 압축률을 제공하지만 CPU 사용량이 높아 성능 저하가 발생할 수 있습니다.
  • Snappy: 매우 빠른 압축 및 해제 속도를 제공하지만 압축률이 낮습니다.
  • LZ4: 빠른 속도와 적절한 압축률을 제공하여 실시간 데이터 처리에 적합합니다.
  • Zstd: 높은 압축률과 빠른 속도를 동시에 제공하며, 최신 Kafka 버전에서 지원됩니다.

선택한 압축 방식: LZ4

  • 이유:
    • 속도와 압축률의 균형: LZ4는 빠른 압축/해제 속도와 중간 수준의 압축률을 제공하여 실시간 데이터 처리에 적합합니다.
    • 낮은 CPU 사용량: 시스템에 과도한 부하를 주지 않고도 효율적인 압축이 가능합니다.
    • 호환성: 대부분의 Kafka 클라이언트와 브로커에서 기본적으로 지원됩니다.

5. 구현 및 테스트

핵심 코드

Kafka 메시지 압축을 적용하기 위해 Spring Boot와 Kotlin을 사용하여 간단한 예제 코드를 작성했습니다.

프로듀서 코드
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.stereotype.Service
import java.util.*

@Service
class ImageProducer(private val kafkaTemplate: KafkaTemplate<String, ByteArray>) {

    fun sendImage(imageData: ByteArray) {
        val topic = "image-topic"
        val key = UUID.randomUUID().toString()
        kafkaTemplate.send(topic, key, imageData)
    }
}
컨슈머 코드
import org.springframework.kafka.annotation.KafkaListener
import org.springframework.stereotype.Service

@Service
class ImageConsumer {

    @KafkaListener(topics = ["image-topic"], groupId = "image-group")
    fun consumeImage(imageData: ByteArray) {
        // 이미지 데이터를 처리하는 로직
        println("Received image data of size: ${imageData.size} bytes")
    }
}
이미지 압축 및 전송

이미지를 전송하기 전에 추가로 압축하고 싶다면, 이미지 데이터를 GZIP 등으로 압축할 수 있습니다. 그러나 Kafka의 LZ4 압축을 활용하기 때문에 추가적인 압축은 필요하지 않았습니다.

테스트 수행

  • 메시지 크기 비교: LZ4 압축 적용 전후의 메시지 크기를 비교했습니다.
  • 성능 지표 수집: 압축 적용 후 네트워크 대역폭 사용량, 메시지 처리량 등을 측정했습니다.
  • 시스템 안정성 확인: 브로커와 컨슈머의 메모리 사용량을 모니터링했습니다.

결과 분석

  • 메시지 크기 감소: 평균 메시지 크기가 약 65% 감소했습니다.
  • 네트워크 효율성 개선: 네트워크 대역폭 사용량이 크게 줄어들어 다른 서비스의 성능도 개선되었습니다.
  • 시스템 안정성 향상: 메모리 사용량이 감소하여 브로커와 컨슈머의 안정성이 향상되었습니다.
  • CPU 사용량 최적화: LZ4를 사용함으로써 CPU 부하를 최소화했습니다.

결론

Kafka의 LZ4 메시지 압축 기능을 활용하여 대용량 이미지 데이터를 효율적으로 전송할 수 있었습니다. 이를 통해 시스템 성능 저하 문제를 해결하고, 네트워크 및 시스템 자원을 효율적으로 사용할 수 있게 되었습니다.

  • 압축 방식의 중요성: 적절한 압축 방식을 선택함으로써 성능과 효율성을 모두 잡을 수 있습니다.
  • 내장된 기능 활용: 기존 시스템이 제공하는 기능을 적극 활용하면 복잡한 문제도 효과적으로 해결할 수 있습니다.
  • 성능 측정 및 모니터링: 변경 사항의 효과를 객관적으로 평가하기 위해 성능 지표를 수집하고 분석하는 과정이 필수적입니다.

이번 경험을 통해 시스템 설계 시 데이터 크기와 전송 효율성에 대한 고려가 얼마나 중요한지 다시 한 번 깨달을 수 있었습니다. 앞으로도 이러한 문제에 직면했을 때 근본적인 원인을 파악하고, 효율적인 해결책을 찾아나갈 것입니다.

0개의 댓글