2026-03-27 (금) 개발일지
오늘의 TIL: APM 프로젝트에서 ClickHouse 스키마와 로그 분석 삽질기
오늘은 APM 프로젝트에서 OpenTelemetry 데이터를 ClickHouse에 저장하고 분석하는 작업을 했다. APM 프로젝트 발표를 준비하면서 실시간 대시보드 구현 방법을 검토하고 있었는데, 갑자기 LLM API 응답이 30초 이상 걸리는 문제가 발생했다. “어디서 병목이 생기는 거지?”라는 생각으로 하루를 시작하게 됐다.
ClickHouse 스키마와 쿼리 오류
LLM API 응답 시간 문제를 추적하려고 APM 대시보드에서 쿼리를 날렸는데, 갑자기 이런 에러가 떴다:
DB::Exception: Unknown expression or function identifier 'Timestamp' in scope SELECT count() AS cnt FROM otel.otel_metrics_gauge WHERE Timestamp >= (now() - toIntervalHour(24))
“Timestamp 컬럼이 없다고? 분명히 테이블에는 있는데…” 하면서 ClickHouse에 직접 접속해서 스키마를 확인했다. otel_metrics_gauge 테이블을 살펴보니:
Timestamp DateTime64(9) Delta(8), ZSTD(1)
TimestampTime DateTime DEFAULT toDateTime(Timestamp)
아하! 컬럼명이 Timestamp가 아니라 TimestampTime이었다. 그리고 Timestamp는 DateTime64(9) 타입으로 존재했다. 문제는 LLM이 생성한 쿼리가 잘못된 컬럼명을 사용하고 있었던 것.
더 깊이 파고들어보니, 각 테이블마다 타임스탬프 컬럼명이 달랐다:
otel_metrics_gauge:Timestamp(DateTime64(9))와TimestampTime(DateTime) 두 개가 있었음- 다른 테이블들은 또 다른 컬럼명을 사용하고 있었음
결국 Timestamp 대신 TimestampTime을 사용하도록 쿼리를 수정했고, 이제 정상적으로 데이터를 조회할 수 있었다. ClickHouse 테이블의 실제 스키마를 확인하지 않고 LLM이 생성한 쿼리를 맹신한 게 문제였다.
로그 분석에서의 헛발질
다음으로는 frontend-proxy 서비스의 에러 로그를 분석하려고 했다. 대시보드에서 에러 로그를 조회했는데, “최근 1시간 동안 ERROR 또는 WARN 수준의 로그가 발생하지 않았습니다”라는 결과가 나왔다.
“에러가 없으면 좋은 거 아니야?”라고 생각했지만, 실제로는 에러가 있었는데 쿼리가 잘못된 거였다. 직접 ClickHouse에 쿼리를 날려보니:
docker exec otel-clickhouse clickhouse-client --query \
"SELECT DISTINCT ServiceName FROM otel.otel_logs WHERE ServiceName ILIKE '%frontend%' LIMIT 10"
이 쿼리도 아무 결과가 안 나왔다. 그래서 다른 방법으로 접근해서 전체 서비스별 로그 건수를 조회해봤더니:
logs load-generator 13130
logs cart 7982
logs checkout 4936
logs shipping 2475
logs ad 2450
logs currency 2351
logs payment 1644
logs recommendation 1226
logs quote 829
logs fraud-detection 823
frontend-proxy가 아예 안 보인다. 이게 무슨 일이지?
결국 ServiceName 컬럼이 아니라 ResourceAttributes 맵 안에 서비스 정보가 들어있을 수 있다는 걸 깨달았다. ClickHouse에 OpenTelemetry 데이터를 저장할 때 구조가 서비스마다 다를 수 있다는 점을 간과했던 것.
APM 대시보드의 한계와 해결
APM 대시보드에서 “최근 1시간 error 트레이스 요약”을 조회했는데, 결과가 이상했다:
load-generator 서비스: 총 30,735건의 요청 중 0건의 오류가 발생하여 오류율은 0.0%입니다.
cart 서비스: 총 23,266건의 요청 중 0건의 오류가 발생하여 오류율은 0.0%입니다.
분명히 frontend-proxy 서비스에서 에러가 발생하고 있는데, 대시보드에서는 전혀 보이지 않았다.
이 문제를 해결하기 위해 직접 로그를 분석해보니, frontend-proxy 서비스의 에러는 otel_logs 테이블에 Error 레벨로 기록되어 있었지만, 대시보드의 쿼리가 ServiceName 컬럼을 기준으로 조회하고 있어서 누락되고 있었다.
배운 점:
- APM 도구를 맹신하지 말고, 직접 DB에 쿼리를 날려서 확인하는 습관이 필요하다.
- OpenTelemetry 데이터는 서비스마다 다른 구조로 저장될 수 있다. 스키마를 항상 확인하자.
- 대시보드가 모든 것을 보여주는 건 아니다. 때로는 직접 SQL을 작성해야 한다.
스키마 정리와 배포
이런 삽질을 겪으면서 ClickHouse 테이블 스키마를 정리할 필요성을 느꼈다. Timestamp 컬럼명을 통일하고, ServiceName 컬럼이 없는 경우를 대비한 쿼리 로직을 수정했다.
78서버에 dev 푸쉬했는데, 빌드 다시 해야 할 것 같아서 빌드 명령어를 확인했다. “이거 빌드 안 하면 스키마 변경이 반영 안 되겠지?” 하면서 빌드를 진행했다.
오늘의 깨달음
APM 프로젝트 발표를 준비하면서 “실시간 대시보드 구현 방법 검토” 같은 큰 그림을 그리려고 했는데, 실제로는 Timestamp 컬럼명 하나 때문에 30분을 날렸다.
그런데 이게 결국 중요한 거다. APM 도구를 만드는 우리가, 우리 자신의 도구를 믿지 못하고 직접 쿼리를 날려야 한다는 아이러니. 도구를 만드는 사람도 도구를 맹신하면 안 된다는 교훈을 얻었다.
오늘의 한줄
“APM 대시보드가 보여주지 않는 것들을 보기 위해선, 결국 직접 SQL을 작성해야 한다는 현실을 깨달았다.”