
지난 글에서는 소프트웨어 건강 상태를 알아보기위한 인프라 메트릭에 대해서 다루어보았다.
Software 개발에서 꼭 알아야하는 인프라 메트릭
그런데 일반적으로 소프트웨어 엔지니어링에서 문제가 발견될 때는 애플리케이션 레벨에서의 증상으로 발견되는것이 대부분이다.
예를 들면 유저가 갑자기 사용하던 기능이 안된다던가.. 로그인이 안된다던가.. 앱이 튕겼다던가 하는 그런 것들이다.
그래서 시스템의 상태를 이해하기위해서는 Application Metric을 반드시 수집해야한다. (대표적으로 Latency가 있다.) Application Metric 수집을 알기위해 또 Instrumentation 이라는 개념을 알아하는데, 이번에는 이것에 대해 이야기를 해보고자한다!
Instrumentation은 Application Metric을 측정하기위해 코드를 넣는 작업이다. 그러면 구체적으로 뭘 의미하는걸까? 예시 코드를 살펴보자
import time
def get_user(user_id):
start = time.time()
user = query_db(user_id)
latency = time.time() - start
print(f"get_user latency: {latency}s")
return user
예를 들어 이렇게 하면 get_user 함수가 실행된 시간, 즉 Latency를 측정할 수 있다.
그런데 이렇게 직접 측정 코드를 넣는 방식에는 아주 큰 문제가있다..
모든 API, 모든 함수마다 시작 시간 재고, 끝나고 계산하고, 에러 처리까지 넣는 것은 현실적으로 유지보수가 어렵다. 기능이 늘어날수록 계측 코드는 기하급수적으로 증가하고, 결국 성능 측정을 위한 코드가 비즈니스 로직보다 더 많이 보이게 된다.
그래서 계측 코드 삽입부터 데이터 전송까지를 한 번에 담당해주는 라이브러리가 필요해졌다. 이런 접근 방식을 Automatic Instrumentation이라고 부른다.
개발자는 비즈니스 로직에 집중하고, Instrumentation은 라이브러리가 대신 처리한다.
예를 들어 Java 애플리케이션의 경우, JVM 위에서 동작하는 agent를 함께 붙이면 바이트코드 레벨에서 자동으로 계측이 이루어진다. OpenTelemetry(OTEL) 예시를 보면 이렇다.
java -javaagent:path/to/opentelemetry-javaagent.jar \
-Dotel.resource.attributes=service.name=your-service-name \
-Dotel.traces.exporter=zipkin \
-jar myapp.jar
Docker Compose에서도 agent JAR 경로와 환경변수만 설정해주면 동일하게 적용할 수 있다.
이렇게 하면 자동으로 계측된 정보를 생성할 수 있다.

자동으로 애플리케이션 메트릭을 생성해주는것은 오케이.. 이렇게 생성된 메트릭 프로토콜처럼 데이터 양식을 표준화할 필요성이 생겼다.
예를 들어 HTTP 요청의 latency를 측정한다고 할 때, 어떤 라이브러리는 http.request.duration이라 하고, 어떤 라이브러리는 http_latency_ms라고 하면 서로 다른 서비스 간에 메트릭을 비교하거나 합쳐서 볼 수가 없다.
그래서 OpenTelemetry에서는 Semantic Convention이라는 표준 네이밍 규칙을 정의하고 있다.
여기서 등장하는 OpenTelemetry(OTel)는 CNCF에서 관리하는 오픈소스 Observability 프레임워크로, 트레이스·메트릭·로그 같은 텔레메트리 데이터를 벤더에 종속되지 않고 계측·수집·전송할 수 있게 해주는 표준이다. 현재 Datadog, Grafana, New Relic 등 대부분의 Observability 벤더가 OTel을 지원하고 있어서, 사실상 업계 표준으로 자리잡았다.
HTTP 요청 관련 메트릭은 http.server.request.duration, 상태 코드는 http.response.status_code, 이런 식으로 이름과 단위, 속성(attribute)까지 미리 정해놓은 것이다.

자동 계측 라이브러리가 이 Semantic Convention을 따르기 때문에, 어떤 언어에서 어떤 프레임워크를 쓰든 동일한 이름과 형식으로 메트릭이 생성된다. 덕분에 Java로 만든 서비스와 Python으로 만든 서비스의 메트릭을 같은 대시보드에서 비교할 수 있게 되는 것이다.
Semantic conventions for HTTP spans
자 이제 Latency를 측정하는 것 자체는 어렵지 않지만, 그 데이터를 어디에, 어떤 형식으로, 얼마나 오래 보관할지는 또 다른 문제다.
결국 측정된 값은 외부 시스템으로 전송되어 저장되고, 분석될 수 있어야 한다.
하나의 API 엔드포인트에서 초당 수백~수천 건의 요청이 들어온다고 생각해보면, 각 요청마다 latency, status code, timestamp 등이 기록된다. 서비스가 10개, 엔드포인트가 서비스당 20개만 되어도 초당 생성되는 데이터 포인트는 어마어마하다.
이걸 전통적인 RDB나 MongoDB 같은 Document DB에 넣으면 아래와 같은 문제들이 있다.
1. 쓰기 성능이 버티질 못한다.
매초 수만 건의 INSERT가 발생하는데, 일반 DB는 인덱싱과 트랜잭션 오버헤드 때문에 이런 워크로드에 최적화되어 있지 않다.
2. 조회 패턴이 다르다.
메트릭 데이터는 거의 항상 "최근 5분간의 p99 latency", "지난 1시간 동안의 error rate 추이"처럼 시간 범위 기반으로 조회된다. 일반 DB에서 이런 시간 범위 집계를 대량 데이터에 대해 돌리면 굉장히 느리다.
3. 오래된 데이터의 관리가 어렵다.
1초 단위의 raw 데이터를 영원히 보관할 수는 없으니, 시간이 지나면 1분 → 5분 → 1시간 단위로 다운샘플링해서 압축하고 싶은데, 일반 DB에서는 이런 기능을 직접 구현해야 한다.
그래서 등장한 것이 시계열 데이터베이스(Time-Series Database)이다.
Fast Open-Source OLAP DBMS - ClickHouse

대표적인 시계열DB로 자리잡은 ClickHouse..
시계열 DB는 이름 그대로 시간 축을 기준으로 데이터를 저장하고 조회하는 데 특화된 데이터베이스다. Prometheus, InfluxDB, VictoriaMetrics 같은 것들이 있다. 이런 DB들은 대량의 쓰기를 효율적으로 처리하고, 시간 범위 쿼리에 최적화되어 있으며, 오래된 데이터를 자동으로 다운샘플링하거나 만료시키는 기능을 기본으로 제공한다고 한다.
시계열 DB에 쌓인 숫자 데이터를 터미널에서 쿼리해서 보는 건 현실적이지 않다. 결국 사람이 보고 판단해야 하니까, 시각화가 필요하다. 이 영역에서 사실상 표준처럼 쓰이는 오픈소스가 Grafana다. Grafana는 Prometheus, InfluxDB 등 다양한 데이터 소스에 연결해서 대시보드를 구성할 수 있게 해주는 시각화 도구가 필오하다.

ClickHouse에 저장된 Trace 데이터를 바탕으로 시각화한 모습
그리고 여기까지 읽다보면 알겠지만.. 이를 운영하기위한 정말 많은 작업들이 필요하다. 시계열 DB의 관리부터 대시보드 관리, 메트릭 선정 및 알람 관리 등등.. 그래서 이런 기술적 문제들을 대신 해결해주는 여러 Application Observability 솔루션들이 시장에 나오게 되었다.

대표적인 Application Observability 솔루션인 Datadog
전체적으로 Application Metric을 얻고 이를 기반으로 의사결정하는데 필요한 흐름을 다시 정리해보면 이렇다.
계측 코드 삽입(Instrumentation) → 메트릭 데이터 생성 → 시계열 DB에 저장 → 시각화 도구로 모니터링
다음 시간에는 이렇게 얻어진 메트릭 중 어떤 메트릭들을 봐야하는지 정리해보고자 한다!