개발 노트/배포와 운영

OpenTelemetry가 기본으로 들어온 시대 — 1인 서비스의 관측 구성 최소안

Roslyn 2026. 9. 8. 09:00
반응형

느리다는 문의를 받고 로그를 열었는데 아무것도 못 찾은 적이 있습니다. 로그에는 요청이 들어왔다는 줄과 응답을 보냈다는 줄이 있었고, 그사이에 무슨 일이 있었는지는 비어 있었습니다. 데이터베이스가 느렸는지, 외부 API가 늦게 답했는지, 제 코드가 오래 걸렸는지 구분할 방법이 없었습니다.

로그 도구를 바꾼다고 해결되는 문제가 아니었습니다. 필요한 것은 하나의 요청이 어디를 거쳐 얼마나 걸렸는지 이어 보는 방법이었고, 그게 분산 추적입니다. .NET 11에서는 ASP.NET Core가 OpenTelemetry 태그를 네이티브로 방출하는 방향으로 가고 있어서, 이 부분을 다시 정리해볼 만한 시점이 됐습니다. 아래 .NET 11 관련 내용은 현재 프리뷰 기준이며 GA까지 바뀔 수 있습니다.

세 축은 각자 답하는 질문이 다릅니다

추적, 메트릭, 로그를 묶어서 부르지만 쓰임은 확실히 다릅니다. 저는 이렇게 구분해서 씁니다.

축답하는 질문주로 보는 때

메트릭지금 정상인가, 언제부터 나빠졌나평소 대시보드와 알림
추적이 요청이 어디에서 오래 걸렸나느린 요청의 원인을 좁힐 때
로그그 순간 정확히 무슨 일이 있었나원인 지점을 찾은 다음

순서가 있습니다. 메트릭으로 이상을 알아채고, 추적으로 구간을 좁히고, 로그로 세부를 확인합니다. 그동안 저는 로그만 가지고 이 셋을 다 하려고 했고, 그래서 앞의 두 단계가 통째로 비어 있었던 겁니다.

네이티브 방출이 공짜로 주는 것

ASP.NET Core가 표준 태그를 직접 붙여 내보내면, 예전 같으면 직접 붙였어야 할 정보들이 별도 계측 없이 따라옵니다. 요청 경로와 라우트 패턴, 상태 코드, 처리 시간 같은 것들입니다. 여기서 중요한 건 이름이 표준을 따른다는 점입니다.

• 도구를 바꿔도 대시보드를 다시 만들지 않습니다: 속성 이름이 표준이면 백엔드를 옮겨도 질의가 그대로 통합니다.

• 라이브러리 계측과 이름이 맞습니다: HTTP 클라이언트, 데이터베이스 드라이버 쪽 계측과 규칙이 같아서 한 화면에서 이어 볼 수 있습니다.

• 직접 만든 태그가 줄어듭니다: 손으로 붙인 이름은 오타와 규칙 불일치가 생기기 마련인데, 그 표면적이 줄어듭니다.

공짜가 아닌 것도 분명합니다. 도메인 정보는 여전히 직접 붙여야 합니다. 어떤 기능에서 발생한 요청인지, 어떤 종류의 작업이었는지 같은 것은 프레임워크가 알 수 없습니다. 저는 여기에만 직접 만든 태그를 씁니다.

어디까지 수집하고 어디서 멈출지

혼자 운영하는 서비스에서 관측 구성이 실패하는 방식은 대개 두 가지입니다. 아예 없거나, 너무 많이 모아서 비용과 잡음에 지치는 경우입니다. 제가 정한 선은 이렇습니다.

• 샘플링: 모든 요청의 추적을 저장할 필요는 없습니다. 정상 요청은 일부만 남기고, 오류와 느린 요청은 가급적 남기는 방향이 실용적입니다. 트래픽이 적을 때는 전부 남겨도 되지만, 늘어난 뒤에 바꾸려면 늦습니다.

• 보관 기간: 추적 데이터는 짧게 둡니다. 대개 문제는 며칠 안에 확인합니다. 반면 메트릭은 추세를 보기 위해 길게 둡니다. 이 둘의 보관 기간을 같게 잡으면 저장 비용이 빠르게 늘어납니다.

• 카디널리티: 이게 가장 위험합니다. 사용자 ID, 세션 ID, 원본 URL 전체처럼 값의 종류가 무한에 가까운 것을 태그로 붙이면 시계열이 폭발합니다. 비용도 오르고 질의도 느려집니다.

• 알림 개수: 처음에는 서너 개면 충분합니다. 오류율, 응답 시간, 서비스 살아 있음 정도입니다. 알림이 많아 무시하기 시작하면 없는 것과 같습니다.

카디널리티는 태그와 속성을 나눠서 다룹니다

제가 쓰는 기준은 단순합니다. 메트릭 태그에는 값의 종류가 정해진 것만 넣고, 값이 많은 정보는 추적의 속성으로 넣습니다. 라우트 패턴과 상태 코드는 종류가 제한적이라 태그로 적당하고, 사용자 ID는 추적 속성으로 남겨두면 필요할 때 찾아볼 수 있습니다.

// 개념 예시 — 옵션 이름은 공식 문서에서 확인하세요
builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("merrytalk-api"))
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(m => m
        .AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddOtlpExporter());

실제 설정은 버전에 따라 달라질 수 있으니 패키지 문서를 함께 보시는 편이 좋습니다. 여기서 보실 것은 형태입니다. 내보내는 곳을 한 군데로 모으고, 서비스 이름을 명확히 붙이는 것이 시작점입니다.

백엔드는 비용과 손이 가는 정도로 고릅니다

수집한 데이터를 어디에 보낼지가 남습니다. 크게 자체 호스팅과 관리형으로 나뉘는데, 1인 운영자 관점에서 비교하면 이렇습니다.

• 자체 호스팅: 사용량이 늘어도 요금이 급격히 오르지 않고 데이터를 직접 통제합니다. 대신 저장소 용량, 업그레이드, 백업까지 제 일이 됩니다. 관측 도구가 죽어서 새벽에 일어나는 상황은 피하고 싶은 종류의 일입니다.

• 관리형: 붙이면 바로 쓰고 운영 부담이 없습니다. 대신 데이터 양에 따라 요금이 오르고, 무엇을 보낼지 통제하지 않으면 청구서로 배우게 됩니다.

• 중간 선택: 수집기는 내 쪽에 두고 저장은 외부로 보내는 구성입니다. 무엇을 걸러 보낼지 내가 정할 수 있어서 비용 통제에 유리합니다.

제 판단 기준은 이 도구를 관리하는 데 매달 몇 시간을 쓸 수 있는가입니다. 시간이 거의 없다면 관리형이 맞고, 대신 샘플링과 카디널리티를 처음부터 조입니다. 서비스 규모가 작을 때는 무료 구간만으로 충분한 경우도 많아서, 시작은 가볍게 하고 늘어나면 다시 정하는 편이 낫습니다.

마치며

메트릭으로 알아채고, 추적으로 좁히고, 로그로 확인합니다. 프레임워크가 표준 태그를 직접 내보내면 이 흐름의 첫 두 단계를 적은 노력으로 세울 수 있습니다. 다만 수집 범위는 처음부터 정해두어야 합니다. 샘플링, 보관 기간, 카디널리티 세 가지를 손대지 않으면 비용과 잡음이 먼저 늘어납니다.

오늘 할 수 있는 한 가지는 가장 느리다고 의심되는 요청 하나를 골라, 그 요청이 지나는 구간을 종이에 그려보는 일입니다. 그림에서 시간을 모르는 구간이 곧 계측이 필요한 자리입니다.

반응형