Featured Post

20년 개발자가 진심으로 추천하는, OpenTelemetry로 관측성 챙기는 현실적인 방법

OpenTelemetry - 관측성 관련 이미지

왜 갑자기 OpenTelemetry 이야기를 꺼냈냐면요

요즘 개발자들끼리 이야기하다 보면 관측성(Observability)이라는 말, 진짜 자주 나오죠. 예전에는 그냥 로그 잘 찍고, 메트릭 적당히 쌓고, 장애 나면 서버 들어가서 로그 뒤지면 되는 거 아니야? 저도 한때는 그렇게 생각했어요. 솔직히 말하면 꽤 오래 그렇게 일했습니다.

그런데 서비스가 커지고, 배포 단위가 많아지고, MSA로 쪼개지기 시작하면 이야기가 완전히 달라지더라고요. 예를 들어 A 서비스에서 에러가 났는데, 그게 진짜 A 서비스 문제인지, B 서비스의 타임아웃 때문인지, C 데이터베이스의 락 때문인지, 아니면 외부 API가 잠깐 정신을 놓은 건지... 로그만 보고는 감이 안 와요. 로그가 없는 게 아니라 너무 많아서 못 찾는 상황이 옵니다. 이게 참 사람 피곤하게 만들어요.

저도 처음 OpenTelemetry를 접했을 때는 살짝 귀찮았습니다. “아, 또 뭘 배워야 하네...” 이런 마음이었죠. 이미 Prometheus도 있고, Grafana도 있고, Jaeger도 들어봤고, Datadog이나 New Relic 같은 APM 도구도 있는데 굳이 또 새로운 걸 해야 하나 싶었거든요. 그런데 막상 써보니까 생각이 좀 바뀌었습니다. 뭐랄까, 이건 또 하나의 도구라기보다는 관측 데이터를 다루는 표준 언어에 가깝더라고요.

예전에는 Datadog을 쓰면 Datadog 방식대로 instrumentation을 해야 하고, New Relic을 쓰면 또 그쪽 방식대로 맞춰야 했잖아요. 그러다가 백엔드를 바꾸면 코드도 같이 흔들리고요. 그런데 OpenTelemetry를 쓰면 애플리케이션에서는 표준 방식으로 로그, 메트릭, 트레이스를 내보내고, 뒤에서는 Jaeger든 Prometheus든 Grafana Tempo든 원하는 곳으로 보낼 수 있어요. 이게 별거 아닌 것 같아도 운영하는 입장에서는 엄청 큽니다. 나중에 도구를 바꿔도 애플리케이션 코드가 덜 흔들리니까요.

생각해보면 개발자 입장에서 가장 무서운 건 장애 자체보다 “왜 터졌는지 모르는 장애”예요. 원인을 모르면 뭘 고쳐야 할지도 모르고, 같은 문제가 또 터질까 봐 계속 찜찜하거든요. OpenTelemetry는 그 찜찜함을 줄여주는 데 꽤 큰 역할을 합니다. 완벽한 마법은 아니지만, 최소한 어둠 속에서 손전등 하나 들고 들어가는 느낌은 줘요. 저는 그게 꽤 중요하다고 봅니다.

관측성의 핵심은 결국 ‘흐름’을 보는 거예요

관측성 이야기를 하다 보면 트레이스(Trace), 스팬(Span), 메트릭(Metric), 로그(Log) 이런 단어들이 막 나오죠. 처음 들으면 좀 딱딱합니다. 저도 처음엔 용어만 보고 살짝 물러섰어요. 그런데 복잡하게 생각하지 않아도 됩니다. 핵심은 그냥 이거예요. “사용자의 요청 하나가 우리 시스템 안에서 어디를 지나갔고, 어디서 시간을 잡아먹었고, 어디서 실패했는지 보는 것.”

기존 로그는 보통 점처럼 흩어져 있습니다. A 서버 로그 따로, B 서버 로그 따로, DB 쿼리 로그 따로, Nginx 로그 따로. 물론 다 중요하죠. 그런데 장애 상황에서는 이 점들을 하나씩 이어서 선으로 만들어야 하잖아요. 이게 사람이 수동으로 하면 너무 힘듭니다. 특히 요청량이 많고 서비스가 여러 개면 거의 퍼즐 맞추기예요. 그것도 조각 몇 개 잃어버린 퍼즐...

OpenTelemetry의 트레이스는 이 흩어진 점들을 선으로 이어줍니다. 사용자가 버튼을 눌렀을 때 시작된 요청이 어느 서비스를 거쳐 갔는지, 각 구간에서 시간이 얼마나 걸렸는지, 실패한 지점이 어딘지 한눈에 볼 수 있게 해줘요.

트레이스와 스팬을 아주 현실적으로 이해해보면

예를 들어 사용자가 쇼핑몰에서 ‘주문하기’ 버튼을 눌렀다고 해볼게요. 이 요청 하나가 트레이스입니다. 그리고 그 안에서 일어나는 작은 작업들, 예를 들면 “주문 생성”, “결제 API 호출”, “재고 차감”, “쿠폰 검증”, “이메일 발송” 같은 각각의 작업 단위가 스팬이에요.

이걸 화면에서 보면 꽤 직관적입니다. 주문 전체는 3초 걸렸는데, 결제 API는 200ms밖에 안 걸렸고, 재고 확인에서 2.5초를 잡아먹었다든지. 아니면 이메일 발송 쪽에서 외부 SMTP 호출이 늦어져서 전체 응답이 밀렸다든지. 이런 게 보입니다. 그냥 로그만 보면 “주문 API 느림”으로 끝날 문제가, 트레이스를 보면 “아, 주문 API가 느린 게 아니라 재고 시스템 호출이 느린 거였구나”로 바뀌는 거죠.

저도 실제로 이런 경험이 있었어요. 예전에 어떤 API가 가끔 30초 넘게 타임아웃이 나는데, 로그만 봐서는 도무지 원인을 못 찾겠더라고요. OpenTelemetry로 트레이스를 걸어보니까 외부 레거시 시스템 호출 구간에서 retry가 계속 반복되고 있었습니다. 알고 보니 클라이언트 라이브러리의 재시도 설정이 너무 공격적으로 잡혀 있었고, 외부 시스템이 느려질 때마다 우리 서비스까지 같이 질질 끌려가고 있었던 거예요. 타임아웃과 retry 정책을 정리하니까 바로 안정됐습니다. 그때 진짜 속으로 “아, 이게 관측성이구나...” 했어요.

OpenTelemetry를 도입할 때 너무 완벽하게 시작하려고 하지 마세요

이런 도구를 도입할 때 개발팀에서 자주 하는 실수가 있습니다. 처음부터 모든 서비스에 다 붙이고, 모든 로그와 메트릭과 트레이스를 완벽하게 연결하려고 해요. 마음은 이해합니다. 저도 그런 성격이 좀 있었고요. 그런데 현실에서는 그렇게 하면 보통 지칩니다. 회의만 길어지고, 표준 문서만 만들다가 끝나요.

제가 추천하는 방식은 훨씬 단순합니다. 트래픽이 많거나 장애 영향도가 큰 서비스 하나를 고르세요. 주문, 결제, 로그인, 검색 같은 핵심 API가 좋습니다. 거기에 먼저 OpenTelemetry 자동 계측(auto-instrumentation)을 붙여보고, 트레이스가 실제로 어떤 식으로 보이는지 팀에서 같이 보는 거예요. 화면을 같이 보는 순간 설득이 빨라집니다. 말로 “관측성이 중요합니다” 백 번 하는 것보다, 느린 API 하나를 트레이스 화면에서 딱 보여주는 게 훨씬 세요.

Java Agent는 진짜 편합니다

Java나 Spring Boot 기반으로 일하고 있다면 OpenTelemetry Java Agent부터 보는 걸 추천합니다. 코드 한 줄 바꾸지 않고 JVM 옵션에 -javaagent:opentelemetry-javaagent.jar를 붙이는 방식이죠. HTTP 요청, JDBC 호출, gRPC, Redis, Kafka 같은 주요 라이브러리를 자동으로 추적해주는 경우가 많습니다.

물론 자동 계측이 모든 걸 완벽하게 해주진 않습니다. 비즈니스적으로 중요한 구간에는 직접 스팬을 추가하는 게 좋고요. 그래도 시작점으로는 정말 훌륭해요. 처음부터 코드 곳곳에 @WithSpan을 붙이다 보면 금방 지칩니다. 실제로 저도 예전에 “중요한 메서드에 다 붙이면 되겠지” 하고 시작했다가 반나절 만에 마음이 꺾였어요. 자동 계측으로 큰 흐름을 먼저 잡고, 필요한 곳만 수동 계측하는 게 훨씬 현실적입니다.

    • 처음에는 자동 계측으로 HTTP, DB, 외부 API 호출 흐름을 먼저 확인합니다.
    • 비즈니스 핵심 구간에만 수동 스팬을 추가합니다. 예를 들면 결제 승인, 재고 차감, 쿠폰 적용 같은 부분이죠.
    • 서비스 이름은 꼭 명확하게 정합니다. 나중에 Grafana나 Jaeger에서 볼 때 이름이 대충이면 진짜 헷갈립니다.
    • 환경 구분도 빼먹지 마세요. dev, staging, production이 섞이면 분석하다가 머리 아파집니다.

샘플링 전략을 안 잡으면 비용이 먼저 터집니다

OpenTelemetry를 붙이고 트레이스가 잘 보이기 시작하면 처음엔 기분이 좋습니다. “오, 다 보이네?” 싶죠. 그런데 여기서 조심해야 합니다. 모든 요청을 전부 수집하면 데이터 양이 생각보다 빨리 불어납니다. 트래픽이 많은 서비스라면 하루에도 어마어마한 스팬이 쌓여요. 그리고 이건 결국 저장 비용, 조회 비용, 네트워크 비용으로 돌아옵니다.

그래서 샘플링(Sampling) 전략은 초반부터 같이 고민하는 게 좋습니다. “일단 다 쌓고 나중에 줄이지 뭐”라고 생각하면, 나중에 비용 보고 깜짝 놀랄 수 있어요. 특히 클라우드 기반 관측성 도구를 쓰고 있다면 더 그렇습니다. 데이터는 공짜가 아닙니다. 개발자 마음속에서는 공짜였으면 좋겠지만요...

정상 요청은 적당히, 에러 요청은 놓치지 않게

제가 좋아하는 방식은 정상 요청은 일부만 보고, 에러 요청은 최대한 놓치지 않는 구조입니다. 예를 들면 정상 요청은 5%나 10% 정도만 샘플링하고, 에러가 발생한 요청이나 지연 시간이 긴 요청은 100% 저장하는 식이죠. 이런 접근을 위해 Tail-Based Sampling을 많이 이야기합니다.

Head-Based Sampling은 요청이 시작될 때 저장 여부를 결정합니다. 단순하고 빠르지만, 나중에 에러가 날 요청인지 미리 알 수 없다는 단점이 있어요. 반면 Tail-Based Sampling은 요청이 끝난 뒤 결과를 보고 저장 여부를 판단할 수 있습니다. 그래서 “응답 코드가 500인 요청은 저장”, “전체 시간이 3초 이상 걸린 요청은 저장” 같은 정책을 만들기 좋아요.

    • 에러 트레이스는 가능한 한 많이 남기는 게 좋습니다. 장애 분석에 바로 필요하니까요.
    • 정상 트래픽은 전체 패턴을 볼 수 있을 정도만 샘플링해도 충분한 경우가 많습니다.
    • 느린 요청은 별도 기준으로 저장해두면 성능 개선에 정말 도움이 됩니다.
    • 내부 헬스체크 요청은 대부분 관측 대상에서 빼는 게 낫습니다. 쌓아봐야 노이즈만 늘어요.

이 부분은 팀마다 기준이 다릅니다. 트래픽 규모, 장애 민감도, 비용 구조가 다르니까요. 다만 하나는 분명해요. 샘플링을 아무 생각 없이 두면 언젠가는 비용 그래프가 말을 걸어옵니다. “나 좀 봐...” 하고요.

LLM으로 OpenTelemetry 설정을 물어볼 때, 토큰을 아끼는 법

요즘은 OpenTelemetry 설정할 때도 ChatGPT나 Claude 같은 LLM에게 많이 물어보잖아요. 저도 자주 씁니다. 예전 같으면 공식 문서 뒤지고, GitHub issue 찾아보고, Stack Overflow 헤매던 일을 이제는 꽤 빠르게 줄일 수 있어요. 다만 LLM도 대충 물어보면 답변이 길어지고, 토큰도 많이 쓰고, 가끔은 엉뚱한 버전 기준으로 답을 줍니다.

여기서 중요한 건 친절하게 장문의 사연을 쓰는 게 아니라, 필요한 조건을 짧고 정확하게 던지는 거예요. 사람한테는 맥락이 길수록 좋을 때도 있지만, LLM에게 설정 코드를 받을 때는 오히려 짧고 선명한 요청이 더 잘 먹힐 때가 많습니다.

짧게 물어보되, 버전은 정확히 적어주세요

예를 들어 이렇게 길게 쓰는 경우가 많죠.

나는 20년 경력의 Java 개발자이고 Spring Boot 3.2를 쓰고 있으며 OpenTelemetry를 처음 도입하려고 해. Jaeger로 트레이스를 보내고 싶은데 어떤 dependency를 추가하고 application.yml은 어떻게 작성해야 하는지 자세히 알려줘.

나쁜 프롬프트는 아닙니다. 그런데 토큰을 아끼고 싶으면 더 짧게 가도 됩니다.

Spring Boot 3.2, OpenTelemetry Java Agent, Jaeger OTLP 설정. application.yml과 JVM 옵션만. 설명 제외.

이렇게만 써도 필요한 답을 꽤 잘 줍니다. 특히 “설명 제외”, “코드만”, “주석 없이” 같은 말을 붙이면 답변 길이가 확 줄어요. 저는 실제로 자주 씁니다. 급할 때는 정말 이렇게 던져요. “Spring Boot 3.2 + OpenTelemetry + Grafana Tempo 설정, Docker Compose 포함, 설명 짧게.” 이 정도면 대화가 빨리 시작됩니다.

불필요한 예시 코드는 과감히 줄이세요

LLM은 친절하려고 합니다. 그래서 물어보면 build.gradle, pom.xml, application.yml, Docker Compose, Controller 예제, Service 예제, 테스트 코드까지 줄줄이 뽑아줄 때가 있어요. 고맙긴 한데, 지금 필요한 게 application.yml 하나라면 나머지는 토큰 낭비입니다.

이럴 땐 이렇게 요청하는 게 좋습니다.

    • application.yml만 출력해줘. 설명과 주석은 빼줘.
    • Gradle dependency만 알려줘. 버전 설명은 생략.
    • Docker Compose에서 otel-collector와 tempo 설정만 보여줘.
    • 오류 원인 후보를 5개 이하로만 정리해줘.

이렇게 범위를 좁히면 답변이 짧아지고, 내가 읽어야 할 양도 줄어듭니다. 사실 이게 진짜 중요해요. 토큰도 돈이지만, 개발자 집중력도 돈입니다. 긴 답변 받아놓고 다시 필요한 부분만 찾는 것도 일이거든요.

의존성 버전은 꼭 박아두는 편이 좋습니다

LLM에게 기술 설정을 물어볼 때 은근히 중요한 게 버전입니다. OpenTelemetry도 버전에 따라 설정 방식이나 exporter 이름, dependency 구성이 달라질 수 있어요. 그래서 저는 가능하면 버전을 같이 적습니다.

예를 들면 이런 식이죠.

Spring Boot 3.2, Java 21, opentelemetry-javaagent 2.x, OTLP exporter 기준으로 설정 예시만 줘.

또는 특정 라이브러리를 쓴다면 이렇게요.

io.opentelemetry:opentelemetry-api:1.32.0 기준. 수동 span 생성 예제만 보여줘.

이렇게 버전을 지정하면 LLM이 옛날 방식과 최신 방식을 섞어서 답하는 일이 줄어듭니다. 그리고 불필요한 호환성 설명도 덜 나와요. 체감상 이것만 잘해도 답변 품질이 꽤 안정됩니다. 토큰도 아끼고, 삽질도 줄고요. 이건 진짜 소소하지만 꽤 쓸만한 습관입니다.

개발 환경에 미리 세팅해두면 나중에 편해지는 것들

OpenTelemetry를 운영 환경에 바로 붙이기 전에, 로컬이나 개발 환경에서 먼저 작은 실험판을 만들어두는 걸 추천합니다. 너무 거창할 필요 없어요. Docker Compose로 otel-collector, Grafana, Tempo, 필요하면 Prometheus, Loki 정도를 띄워두고, 샘플 Spring Boot 앱 하나 연결해보는 정도면 충분합니다.

이걸 한 번 만들어두면 팀원 온보딩할 때도 좋고, 새로운 설정을 테스트할 때도 편합니다. 운영에서 바로 설정 바꾸는 건 아무래도 부담스럽잖아요. 로컬 실험장이 있으면 마음이 훨씬 가벼워집니다. 저는 이런 실험 환경을 개인적으로 “놀이터”라고 부르는데요, 개발자는 이런 놀이터가 있어야 새로운 기술을 덜 무섭게 받아들이는 것 같아요.

Grafana + Tempo 조합은 꽤 만족스럽습니다

예전에는 트레이스 하면 Jaeger를 많이 떠올렸습니다. 지금도 좋은 도구예요. 그런데 요즘은 Grafana Tempo도 많이 씁니다. Grafana 생태계 안에서 로그는 Loki, 메트릭은 Prometheus, 트레이스는 Tempo로 묶어보면 흐름이 꽤 자연스럽거든요.

한 화면에서 CPU 사용량이 튀는 시점, 특정 API의 latency가 늘어난 시점, 에러 로그가 터진 시점, 관련 trace까지 이어서 볼 수 있으면 장애 분석 속도가 확 달라집니다. 예전에는 “로그 봤어?”, “메트릭은?”, “트레이스 링크 어디 있어?” 하면서 여기저기 창을 오갔는데, 이제는 한 대시보드 안에서 어느 정도 이어볼 수 있으니까요. 이건 정말 편합니다.

물론 Grafana를 예쁘게 꾸미는 데 너무 빠지면 또 시간이 순식간에 사라집니다. 대시보드 색깔 바꾸고 패널 위치 맞추다 보면 이상하게 퇴근 시간이 다가와요. 그래서 처음에는 예쁜 대시보드보다 장애 때 바로 볼 수 있는 대시보드를 목표로 잡는 게 좋습니다.

    • 서비스별 요청 수를 봅니다. 갑자기 트래픽이 줄거나 늘면 바로 티가 나야 합니다.
    • 응답 시간 p95, p99를 봅니다. 평균만 보면 중요한 지연이 숨어버려요.
    • 에러율을 봅니다. 5xx, 4xx를 나눠서 보는 게 좋습니다.
    • 느린 trace 링크로 바로 이동할 수 있게 연결합니다.
    • 배포 시점을 같이 표시하면 장애 원인 파악이 훨씬 빨라집니다.

OpenTelemetry를 팀에 퍼뜨리는 방법도 중요합니다

기술은 혼자 잘 안다고 끝나는 게 아니더라고요. 특히 관측성은 더 그렇습니다. 한 사람이 OpenTelemetry를 잘 붙여놔도, 팀원들이 trace를 안 보고 로그만 뒤지면 효과가 반쪽이에요. 그래서 도입 자체보다 “팀이 이걸 자연스럽게 쓰게 만드는 것”이 더 중요할 때도 많습니다.

제가 괜찮다고 느낀 방식은 장애 리뷰나 성능 개선 회의 때 실제 trace 화면을 같이 보는 겁니다. 문서로 설명하는 것보다 훨씬 빨라요. “이 요청이 여기서 2초 멈췄고, DB 쿼리가 여기서 1.5초 걸렸고, 외부 API가 여기서 실패했네요.” 이렇게 보여주면 다들 금방 이해합니다. 개발자들은 결국 눈으로 보면 납득이 빠르거든요.

그리고 신규 API를 만들 때도 아주 가볍게 기준을 잡아두면 좋습니다. 모든 메서드에 스팬을 붙이라는 식은 부담스럽고요. 대신 “외부 시스템 호출”, “비즈니스 핵심 처리”, “오래 걸릴 수 있는 배치성 작업” 정도에는 수동 스팬을 남기자, 이 정도면 현실적입니다.

너무 많은 데이터보다 쓸모 있는 데이터가 낫습니다

관측성을 이야기하면 자꾸 “많이 수집해야 한다”는 쪽으로 흐르기 쉬운데, 저는 조금 다르게 생각합니다. 많이 쌓는 것보다 장애 때 도움이 되는 데이터를 남기는 것이 더 중요해요. 로그도 마찬가지잖아요. 의미 없는 info 로그가 수십만 줄 있어봐야 정작 장애 때는 방해가 됩니다.

트레이스도 너무 잘게 쪼개면 보기 힘들어집니다. 모든 private method마다 스팬을 만들면 화면이 지저분해져요. 진짜 중요한 경계에 스팬을 남겨야 합니다. 서비스 간 호출, DB 호출, 외부 API 호출, 메시지 발행과 소비, 핵심 비즈니스 단계. 이 정도만 잘 보여도 장애 분석에는 꽤 큰 도움이 됩니다.

그래도 결국 중요한 건 ‘장애를 덜 무섭게 만드는 것’ 같아요

OpenTelemetry가 만능은 아닙니다. 붙인다고 장애가 사라지는 것도 아니고, 성능 문제가 자동으로 해결되는 것도 아니에요. 하지만 장애가 났을 때 팀이 덜 당황하게 만들어줍니다. 저는 이게 정말 큰 가치라고 생각해요.

운영하다 보면 장애는 언젠가 납니다. 아무리 테스트를 잘하고, 코드 리뷰를 꼼꼼히 해도 예상 못 한 일이 생겨요. 외부 API가 느려질 수도 있고, DB 인덱스 하나 빠져서 쿼리가 터질 수도 있고, 배포 후 특정 조건에서만 문제가 생길 수도 있습니다. 그때 “어디부터 봐야 하지?”가 아니라 “trace부터 보자”라고 말할 수 있으면, 팀의 분위기가 달라집니다.

저는 개발자 커리어가 길어질수록 이런 생각을 자주 합니다. 좋은 코드는 중요합니다. 당연하죠. 그런데 좋은 운영 습관도 그만큼 중요합니다. 내가 만든 서비스가 실제 사용자 앞에서 어떻게 움직이는지 볼 수 있어야, 그다음 개선도 제대로 할 수 있거든요. OpenTelemetry는 그걸 가능하게 해주는 꽤 괜찮은 도구입니다.

처음엔 조금 낯설 수 있습니다. 용어도 많고, Collector 설정도 헷갈리고, exporter니 sampler니 이런 말들이 좀 귀찮게 느껴질 거예요. 그런데 핵심 서비스 하나에 작게 붙여보고, 느린 요청 하나를 trace로 잡아보면 감이 옵니다. “아, 이래서 다들 관측성 이야기하는구나” 하고요.

그러니 너무 거창하게 시작하지 마세요. Java Agent 하나 붙여보고, Grafana Tempo나 Jaeger로 trace 하나 확인해보고, LLM에게 설정 물어볼 때는 짧고 정확하게 요청해보세요. 그렇게 한 걸음씩 가면 됩니다. 개발도 여행이랑 비슷한 구석이 있어요. 처음부터 완벽한 코스를 짜려고 하면 출발을 못 하거든요. 일단 가보고, 길이 보이면 조금씩 더 멀리 가는 거죠.

OpenTelemetry도 딱 그런 마음으로 접근하면 좋겠습니다. 어렵게 말하면 관측성이고, 쉽게 말하면 우리 서비스가 어디서 힘들어하는지 눈으로 보는 방법입니다. 이걸 한 번 맛보면, 예전처럼 로그만 붙잡고 밤새 뒤지는 방식으로 돌아가긴 좀 힘들 거예요. 솔직히 저도 그렇습니다...

댓글