IT/모니터링

Vector vs Fluent Bit, 6개월 둘 다 굴려본 노트

gfrog 2026. 5. 23. 09:45
반응형

작년 말쯤 로그 파이프라인을 다시 손볼 일이 생겼다. 기존엔 모든 노드에 Fluent Bit DaemonSet으로 쓰고 있었는데, 트랜스폼 규칙이 복잡해지면서 Lua 필터가 점점 괴물이 되어가는 게 보였다. 그래서 한쪽 클러스터에 Vector를 시범 도입했고, 결국 6개월 동안 두 도구를 같은 워크로드에 나란히 굴려보게 됐다. 이 글은 그 결과 정리다. 결론부터 말하면, 둘 다 자리가 있다. 다만 자리가 다르다.

우리 환경

먼저 맥락. 이걸 안 깔면 비교가 의미가 없다.

  • EKS 클러스터 2개, 합쳐서 노드 약 90대 (m6i.2xlarge ~ m7i.4xlarge 혼재)
  • 로그 발생량: 평시 평균 35k logs/sec, 피크 110k logs/sec
  • 목적지: S3 (장기), OpenSearch (검색), Kafka (실시간 파이프라인)
  • 트랜스폼 요구: PII 마스킹, JSON 재구조화, 일부 라우팅 분기

특이점은 트랜스폼이다. 사실 많은 비교 글이 "Vector가 빠르다 vs Fluent Bit이 가볍다" 정도로 끝나는데, 우리 환경에서 갈리는 건 거기가 아니라 "복잡한 트랜스폼을 누가 더 깔끔하게 다루느냐"였다.

처음 마주친 차이: 트랜스폼 작성 경험

Fluent Bit에서 PII 마스킹을 하려면 보통 Lua 필터를 쓴다. 처음엔 그럭저럭 동작했다. 그런데 마스킹 규칙이 늘어나고, 특정 서비스에만 적용해야 하는 조건부 로직이 들어가니까 Lua 스크립트가 200줄을 넘어가기 시작했다. 디버깅도 쉽지 않았다. 필터에서 에러가 나면 어디서 났는지 추적이 애매했다.

Vector로 같은 걸 짜보니 VRL(Vector Remap Language)이 꽤 잘 맞았다. 예를 들면:

transforms:
  pii_mask:
    type: remap
    inputs: [k8s_logs]
    source: |
      if exists(.payload.email) {
        .payload.email = redact(.payload.email, filters: ["pattern"], patterns: [r'.*'])
      }
      if .service_name == "payment" {
        .payload.card_last4 = string!(.payload.card_last4) ?? "0000"
        del(.payload.card_full)
      }

VRL은 타입이 있고, 컴파일 타임에 에러를 잡아준다. vector validate로 설정 검증이 되는 것도 좋았다. Lua는 런타임에 가서야 알게 되는 게 많다.

근데 단점도 있다. VRL은 표현력이 좋은 만큼 학습 곡선이 있다. 팀 동료 한 명은 "이거 또 새로운 DSL이냐"고 했고, 사실 일리 있는 지적이다.

자원 사용량은 예상대로

이건 큰 변수 없이 예상대로였다. 동일 노드(m6i.2xlarge, 8 vCPU / 32GB)에서 평시 워크로드 기준으로:

항목 Fluent Bit 3.x Vector 0.4x
RSS 메모리 약 95MB 약 280MB
CPU (4코어 환산) 약 6% 약 11%
p99 처리 지연 약 230ms 약 70ms

VictoriaMetrics가 최근에 낸 2026 로그 수집기 벤치마크와 비슷한 결과다. Vector가 latency는 확실히 좋고, Fluent Bit는 메모리가 압도적으로 가볍다. 노드 한 대 단위로는 큰 차이 같지 않아 보이지만, 노드 90대 곱하면 의미 있는 숫자가 된다.

피크 110k logs/sec 부근에서 Fluent Bit는 메모리가 살짝 튀는 정도였고 Vector는 CPU가 더 올라갔다. 둘 다 죽지는 않았다. OOM은 한 번도 없었다.

카프카 출력에서 작은 사고가 있었다

이건 짚고 가야 한다. Fluent Bit의 Kafka 출력 플러그인을 쓰다가 한 번 데이터 유실 의심 케이스가 있었다. 정확히 말하면 유실은 아니고, 브로커가 잠깐 들썩일 때 retry 동작이 우리가 생각한 것과 달랐다. Retry_Limit을 false(무제한)로 잡아놨는데도 특정 케이스에서 메시지가 drop되는 게 보였다. librdkafka 쪽 큐가 가득 차면 그 뒤로 들어오는 게 새로운 것부터 밀려나는 형태였다.

queue.buffering.max.messagesqueue.buffering.max.kbytes를 키우고, rdkafka.queue.full.actionblock으로 바꿔서 해결했다. 근데 이 옵션 찾는 데 시간이 꽤 걸렸다. 사실 Fluent Bit 문서가 librdkafka 옵션을 전부 다 안 적어두고 있어서 librdkafka 본가 문서를 같이 봐야 한다.

Vector의 Kafka sink는 같은 케이스에서 좀 더 명시적이었다. acknowledgements.enabledlibrdkafka_options를 같이 쓸 수 있고, end-to-end ack가 명확히 정의돼 있다. 다만 Vector도 만능은 아니라서, 토픽 파티션이 갑자기 늘었을 때 자동으로 재분배가 잘 안 되는 케이스를 한 번 봤다. 재시작으로 풀었다.

그래서 우리는 결국 어떻게 굴리고 있나

6개월 결론은 하이브리드다. 이게 어떻게 보면 가장 재미없는 답이긴 한데, 그게 우리한테 맞았다.

  • 노드 에이전트는 Fluent Bit. 입력 수집과 1차 필터링(쓰레기 로그 drop, 메타데이터 enrich)만. 트랜스폼 로직 거의 없음. 메모리 부담 최소화.
  • 중앙 어그리게이터는 Vector. Fluent Bit가 보낸 걸 받아서 PII 마스킹, JSON 재구조화, 라우팅 분기, S3/OpenSearch/Kafka로 fan-out.

이 구조에서 Fluent Bit는 자기가 잘하는 것(가볍게 수집)을 하고, Vector는 자기가 잘하는 것(타입 안전한 트랜스폼, 좋은 sink들)을 한다. Lua 필터는 결국 다 들어냈다.

비용 측면도 나쁘지 않다. Vector 어그리게이터는 별도 노드풀(c7i.large 3대)에 띄워놨고, 노드 90대에 Vector를 다 깔았을 때의 메모리 합계보다 훨씬 적게 든다.

한 가지 더, 우리가 못 한 것

OpenTelemetry Collector도 후보였다. 결국 쓰지는 않았는데, 이유는 단순했다. OTel Collector의 logs 파이프라인 성숙도가 우리가 보기엔 아직 Vector/Fluent Bit만큼은 아니었다. trace/metric은 OTel Collector를 쓰는 게 자연스럽지만, logs는 일단 패스. 6개월 더 보고 다시 평가하기로 했다.

이 부분은 솔직히 내가 자신 없다. 우리 평가 시점 이후 OTel Collector logs가 많이 좋아졌을 수도 있고, 다른 팀들 후기를 들어보면 잘 쓰는 곳도 꽤 있는 것 같다. 그래서 "Vector/Fluent Bit가 더 낫다"가 아니라 "그 시점에 우리는 이걸 골랐다" 정도로만 받아주시면 될 것 같다.

정리하지 않은 정리

  • 트랜스폼이 복잡하면 Vector. VRL이 정말 좋다.
  • 노드당 메모리가 빠듯한 환경이면 Fluent Bit.
  • 둘 중 하나만 골라야 한다면, 우리 같은 케이스에선 Vector를 골랐을 것 같다. 다만 노드 수가 수백 대 단위로 늘어나면 또 계산이 달라진다.
  • 하이브리드는 도구가 늘어나는 단점이 있지만, 우리에겐 그게 합리적이었다.

혹시 비슷한 고민하는 분 있으면, 트랜스폼이 얼마나 복잡한지부터 보시는 걸 추천한다. 거기서 거의 다 갈린다.

반응형