> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Grafana와 ClickHouse를 사용한 관측성

> Grafana와 ClickHouse를 사용한 관측성

export const Image = ({img, alt, size = "lg"}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} />
      </Frame>
    </div>;
};

Grafana는 ClickHouse의 관측성 데이터를 시각화하는 데 권장되는 도구입니다. 이는 Grafana용 공식 ClickHouse 플러그인을 통해 구현됩니다. 설치 지침은 [여기](/docs/ko/integrations/connectors/data-visualization/grafana/index)에서 확인할 수 있습니다.

플러그인 V4에서는 새로운 쿼리 빌더 환경에서 로그와 트레이스를 핵심적으로 다룰 수 있습니다. 이를 통해 SRE가 SQL 쿼리를 직접 작성할 필요를 줄이고, SQL 기반 관측성을 더 간편하게 구현할 수 있어 이 새로운 패러다임의 발전을 앞당깁니다.
이를 위해 OpenTelemetry(OTel)를 플러그인의 중심에 배치했습니다. 앞으로 수년간 이것이 SQL 기반 관측성의 토대이자 데이터 수집 방식의 기반이 될 것이라고 보기 때문입니다.

<div id="open-telemetry-integration">
  ## OpenTelemetry 통합
</div>

Grafana에서 ClickHouse 데이터 소스를 구성할 때 플러그인에서 로그와 트레이스에 사용할 기본 데이터베이스와 테이블, 그리고 해당 테이블이 OTel 스키마를 따르는지 여부를 지정할 수 있습니다. 이를 통해 플러그인은 Grafana에서 로그와 트레이스를 올바르게 렌더링하는 데 필요한 컬럼을 반환할 수 있습니다. 기본 OTel 스키마를 변경해 자체 컬럼 이름을 사용하려는 경우 해당 이름을 지정할 수 있습니다. 시간(`Timestamp`), 로그 레벨(`SeverityText`), 메시지 본문(`Body`)과 같은 컬럼에 기본 OTel 컬럼 이름을 사용하면 별도 변경이 필요하지 않습니다.

<Info>
  **HTTP 또는 Native**

  Grafana는 HTTP 또는 Native protocol을 통해 ClickHouse에 연결할 수 있습니다. 후자는 성능상 약간의 이점을 제공하지만, Grafana 사용자가 실행하는 집계 쿼리에서는 그 차이를 체감하기 어려운 경우가 많습니다. 반면 HTTP protocol은 일반적으로 프록시로 구성하고 내부 동작을 검사하기가 더 간단합니다.
</Info>

로그를 올바르게 렌더링하려면 Logs 구성에 시간, 로그 레벨, 메시지 컬럼이 필요합니다.

Traces 구성은 약간 더 복잡합니다(전체 목록은 [여기](/docs/ko/reference/engines/table-engines/mergetree-family/mergetree#mergetree-data-storage)에서 확인할 수 있습니다). 여기서 필요한 컬럼은 전체 trace profile을 구성하는 후속 쿼리를 추상화할 수 있도록 하기 위한 것입니다. 이러한 쿼리는 데이터가 OTel과 유사한 방식으로 구조화되어 있다고 가정하므로, 표준 스키마에서 크게 벗어난 경우 이 기능을 활용하려면 뷰를 사용해야 합니다.

<Image img="https://mintcdn.com/private-7c7dfe99/xE8TEsdF6028Tf3x/images/use-cases/observability/observability-15.webp?fit=max&auto=format&n=xE8TEsdF6028Tf3x&q=85&s=24e82a004cc0bf36891aaf3fc1b09c7d" alt="Connector config" size="sm" width="392" height="949" data-path="images/use-cases/observability/observability-15.webp" />

구성이 완료되면 [Grafana Explore](https://grafana.com/docs/grafana/latest/explore/)로 이동해 로그와 트레이스 검색을 시작할 수 있습니다.

<div id="logs">
  ## 로그
</div>

로그에 대한 Grafana 요구 사항을 충족하는 경우, 쿼리 빌더에서 `Query Type: Log`를 선택하고 `Run Query`를 클릭할 수 있습니다. 그러면 쿼리 빌더가 로그를 나열하는 쿼리를 생성하고, 예를 들어 다음과 같이 표시되도록 합니다.

```sql theme={null}
SELECT Timestamp as timestamp, Body as body, SeverityText as level, TraceId as traceID FROM "default"."otel_logs" WHERE ( timestamp >= $__fromTime AND timestamp <= $__toTime ) ORDER BY timestamp DESC LIMIT 1000
```

<Image img="https://mintcdn.com/private-7c7dfe99/xE8TEsdF6028Tf3x/images/use-cases/observability/observability-16.webp?fit=max&auto=format&n=xE8TEsdF6028Tf3x&q=85&s=90fc2e3110b9ca839be57b1dca3d2032" alt="커넥터 로그 구성" size="lg" border width="1600" height="831" data-path="images/use-cases/observability/observability-16.webp" />

쿼리 빌더를 사용하면 SQL을 직접 작성하지 않고도 쿼리를 손쉽게 수정할 수 있습니다. 키워드가 포함된 로그를 찾는 것을 비롯한 필터링 작업도 쿼리 빌더에서 수행할 수 있습니다. 더 복잡한 쿼리를 작성하려면 SQL Editor로 전환할 수 있습니다. 적절한 컬럼이 반환되고 Query Type으로 `logs`를 선택하면 결과가 로그로 렌더링됩니다. 로그 렌더링에 필요한 컬럼은 [여기](https://grafana.com/developers/plugin-tools/tutorials/build-a-logs-data-source-plugin#logs-data-frame-format)에 나와 있습니다.

<div id="logs-to-traces">
  ### 로그에서 트레이스로
</div>

로그에 트레이스 ID가 포함되어 있으면 특정 로그 항목에서 해당 트레이스로 바로 이동할 수 있습니다.

<Image img="https://mintcdn.com/private-7c7dfe99/xE8TEsdF6028Tf3x/images/use-cases/observability/observability-17.webp?fit=max&auto=format&n=xE8TEsdF6028Tf3x&q=85&s=a98766c1f967c18f34bd5b916a84e9e4" alt="로그에서 트레이스로" size="lg" border width="1600" height="814" data-path="images/use-cases/observability/observability-17.webp" />

<div id="traces">
  ## 트레이스
</div>

위의 로깅 방식과 마찬가지로, Grafana가 트레이스를 렌더링하는 데 필요한 컬럼이 갖춰져 있으면(예: OTel 스키마 사용) 쿼리 빌더가 필요한 쿼리를 자동으로 작성할 수 있습니다. `Query Type: Traces`를 선택하고 `Run Query`를 클릭하면 다음과 유사한 쿼리가 생성되어 실행됩니다(구성된 컬럼에 따라 달라질 수 있으며, 아래 예시는 OTel 사용을 가정합니다):

```sql theme={null}
SELECT "TraceId" as traceID,
  "ServiceName" as serviceName,
  "SpanName" as operationName,
  "Timestamp" as startTime,
  multiply("Duration", 0.000001) as duration
FROM "default"."otel_traces"
WHERE ( Timestamp >= $__fromTime AND Timestamp <= $__toTime )
  AND ( ParentSpanId = '' )
  AND ( Duration > 0 )
  ORDER BY Timestamp DESC, Duration DESC LIMIT 1000
```

이 쿼리는 Grafana에서 필요한 컬럼 이름을 반환하며, 아래와 같이 트레이스 테이블을 표시합니다. SQL을 작성하지 않고도 duration이나 다른 컬럼을 기준으로 필터링할 수 있습니다.

<Image img="https://mintcdn.com/private-7c7dfe99/xE8TEsdF6028Tf3x/images/use-cases/observability/observability-18.webp?fit=max&auto=format&n=xE8TEsdF6028Tf3x&q=85&s=8f10e573878b92c7dc021bce0144193d" alt="Traces" size="lg" border width="1600" height="773" data-path="images/use-cases/observability/observability-18.webp" />

더 복잡한 쿼리를 작성하려는 사용자는 `SQL Editor`로 전환할 수 있습니다.

<div id="view-trace-details">
  ### 트레이스 세부 정보 보기
</div>

위와 같이 트레이스 ID는 클릭 가능한 링크로 표시됩니다. 트레이스 ID를 클릭하면 `View Trace` 링크를 통해 관련 스팬을 볼 수 있습니다. 그러면 필요한 구조로 스팬을 가져오기 위해 다음 쿼리(OTel 컬럼을 기준으로 함)를 실행하며, 결과는 워터폴 형태로 표시됩니다.

```sql theme={null}
WITH '<trace_id>' AS trace_id,
  (SELECT min(Start) FROM "default"."otel_traces_trace_id_ts"
    WHERE TraceId = trace_id) AS trace_start,
  (SELECT max(End) + 1 FROM "default"."otel_traces_trace_id_ts"
    WHERE TraceId = trace_id) AS trace_end
SELECT "TraceId" AS traceID,
  "SpanId" AS spanID,
  "ParentSpanId" AS parentSpanID,
  "ServiceName" AS serviceName,
  "SpanName" AS operationName,
  "Timestamp" AS startTime,
  multiply("Duration", 0.000001) AS duration,
  arrayMap(key -> map('key', key, 'value',"SpanAttributes"[key]),
  mapKeys("SpanAttributes")) AS tags,
  arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]),
  mapKeys("ResourceAttributes")) AS serviceTags
FROM "default"."otel_traces"
WHERE traceID = trace_id
  AND startTime >= trace_start
  AND startTime <= trace_end
LIMIT 1000
```

<Note>
  위 쿼리는 트레이스 ID 조회를 수행할 때 materialized view `otel_traces_trace_id_ts`를 사용합니다. 자세한 내용은 [쿼리 가속화 - 조회를 위한 materialized view 사용](/docs/ko/guides/use-cases/observability/build-your-own/schema-design#using-materialized-views-incremental--for-fast-lookups)을 참조하십시오.
</Note>

<Image img="https://mintcdn.com/private-7c7dfe99/xE8TEsdF6028Tf3x/images/use-cases/observability/observability-19.webp?fit=max&auto=format&n=xE8TEsdF6028Tf3x&q=85&s=04244093d27ef110abd1f77008221169" alt="트레이스 세부 정보" size="lg" border width="1600" height="838" data-path="images/use-cases/observability/observability-19.webp" />

<div id="traces-to-logs">
  ### 트레이스에서 로그로
</div>

로그에 트레이스 ID가 포함되어 있으면 트레이스에서 해당 로그로 이동할 수 있습니다. 로그를 보려면 트레이스 ID를 클릭한 다음 `View Logs`를 선택하세요. 그러면 기본 OTel 컬럼을 기준으로 다음 쿼리가 실행됩니다.

```sql theme={null}
SELECT Timestamp AS "timestamp",
  Body AS "body", SeverityText AS "level",
  TraceId AS "traceID" FROM "default"."otel_logs"
WHERE ( traceID = '<trace_id>' )
ORDER BY timestamp ASC LIMIT 1000
```

<Image img="https://mintcdn.com/private-7c7dfe99/xE8TEsdF6028Tf3x/images/use-cases/observability/observability-20.webp?fit=max&auto=format&n=xE8TEsdF6028Tf3x&q=85&s=54cd6c6657ebe1efc36d98ee00f5bd49" alt="트레이스에서 로그로" size="lg" border width="1600" height="838" data-path="images/use-cases/observability/observability-20.webp" />

<div id="dashboards">
  ## 대시보드
</div>

Grafana에서 ClickHouse 데이터 소스를 사용해 대시보드를 만들 수 있습니다. 자세한 내용은 Grafana 및 ClickHouse [데이터 소스 문서](https://github.com/grafana/clickhouse-datasource)를 참고하는 것이 좋습니다. 특히 [매크로 개념](https://github.com/grafana/clickhouse-datasource?tab=readme-ov-file#macros)과 [변수](https://grafana.com/docs/grafana/latest/dashboards/variables/)를 참고하십시오.

이 플러그인은 바로 사용할 수 있는 여러 대시보드를 제공하며, 여기에는 OTel 사양을 준수하는 로깅 및 트레이싱 데이터용 예시 대시보드인 "Simple ClickHouse OTel dashboarding"도 포함됩니다. 이 대시보드를 사용하려면 OTel의 기본 컬럼 이름 규칙을 따라야 하며, 데이터 소스 구성에서 설치할 수 있습니다.

<Image img="https://mintcdn.com/private-7c7dfe99/yqUlQ9JxYel6WYEx/images/use-cases/observability/observability-21.webp?fit=max&auto=format&n=yqUlQ9JxYel6WYEx&q=85&s=42e48310a2e3e4217da180c40d0be69b" alt="대시보드" size="lg" border width="1600" height="821" data-path="images/use-cases/observability/observability-21.webp" />

아래에는 시각화를 만들 때 유용한 몇 가지 간단한 팁을 제공합니다.

<div id="time-series">
  ### 시계열
</div>

통계와 함께 선 차트는 관측성 사용 사례에서 가장 흔히 사용되는 시각화 방식입니다. 쿼리가 `time`이라는 이름의 `datetime`과 숫자 컬럼을 반환하면 ClickHouse 플러그인은 자동으로 선 차트를 표시합니다. 예시:

```sql theme={null}
SELECT
 $__timeInterval(Timestamp) as time,
 quantile(0.99)(Duration)/1000000 AS p99
FROM otel_traces
WHERE
 $__timeFilter(Timestamp)
 AND ( Timestamp  >= $__fromTime AND Timestamp <= $__toTime )
GROUP BY time
ORDER BY time ASC
LIMIT 100000
```

<Image img="https://mintcdn.com/private-7c7dfe99/yqUlQ9JxYel6WYEx/images/use-cases/observability/observability-22.webp?fit=max&auto=format&n=yqUlQ9JxYel6WYEx&q=85&s=6f40939f88e20a2bee34789b2b25a40c" alt="시계열" size="lg" border width="1457" height="854" data-path="images/use-cases/observability/observability-22.webp" />

<div id="multi-line-charts">
  ### 다중 선 차트
</div>

다음 조건을 충족하면 쿼리가 다중 선 차트로 자동 렌더링됩니다:

* 필드 1: `time` 별칭(alias)을 가진 datetime 필드
* 필드 2: 그룹화할 값. String이어야 합니다.
* 필드 3+: 메트릭 값

예시:

```sql theme={null}
SELECT
  $__timeInterval(Timestamp) as time,
  ServiceName,
  quantile(0.99)(Duration)/1000000 AS p99
FROM otel_traces
WHERE $__timeFilter(Timestamp)
AND ( Timestamp  >= $__fromTime AND Timestamp <= $__toTime )
GROUP BY ServiceName, time
ORDER BY time ASC
LIMIT 100000
```

<Image img="https://mintcdn.com/private-7c7dfe99/yqUlQ9JxYel6WYEx/images/use-cases/observability/observability-23.webp?fit=max&auto=format&n=yqUlQ9JxYel6WYEx&q=85&s=ff0e60493ff868ad7b50172af68ce95f" alt="여러 선이 있는 차트" size="lg" border width="1458" height="967" data-path="images/use-cases/observability/observability-23.webp" />

<div id="visualizing-geo-data">
  ### Geo 데이터 시각화
</div>

이전 섹션에서는 IP 딕셔너리를 사용해 관측성 데이터를 Geo 좌표로 보강하는 방법을 살펴보았습니다. `latitude` 및 `longitude` 컬럼이 있다고 가정하면 `geohashEncode` 함수를 사용해 관측성 데이터를 시각화할 수 있습니다. 이렇게 하면 Grafana Geo Map 차트와 호환되는 geohash가 생성됩니다. 예시 쿼리와 시각화는 아래와 같습니다:

```sql theme={null}
WITH coords AS
        (
        SELECT
                Latitude,
                Longitude,
                geohashEncode(Longitude, Latitude, 4) AS hash
        FROM otel_logs_v2
        WHERE (Longitude != 0) AND (Latitude != 0)
        )
SELECT
        hash,
        count() AS heat,
        round(log10(heat), 2) AS adj_heat
FROM coords
GROUP BY hash
```

<Image img="https://mintcdn.com/private-7c7dfe99/yqUlQ9JxYel6WYEx/images/use-cases/observability/observability-24.webp?fit=max&auto=format&n=yqUlQ9JxYel6WYEx&q=85&s=852dd6bd731d2beb1d294caabdce595f" alt="Geo 데이터 시각화" size="lg" border width="1600" height="817" data-path="images/use-cases/observability/observability-24.webp" />
