자체 운영 PostgreSQL

pg_tracing 확장을 사용하여 PostgreSQL이 직접 스팬을 전송하도록 구성합니다. 이미지 빌드, 서버 설정, 검증 절차와 앱 키 제약을 설명합니다.

데이터베이스 프로세스를 직접 운영하는 환경에서는 pg_tracing 확장을 설치할 수 있습니다. PostgreSQL이 애플리케이션 트레이스에 자체 실행계획을 포함시키므로, 쿼리 지연을 확인하는 데 그치지 않고 인덱스를 사용하지 않은 순차 스캔과 같은 원인까지 확인할 수 있습니다.

postgres-telemetry

동작 방식

pg_tracing은 애플리케이션이 쿼리에 SQLCommenter 주석으로 전달한 W3C traceparent를 해석합니다. 해당 트레이스의 하위 스팬으로 서버 측 스팬을 생성하며, 전송은 백그라운드 워커가 OTLP로 직접 수행합니다. SDK를 경유하지 않습니다.

스팬은 두 계층으로 생성됩니다. track이 생성하는 문장 단위 스팬과 planstate_spans가 생성하는 실행계획 노드 단위 스팬입니다. 후자를 비활성화하면 SeqScan, Hash Join과 같은 노드는 수집되지 않고 문장 전체의 소요 시간만 기록됩니다.

요구 사항

항목
PostgreSQL14, 15, 16 — 17은 미지원
확장pg_tracing (DataDog)
빌드서버 헤더를 대상으로 컴파일하는 C 확장이므로 postgresql-server-dev-<버전>libcurl이 필요합니다
런타임OTLP 익스포터가 libcurl로 전송합니다. https 엔드포인트를 사용하는 경우 ca-certificates가 필요합니다

이미지 빌드

확장은 배포판 패키지로 제공되지 않으므로 직접 빌드해야 합니다. Sophonz는 다음 Dockerfile로 빌드한 이미지를 사용합니다.

Dockerfile
FROM postgres:16-bookworm AS build
ARG PG_TRACING_REF=v0.1.3
 
RUN set -eux; \
    apt-get update; \
    apt-get install -y --no-install-recommends \
        build-essential git ca-certificates postgresql-server-dev-16 \
        libcurl4-openssl-dev; \
    git clone --depth 1 --branch "${PG_TRACING_REF}" \
        https://github.com/DataDog/pg_tracing.git /tmp/pg_tracing; \
    make -C /tmp/pg_tracing install; \
    rm -rf /var/lib/apt/lists/* /tmp/pg_tracing
 
FROM postgres:16-bookworm
RUN set -eux; \
    apt-get update; \
    apt-get install -y --no-install-recommends libcurl4 ca-certificates; \
    rm -rf /var/lib/apt/lists/*
 
COPY --from=build /usr/lib/postgresql/16/lib/pg_tracing.so /usr/lib/postgresql/16/lib/
COPY --from=build /usr/share/postgresql/16/extension/pg_tracing* /usr/share/postgresql/16/extension/

Debian 기반 이미지를 사용하는 이유는 pgxs 툴체인이 지원되는 환경이기 때문입니다. Alpine에서는 헤더와 툴체인을 별도로 구성해야 합니다.

서버 설정

shared_preload_libraries는 서버 시작 시점에 로드되어야 하므로 서버 인자로 전달합니다.

postgres 서버 인자
- -c
- shared_preload_libraries=pg_tracing
- -c
- compute_query_id=on
- -c
- pg_tracing.track=all
- -c
- pg_tracing.planstate_spans=on
- -c
- pg_tracing.otel_naptime=2000
- -c
- pg_tracing.otel_service_name=my-postgres
- -c
- pg_tracing.otel_endpoint=https://in.sophonz.ai/v1/traces
설정의미
shared_preload_libraries확장을 로드합니다. 서버 재시작이 필요합니다
compute_query_id스팬에 안정적인 쿼리 ID를 부여하여 동일한 쿼리를 그룹화할 수 있게 합니다
trackall은 최상위 문과 중첩 문을 모두 포함합니다
planstate_spans실행계획 노드를 개별 스팬으로 기록합니다
otel_naptime전송 주기(ms)
otel_service_name트레이스에 표시될 서비스 이름
otel_endpointOTLP 트레이스 엔드포인트 (경로 포함)

설치 후 데이터베이스에서 확장을 활성화합니다.

CREATE EXTENSION IF NOT EXISTS pg_tracing;

샘플링

기본값은 sample_rate = 0, caller_sample_rate = 1입니다. PostgreSQL은 샘플링된 traceparent와 함께 전달된 쿼리만 추적하며, 그 외의 쿼리는 추적하지 않습니다. 데이터베이스가 독자적으로 샘플링을 결정하지 않으므로, 애플리케이션 트레이스에 포착된 요청은 데이터베이스 스팬을 함께 포함합니다.

sample_rate를 상향하면 애플리케이션과 무관하게 데이터베이스가 자체적으로 추적을 시작합니다. 이때 생성되는 스팬은 상위 스팬이 없으므로 별도의 트레이스로 기록됩니다.

애플리케이션 설정

PostgreSQL이 traceparent를 해석하려면 애플리케이션이 쿼리에 이를 포함하여 전달해야 합니다. 이 역할을 하는 것이 SQLCommenter입니다. 특정 언어의 기능이 아니라 여러 OpenTelemetry 구현이 공유하는 규약이지만, 기본값으로 켜져 있지 않으므로 명시적으로 활성화해야 합니다.

Node.js SDK에서는 instrumentations 옵션으로 하위 계측에 전달합니다.

init({
  service: 'my-api',
  apiKey: process.env.SOPHONZ_API_KEY,
  instrumentations: {
    '@opentelemetry/instrumentation-pg': {
      addSqlCommenterCommentToQueries: true,
    },
  },
});

이 설정이 없으면 쿼리에 주석이 붙지 않고, PostgreSQL은 추적할 근거를 받지 못합니다. 확장은 정상 동작하지만 스팬은 생성되지 않습니다.

CAUTION — 이름 있는 프리페어드 스테이트먼트를 사용하는 경우

주석에는 쿼리마다 다른 트레이스 ID가 들어가므로 쿼리 텍스트가 매번 달라집니다. 문장에 이름을 붙여 재사용하는 구성에서는 캐시 효율이 떨어질 수 있습니다. 이름 없는 문장을 쓰는 구성에서는 해당하지 않습니다.

앱 키 제약

WARNING — 자체 호스팅 환경에서는 스팬이 수집되지 않을 수 있습니다

Sophonz 수집기는 service.key 리소스 속성으로 테넌트를 판별하며, 확인되지 않은 경우 해당 리소스를 폐기합니다. 그러나 pg_tracing이 제공하는 설정은 otel_service_nameotel_endpoint 두 가지뿐이므로, 임의의 리소스 속성을 지정할 수 없습니다.

리소스가 폐기된 경우에도 수집기는 200을 반환하므로, PostgreSQL 로그에는 오류가 나타나지 않습니다.

해결 방법은 배포 형태에 따라 다릅니다.

  • Sophonz 관리형 — 이미 적용되어 있습니다. 수집기에 내부 전용 수신 포트를 두고, 수신 시점에 키를 부여합니다.
  • 온프레미스·자체 호스팅 — 도입 시 함께 구성합니다. 엔터프라이즈 개요를 참고하거나 문의해 주세요.

NOTE — 확장 자체를 수정하는 것이 근본적인 해결입니다

리소스 속성을 지정할 수 있는 GUC를 확장에 추가하면 이 우회 구성이 필요하지 않게 됩니다. 이미지를 직접 빌드하고 있으므로 패치 적용이 가능하며, 업스트림에 기여할 수 있는 변경입니다.

검증

확장이 스팬을 생성하여 전송하고 있는지 먼저 확인합니다.

SELECT * FROM pg_tracing_info();

처리된 스팬 수와 마지막 전송 시각을 반환합니다. 이 값이 증가하는데도 트레이스가 비어 있다면, 유실은 데이터베이스가 아니라 수집 경로에서 발생한 것입니다.

실제로 적용된 설정은 pg_settings에서 확인합니다. 오타가 포함된 GUC는 경고 없이 무시되므로, 서버 인자만으로 적용 여부를 판단해서는 안 됩니다.

SELECT name, setting FROM pg_settings
WHERE name LIKE 'pg_tracing%' ORDER BY name;

마지막으로 단일 트레이스에 포함된 서비스를 확인합니다.

SELECT serviceName, count()
FROM sophonz_traces.distributed_sophonz_index_v2
WHERE traceID = '...'
GROUP BY 1;

애플리케이션과 데이터베이스가 모두 조회되어야 합니다. 데이터베이스만 누락된 경우에는 컨텍스트 전파가 아니라 수집 단계의 문제입니다. 전파 여부는 별도로 확인할 수 있습니다. 애플리케이션 응답의 Server-Timing 헤더에 요청 시 전송한 것과 동일한 트레이스 ID가 포함되어 있으면 전파는 정상입니다.