OTel, Clickhouse 데이터 Delay 해결하기

zuckerfrei·2025년 12월 22일

Observability

목록 보기
9/10

무엇을 했는지

Observability 메인 대시보드 중 일부 그래프에서 최신 데이터가 누락되는 현상이 발생했다.

otel collector exporter dsn 설정 및 clickhouse async_insert 설정으로 해결한 내용을 정리하고 공유한다.


이슈 사항

솔루션의 메인 대시보드 중 일부 그래프에서 최신 데이터가 누락되는 현상이 발생

위의 사진은 오후 3시 23분 확인한 그래프
하지만 3시 20분까지의 데이터만 시각화되어 표시되는 상황

오후 3시 28분쯤 되어 그래프를 다시 새로고침하면, 3시 20분까지의 데이터가 정상적으로 표시되는 것을 확인할 수 있었다.

실시간으로 장애 원인 분석 및 모니터링을 하기 위한 솔루션인데, 실시간성이 떨어진다는 점에서 큰 문제이다.


원인

맨 처음에 추정한 원인은 otel collector gw → clickhouse 적재 과정에서의 병목 현상이었다.

연결된 agent 클러스터의 숫자가 증가할수록 otel collector gw는 메모리 큐가 증가하여 부하를 받기 마련이고, clickhouse에서 insert 쿼리를 빠르게 쳐내지 못하는 것이 아닌가 의심했다.

그러나 이런 현상이 발생하는 확실한 이유를 알지 못했다.


클로드에게 이것저것 질의응답을 하며 쿼리 지연 시간과 설정을 살펴보았고

어떤 설정이 문제를 발생시켰는지, 어떻게 해결할 수 있는 것인지 알 수 있었다.


데이터 적재 지연이 발생할 때는 아래와 같이 insert 쿼리에서 지연 시간이 존재했음

동기 INSERT: 매번 4-5초 대기 (디스크 쓰기 완료까지)

SELECT
    event_time,
    query_duration_ms,
    written_rows,
    substring(query, 1, 100) AS query_preview
FROM system.query_log
WHERE (query_kind = 'Insert') AND has(tables, 'signoz_metrics.samples_v4') AND (event_time >= (now() - toIntervalMinute(5)))
ORDER BY event_time DESC
LIMIT 20
FORMAT Pretty

쿼리 결과

Query id: 70ab1aac-38e1-494c-8540-84ca87025120

┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃          event_time ┃ query_duration_ms ┃ written_rows ┃ query_preview                                                                                        ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 2025-12-22 07:18:46 │              4577 │        91091 │ INSERT INTO signoz_metrics.distributed_samples_v4 (env, temporality, metric_name, fingerprint, unix_ │
├─────────────────────┼───────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 2025-12-22 07:18:41 │              4339 │         6785 │ INSERT INTO signoz_metrics.distributed_samples_v4 (env, temporality, metric_name, fingerprint, unix_ │
└─────────────────────┴───────────────────┴──────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃          event_time ┃ query_duration_ms ┃ written_rows ┃ query_preview                                                                                        ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 2025-12-22 07:18:37 │              4541 │         7085 │ INSERT INTO signoz_metrics.distributed_samples_v4 (env, temporality, metric_name, fingerprint, unix_ │
└─────────────────────┴───────────────────┴──────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃          event_time ┃ query_duration_ms ┃ written_rows ┃ query_preview                                                                                        ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 2025-12-22 07:18:32 │              4339 │         7376 │ INSERT INTO signoz_metrics.distributed_samples_v4 (env, temporality, metric_name, fingerprint, unix_ │
├─────────────────────┼───────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 2025-12-22 07:18:28 │              4448 │         8308 │ INSERT INTO signoz_metrics.distributed_samples_v4 (env, temporality, metric_name, fingerprint, unix_ │
├─────────────────────┼───────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 2025-12-22 07:18:23 │              4494 │         6951 │ INSERT INTO signoz_metrics.distributed_samples_v4 (env, temporality, metric_name, fingerprint, unix_ │
├─────────────────────┼───────────────────┼──────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤

해결 방법

1. async_insert=1 활성화

  • async_insert 옵션은 "INSERT를 메모리에 받고 바로 응답할지 (비동기, 1), 디스크에 다 쓰고 응답할지 (동기, 0)를 결정하는 플래그”이다.
  • async_insert=1는 이전 insert 종료여부 상관없이 그냥 배치 도는대로 insert 때려박는 것이고
  • async_insert=0는 이전 insert가 종료되길 기다리고 다음 insert를 호출한다.

  • async_insert 설정은 config.xml이 아니라, users 파일로 관리한다.
  • 이렇게 설정하면, 서버 기본값으로 모든 연결에 적용
# /etc/clickhouse-server/users.xml 수정
# 설정 후 systemctl restart clickhouse-server

<clickhouse>
    <!-- See also the files in users.d directory where the settings can be overridden. -->

    <!-- Profiles of settings. -->
    <profiles>
        <!-- Default settings. -->
        <default>
            <!-- async_insert 설정 추가 20251222 -->
            <async_insert>1</async_insert>
            <wait_for_async_insert>0</wait_for_async_insert>
            <async_insert_max_data_size>10000000</async_insert_max_data_size>
            <async_insert_busy_timeout_ms>1000</async_insert_busy_timeout_ms>
        </default>
        ...
    </profiles>
    ...
</clickhouse>
  • ClickHouse가 INSERT를 메모리 버퍼에 받고 즉시 응답 (0-17ms)
  • 실제 디스크 쓰기는 백그라운드에서 일괄 처리 (1초마다)
  • OTEL Collector는 블로킹 없이 계속 전송 가능하다고 한다.

2. OTEL Collector DSN에 async_insert 파라미터 추가

async_insert 활성화는 otel collector dsn에서도 설정이 가능하다.
이렇게 설정하면, 해당 클라이언트(otel collector)만 async_insert 옵션을 사용한다.

clickhouse와 otel collector에서 둘 다 명시적으로 확실하게 설정해도 괜찮다.

# BEFORE
signozclickhousemetrics:
  dsn: tcp://${env:CLICKHOUSE_USER}:${env:CLICKHOUSE_PASSWORD}@${env:CLICKHOUSE_HOST}:${env:CLICKHOUSE_PORT}/${env:CLICKHOUSE_DATABASE}

# AFTER
signozclickhousemetrics:
  dsn: tcp://${env:CLICKHOUSE_USER}:${env:CLICKHOUSE_PASSWORD}@${env:CLICKHOUSE_HOST}:${env:CLICKHOUSE_PORT}/${env:CLICKHOUSE_DATABASE}?async_insert=1&wait_for_async_insert=0
  
# async_insert=1 → 비동기 insert 활성화
# wait_for_async_insert=0 → insert 완료 대기 안 함

이 설정으로 otel collector가 ClickHouse에 연결할 때 async_insert 모드 사용하게 된다.

DSN = Data Source Name / 데이터베이스 연결 문자열을 의미
예를 들어 tcp://user:password@host:port/database?옵션들


3. OTEL Collector Gateway Batch 크기 10배 증가 (1K → 10K)

  • 배치 사이즈 크기를 크게 늘려서 INSERT 횟수를 대폭 감소시켰다.
    • 200회/분 → 20회/분 (90% 감소)
    • BEFORE (배치 1000): 6785, 7085, 7376, 8308 rows → 지연 쿼리에서 확인 가능
      AFTER (배치 10000): 57952, 90406, 3598 rows → write_rows 값으로 확인 가능
  • 네트워크 오버헤드 및 ClickHouse 파싱 부하 감소

4. sending_queue=true

  • OTEL Collector 내부에서도 비동기 전송하도록 설정한다.
  • 이중 버퍼링으로 안정성이 향상된다고 한다.

5. 추가적으로 프론트엔드 쿼리 수정

  • 5분 집계가 아니라, 1분 집계로 변경하여 실시간성 확보

결과

이러한 작업으로 Clickhouse Insert 응답 시간이 4~5초 → 0-17ms로 약 99.6% 개선되었고,

메인 대시보드에서 5분 이상 텀이 발생하던 데이터 딜레이는 1분 이내로 약 80% 개선되었다.

그래서 결과적으로 모니터링 대시보드의 실시간성을 확보할 수 있게 되었다.


오후 5시 42분 확인한 그래프의 모습이다.
1분 전인 5시 41분까지의 그래프가 정상적으로 그려지고 있다.

profile
무설탕 음료를 좋아합니다

0개의 댓글