OpenTelemetry로 ClickHouse 추적하기

OpenTelemetry로 ClickHouse 추적하기

ClickHouse에서 OpenTelemetry 표준을 통해 트레이스를 수집하는 방법을 소개해요. 트레이스 컨텍스트 전달, 분산 쿼리 스팬, Keeper 요청 추적, Zipkin 연동 예시까지 다뤄요.

출처: 문서

본문

OpenTelemetry는 분산 애플리케이션에서 트레이스와 메트릭을 수집하기 위한 오픈 표준이에요. ClickHouse는 OpenTelemetry를 일부 지원해요.

ClickHouse에 트레이스 컨텍스트 공급하기

ClickHouse는 W3C 권장사항에 설명된 대로 트레이스 컨텍스트 HTTP 헤더를 받아들여요. 또한 ClickHouse 서버 간 또는 클라이언트와 서버 간 통신에 사용되는 네이티브 프로토콜로도 트레이스 컨텍스트를 받아들여요. 수동 테스트를 위해, Trace Context 권장사항을 따르는 트레이스 컨텍스트 헤더를 --opentelemetry-traceparent--opentelemetry-tracestate 플래그를 사용해 clickhouse-client에 전달할 수 있어요. 상위 트레이스 컨텍스트가 제공되지 않거나 제공된 트레이스 컨텍스트가 위 W3C 표준을 따르지 않는 경우, ClickHouse는 opentelemetry_start_trace_probability 설정으로 제어되는 확률로 새 트레이스를 시작할 수 있어요.

트레이스 컨텍스트 전파

트레이스 컨텍스트는 다음 경우에 다운스트림 서비스로 전파돼요:

  • Distributed 테이블 엔진을 사용할 때와 같이 원격 ClickHouse 서버에 대한 쿼리
  • url 테이블 함수. 트레이스 컨텍스트 정보가 HTTP 헤더로 전송돼요.

분산 쿼리의 스팬

분산 SELECT의 경우 원격 샤드에서의 모든 읽기는 RemoteQueryExecutor::execute 스팬으로 덮여져요. 이 스팬들은 쿼리 프래그먼트를 식별하는 속성을 가져요:

  • clickhouse.clusterclickhouse.shard_num — 실행자가 읽어오는 클러스터와 샤드
  • clickhouse.query_idclickhouse.initial_query_id — initiator 관점의 쿼리 ID
  • clickhouse.target_host — 수립된 연결들의 주소

이런 스팬의 status_code는 프래그먼트가 어떻게 끝났는지 알려줘요:

  • OK — 샤드가 전체 결과를 전달했어요.
  • ERROR — 프래그먼트가 실패했어요: 샤드가 예외를 반환했거나, initiator가 데이터를 읽지 못했거나, 샤드 취소가 실패했어요.
  • UNSET — 성공도 실패도 아니에요. 이런 스팬은 더 많은 컨텍스트를 주는 속성을 갖고 있어요.

Distributed 테이블에 대한 INSERT 쿼리는 DistributedSink에서 동일한 clickhouse.clusterclickhouse.shard_num 속성 키를 가진 유사한 스팬을 만들어요.

ClickHouse Keeper 요청 추적

ClickHouse는 ClickHouse Keeper 요청(ZooKeeper 호환 조정 서비스)에 대해 OpenTelemetry 트레이싱을 지원해요. 이 기능은 클라이언트 요청 제출부터 서버 측 처리까지 Keeper 작업의 수명 주기에 대한 상세한 가시성을 제공해요.

Keeper 트레이싱 활성화

Keeper 요청 트레이싱을 활성화하려면 ZooKeeper/Keeper 클라이언트 설정에 다음을 구성해요:

<clickhouse>
    <zookeeper>
        <node>
            <host>keeper1</host>
            <port>9181</port>
        </node>
        <!-- Enable OpenTelemetry tracing context propagation -->
        <pass_opentelemetry_tracing_context>true</pass_opentelemetry_tracing_context>
    </zookeeper>
</clickhouse>

Keeper 스팬 유형

트레이싱이 활성화되면 ClickHouse는 클라이언트 측과 서버 측 Keeper 작업에 모두 스팬을 만들어요.

클라이언트 측 스팬:

  • zookeeper.create — 새 노드 만들기
  • zookeeper.get — 노드 데이터 가져오기
  • zookeeper.set — 노드 데이터 설정
  • zookeeper.remove — 노드 제거
  • zookeeper.list — 자식 노드 나열
  • zookeeper.exists — 노드 존재 여부 확인
  • zookeeper.multi — 여러 작업을 원자적으로 실행
  • zookeeper.client.requests_queue — 전송 전 요청을 큐에 넣는 데 걸린 시간

서버 측 스팬(Keeper):

  • keeper.receive_request — 클라이언트로부터 요청 수신 및 파싱
  • keeper.dispatcher.requests_queue — dispatcher에서의 요청 큐잉
  • keeper.write.pre_commit — Raft 커밋 전 쓰기 요청 사전 처리
  • keeper.write.commit — Raft 커밋 후 쓰기 요청 처리
  • keeper.read.wait_for_write — 의존하는 쓰기를 기다리는 읽기 요청
  • keeper.read.process — 읽기 요청 처리
  • keeper.dispatcher.responses_queue — dispatcher에서의 응답 큐잉
  • keeper.send_response — 클라이언트에 응답 전송

샘플링과 성능

트레이싱 오버헤드를 관리하기 위해 Keeper는 동적 샘플링을 구현해요. 샘플링 비율은 요청 크기에 따라 1/10,000과 1/10 사이에서 자동으로 조정돼요. 모든 요청(샘플링된 것과 되지 않은 것 모두)의 지속 시간은 성능 모니터링을 위해 히스토그램 메트릭으로 기록돼요.

ClickHouse 자체 추적

ClickHouse는 각 쿼리와 쿼리 실행 단계 중 일부(쿼리 계획, 분산 쿼리 등)에 대해 trace span을 만들어요. 트레이싱 정보가 유용하려면 JaegerPrometheus처럼 OpenTelemetry를 지원하는 모니터링 시스템으로 내보내야 해요. ClickHouse는 특정 모니터링 시스템에 대한 의존성을 피하고, 대신 시스템 테이블을 통해서만 트레이싱 데이터를 제공해요. 표준에서 요구하는 OpenTelemetry 트레이스 스팬 정보는 system.opentelemetry_span_log 테이블에 저장돼요. 이 테이블은 서버 설정에서 활성화되어야 하는데, 기본 설정 파일 config.xmlopentelemetry_span_log 요소를 참고해요. 기본적으로 활성화되어 있어요. 태그나 속성은 키와 값을 담은 두 개의 병렬 배열로 저장돼요. 이들을 다루려면 ARRAY JOIN을 사용해요.

Log-query-settings

log_query_settings 설정은 쿼리 실행 중 쿼리 설정 변경을 기록할 수 있게 해줘요. 활성화하면 쿼리 설정에 대한 변경 사항이 OpenTelemetry 스팬 로그에 기록돼요. 이 기능은 쿼리 성능에 영향을 줄 수 있는 설정 변경을 추적해야 하는 프로덕션 환경에서 특히 유용해요.

모니터링 시스템과의 통합

현재 ClickHouse에서 모니터링 시스템으로 트레이싱 데이터를 내보내는 준비된 도구는 없어요. 테스트를 위해 system.opentelemetry_span_log 테이블 위에 URL 엔진을 가진 materialized view로 내보내기를 설정해서, 들어오는 로그 데이터를 트레이스 수집기의 HTTP 엔드포인트로 푸시할 수 있어요. 예를 들어 http://localhost:9411에서 실행 중인 Zipkin 인스턴스에 최소 스팬 데이터를 Zipkin v2 JSON 형식으로 푸시하려면:

CREATE MATERIALIZED VIEW default.zipkin_spans
ENGINE = URL('http://127.0.0.1:9411/api/v2/spans', 'JSONEachRow')
SETTINGS output_format_json_named_tuples_as_objects = 1,
    output_format_json_array_of_rows = 1 AS
SELECT
    lower(hex(trace_id)) AS traceId,
    CASE WHEN parent_span_id = 0 THEN '' ELSE lower(hex(parent_span_id)) END AS parentId,
    lower(hex(span_id)) AS id,
    operation_name AS name,
    start_time_us AS timestamp,
    finish_time_us - start_time_us AS duration,
    cast(tuple('clickhouse'), 'Tuple(serviceName text)') AS localEndpoint,
    cast(tuple(
        attribute.values[indexOf(attribute.names, 'db.statement')]),
        'Tuple("db.statement" text)') AS tags
FROM system.opentelemetry_span_log

오류가 발생하면 오류가 발생한 로그 데이터 부분은 조용히 유실돼요. 데이터가 도착하지 않으면 서버 로그에서 오류 메시지를 확인해요.

관련 콘텐츠

더 알아보기 (Learn more)