분산 추적
고친 사람 github-actions[bot]
분산 추적은 요청 하나가 여러 서비스를 거쳐 간 길을 나중에 한 줄기로 되살리는 방식입니다. 요청이 처음 들어올 때 번호를 하나 붙입니다. 서비스가 다음 서비스를 부를 때 그 번호를 같이 넘깁니다. 나중에 번호로 기록을 모으면 그 요청이 어디를 거쳤고 어디서 오래 걸렸는지가 드러납니다.
쉽고 빠른 이해
요청 한 건이 여러 서비스를 지나간 길과 시간을 한 벌로 묶어 보여 주는 방식입니다. 앞단 서버가 요청을 받아 중간 서버 둘을 부릅니다. 그중 하나가 다시 뒷단 서버 둘을 부릅니다. 이 다섯 곳의 기록이 한 그림으로 이어집니다.
이게 없으면 전체 응답이 늦다는 것까지만 압니다. 어느 서비스가 범인인지는 서비스마다 흩어진 기록을 사람이 맞춰 봐야 합니다.
어떻게 도나:
- 요청이 처음 들어올 때 그 요청만의 번호를 붙입니다
- 서비스가 다음 서비스를 부를 때 번호를 요청에 실어 넘깁니다
- 서비스마다 「내가 맡은 일이 언제 시작해 언제 끝났나」를 번호와 함께 남깁니다
- 남은 기록을 요청 경로 밖에서 한곳으로 모읍니다. 번호로 찾아 한 화면에 펼칩니다
대가도 있습니다. 모든 서비스에 기록을 남기는 손질을 넣어야 합니다. 요청을 전부 남기면 응답 지연이 눈에 띄게 늘고 쌓을 양도 불어납니다. 그래서 대개 일부만 골라 남깁니다. 고르지 않은 요청은 나중에 볼 수 없습니다. 프로그램을 고칠 수 없거나 작은 지연에도 민감한 서비스라면 다른 길을 고르기도 합니다.
상세
택배 상자를 따라다니는 것은 송장 번호 한 장뿐입니다. 상자가 지나는 집하장과 터미널과 배송 지점은 저마다 자기 장부에 「몇 시에 받았고 몇 시에 넘겼다」를 찍어 둡니다. 상자가 어디서 하루를 묵었는지는 나중에 송장 번호로 그 흩어진 장부를 한곳에 불러 모아야 보입니다.
분산 추적은 요청에 똑같은 일을 합니다. 서비스 코드에 아래 네 가지를 넣기로 하는 결정입니다.
- 바깥에서 들어온 요청마다 고유한 번호를 붙입니다
- 그 요청을 처리하는 모든 서비스로 번호를 넘깁니다
- 서비스마다 맡은 일의 시작 시각과 끝 시각을 그 번호와 함께 남깁니다
- 남긴 기록을 한곳의 서비스로 모읍니다
코드에 기록을 남기는 손질을 넣는 일이 계측입니다. 계측은 「여기서 일이 시작됐고 여기서 끝났다」를 코드가 알리게 만드는 일입니다. 분산 추적은 이 계측을 요청이 지나는 모든 서비스에 넣는 데서 출발합니다.
로그만으로는 못 잇는 까닭
서비스를 여러 개로 쪼개 놓은 마이크로서비스 구조에서는 요청 하나가 서비스 여럿을 지나갑니다. 바깥에서 재는 모니터링은 전체 응답 시간과 호출 횟수만 알려 줍니다. 응답이 늦다는 것은 알아도 어느 서비스 탓인지, 왜 늦어지는지는 짐작하지 못할 수 있습니다.
로그도 같은 벽에 부딪힙니다. 한 요청에 대한 로그 줄이 서비스 수만큼의 로그 파일에 흩어집니다. 같은 서비스를 다른 요청들도 함께 지나갑니다. 흩어진 줄 가운데 어느 것이 내가 쫓는 요청의 것인지 바로 묶어 줄 공통 번호가 로그에는 없습니다.
흩어진 기록을 요청 하나로 묶는 길은 둘로 갈립니다. 표의 어노테이션은 기록마다 붙이는 표시를 말합니다.
| 블랙박스 방식 | 어노테이션 기반 방식 | |
|---|---|---|
| 하는 일 | 주고받은 메시지 기록만 놓고 통계로 어느 기록이 어느 요청 것인지 추정합니다 | 모든 기록에 요청을 가리키는 전역 식별자를 붙입니다 |
| 얻는 것 | 프로그램을 안 고쳐도 되니 어디에나 붙이기 쉽습니다 | 추정이 아니라 번호로 바로 묶습니다 |
| 치르는 것 | 추정이라 정확해지려면 데이터가 더 많이 필요합니다 | 프로그램에 계측을 넣어야 합니다 |
분산 추적은 오른쪽 길입니다. 번호를 붙이고, 넘기고, 모으는 일이 전부 어노테이션 기반 방식에 속합니다.
트레이스 ID와 부모 ID로 세우는 나무
스팬은 요청 하나 안에서 일어난 일 한 단위의 기록입니다. 그 일의 이름과 시작 시각과 끝 시각을 담습니다. 원격 호출 하나를 처리한 것이 대개 스팬 하나입니다.
스팬마다 번호가 셋 붙습니다.
| 번호 | 무엇을 가리키나 |
|---|---|
| 트레이스 ID | 요청 전체. 한 요청에서 생긴 스팬은 전부 같은 번호를 나눠 가집니다 |
| 스팬 ID | 이 스팬 하나 |
| 부모 ID | 이 일을 시킨 바로 윗 스팬의 스팬 ID |
부모 ID 없이 만들어진 스팬이 루트 스팬입니다. 요청이 처음 들어온 지점의 일입니다. 같은 트레이스 ID를 가진 스팬을 모두 모은 한 벌이 트레이스입니다.
모은 스팬을 부모 ID로 이어 붙이면 나무가 됩니다. 아래는 서버 다섯 대짜리 서비스에 요청이 하나 들어온 경우입니다. 앞단 A가 요청을 받아 중간 계층 B와 C를 부릅니다. B는 바로 답합니다. C는 뒷단 D와 E의 답을 받은 뒤에야 A에게 답합니다.
flowchart TD
A["앞단 A · 루트 스팬 · 부모 없음"]
A --> B["중간 계층 B · 부모 A"]
A --> C["중간 계층 C · 부모 A"]
C --> D["뒷단 D · 부모 C"]
C --> E["뒷단 E · 부모 C"]
다섯 스팬은 전부 같은 트레이스 ID를 가집니다. 화살표는 부모에서 자식으로 갑니다. 원격 호출이 한 겹 늘 때마다 나무가 한 단 깊어집니다. 스팬마다 시작 시각과 끝 시각이 있습니다. C 아래의 D와 E 가운데 어느 쪽이 오래 걸렸는지 나란히 견줄 수 있습니다.
요청을 따라 넘어가는 트레이스 컨텍스트
번호가 다음 서비스에 닿아야 나무가 이어집니다. 요청을 따라 넘어가는 이 번호 묶음이 트레이스 컨텍스트입니다. 작고 복사하기 쉬운 묶음입니다. 대표적으로 이런 것이 담깁니다.
- 트레이스 ID
- 지금 일을 하고 있는 스팬의 스팬 ID. 받는 쪽은 이 번호를 자기 스팬의 부모 ID로 적습니다
- 이 요청을 기록하기로 했는지를 알리는 샘플링 표시. 요청 가운데 일부만 골라 기록하는 일을 샘플링이라고 합니다. 자세한 것은 아래 「남길 요청을 고르는 샘플링」 소절에 있습니다
목록은 여기서 닫히지 않습니다. 추적 시스템이 다음 서비스로 넘겨야 하는 다른 데이터도 함께 실을 수 있습니다. 추적 도구가 제 몫으로 덧붙이는 정보가 그런 예입니다.
컨텍스트를 넘기는 일이 컨텍스트 전파입니다. 요청이 지나는 경계마다 넘기는 방법이 다릅니다.
| 경계 | 넘기는 방법 |
|---|---|
| 한 스레드 안 | 요청을 맡은 스레드의 스레드 로컬 저장소(thread-local storage)에 컨텍스트를 붙여 둡니다 |
| 콜백·스레드 풀로 미룬 일 | 콜백을 만들 때 만든 쪽의 컨텍스트를 같이 저장합니다. 콜백이 실행될 때 그 컨텍스트를 실행하는 스레드에 다시 붙입니다 |
| 프로세스 사이 | 원격 호출에 번호를 실어 보냅니다. HTTP(HyperText Transfer Protocol)라면 요청 헤더에 넣습니다 |
프로세스 사이에서는 보내는 쪽이 컨텍스트를 헤더 같은 운반체에 넣습니다. 이 일이 주입(inject)입니다. 받는 쪽은 운반체에서 컨텍스트를 꺼냅니다. 이 일은 추출(extract)입니다. 받은 서비스는 같은 트레이스에 속하는 새 스팬을 만듭니다. 부른 쪽의 스팬은 그 새 스팬의 부모가 됩니다.
아래는 앞 그림의 A가 C를, C가 D를 부를 때 무엇이 넘어가는지 그린 것입니다.
sequenceDiagram
participant A as 앞단 A
participant C as 중간 계층 C
participant D as 뒷단 D
Note over A: 루트 스팬을 열고<br/>트레이스 ID를 새로 만든다
A->>C: 요청 + 트레이스 ID + A의 스팬 ID
Note over C: 같은 트레이스 ID로<br/>스팬을 열고 부모를 A로 적는다
C->>D: 요청 + 트레이스 ID + C의 스팬 ID
Note over D: 부모를 C로 적는다
D-->>C: 응답
C-->>A: 응답
트레이스 ID는 처음부터 끝까지 바뀌지 않습니다. 넘어가는 스팬 ID는 한 칸 갈 때마다 바뀝니다. 받는 쪽에서 그 번호가 부모 ID가 되기 때문입니다.
넣고 꺼내는 일은 보통 공용 라이브러리가 맡습니다. 계측 라이브러리는 스레드와 콜백을 다루는 라이브러리나 원격 호출(RPC, Remote Procedure Call) 라이브러리 같은 것입니다. 여기에 이 일을 넣어 두면 애플리케이션 개발자는 추적을 의식하지 않아도 됩니다. 손으로 넘겨야 하는 경계가 남으면 그 부분만 직접 넣습니다.
넘기는 헤더의 모양은 추적 도구마다 제각각 구현해 왔습니다. 서로 다른 도구가 한 요청에 끼면 경계에서 번호가 이어지지 않아 트레이스가 끊깁니다. 이 모양을 하나로 맞추려고 W3C(World Wide Web Consortium)가 Trace Context 표준을 냈습니다. 모든 도구가 읽는 traceparent 헤더 하나와 도구별 정보를 덧붙이는 tracestate 헤더 하나로 나눴습니다. 헤더에 실린 실물은 「예시」 절에 있습니다.
요청 밖에서 모으는 기록
스팬은 서비스마다 남습니다. 이것을 한곳으로 모으는 길은 요청 경로와 갈라져 있습니다. 응답에 실어 돌려보내지 않습니다. 모으는 길에는 수집기가 섭니다. 수집기는 서비스들이 뱉은 스팬을 받아 저장소로 넘기는 서버나 데몬입니다.
아래는 세 단계로 모으는 한 예입니다.
flowchart TD
S["서비스 프로세스 · 스팬을 만든다"] --> L["① 그 호스트의 로컬 로그 파일에 쓴다"]
L --> K["② 수집기가 모든 호스트에서 끌어온다"]
K --> R["③ 중앙 저장소에 트레이스 단위로 쌓는다"]
R --> Q["트레이스 ID로 찾아 펼쳐 본다"]
저장소와 조회 화면을 합친 것이 추적 백엔드입니다. 수집기와 추적 백엔드는 둘 다 애플리케이션 서버와 별개로 띄우고 운영하는 물건입니다.
응답 헤더에 실어 돌려보내는 방식을 안 쓰는 데는 까닭이 둘 있습니다.
첫째, 기록이 응답보다 클 수 있습니다. 응답에 실어 보내는 방식은 애플리케이션의 네트워크 동작에 영향을 줄 수 있습니다. 규모가 큰 시스템 여럿에서는 스팬이 수천 개 달린 트레이스가 드물지 않습니다. 그런 트레이스의 루트 가까이에서도 응답은 10KB(킬로바이트) 안쪽으로 작은 경우가 많습니다. 이런 경우 추적 데이터를 응답에 실으면 애플리케이션 데이터보다 훨씬 커집니다. 뒤에 하는 분석 결과도 비뚤어집니다.
둘째, 부른 쪽이 늘 자식 호출이 다 끝난 뒤에 답하지는 않습니다. 응답에 실으려면 모든 호출이 자식 호출을 다 끝낸 뒤에 답한다고 가정해야 합니다. 뒷단이 답을 다 주기 전에 부른 쪽에 먼저 답하는 미들웨어가 많습니다. 그래서 그 가정이 깨집니다.
남길 요청을 고르는 샘플링
요청을 전부 기록하면 그만큼 일이 늘고 쌓을 양이 불어납니다. 그래서 요청 가운데 일부만 골라 기록합니다. 이 고르기가 샘플링입니다.
고르는 단위는 요청 하나, 곧 트레이스 하나입니다. 한 번 정한 결정을 컨텍스트의 샘플링 표시에 실어 뒤로 넘깁니다. 뒤 서비스가 그 표시를 따르면 같은 트레이스 ID의 스팬은 전부 남거나 전부 안 남습니다.
언제 정하느냐에 따라 둘로 갈립니다.
| 헤드 샘플링 | 테일 샘플링 | |
|---|---|---|
| 정하는 때 | 될 수 있는 한 일찍. 트레이스 전체를 보지 않고 정합니다 | 트레이스의 스팬을 전부, 또는 대부분 모은 뒤 |
| 흔한 방식 | 트레이스 ID와 남길 비율로 정합니다. 트레이스가 스팬 하나 빠짐없이 남거나 함께 빠집니다 | 오류가 든 트레이스는 늘 남기기 · 전체 지연이 긴 트레이스 남기기 |
| 못 하는 것 | 오류가 난 트레이스를 빠짐없이 남기는 일 | 스팬을 결정 때까지 들고 있어야 해서 그 일을 맡는 쪽이 무거워집니다 |
대가
분산 추적을 하기로 정하는 순간 비용이 넷 붙습니다. 모든 서비스에 넣어야 하는 계측, 요청마다 붙는 처리 비용, 기록을 모으고 쌓는 인프라, 그리고 일부만 남길 때 놓치는 요청입니다. 이 절 끝에서는 이 비용을 치르지 않고 다른 길을 고르는 조건도 봅니다.
빠짐없이 넣어야 하는 계측
추적은 요청이 지나는 모든 구간에 계측이 있어야 쓸모가 납니다. 시스템의 작은 부분만 비어도 쓸모가 크게 깎일 수 있습니다. 계측이 빈 구간에서는 트레이스가 끊깁니다(「실패」 절).
계측을 애플리케이션 개발자 손에 맡기면 추적이 매우 취약해집니다. 계측 코드의 버그나 빠뜨림으로 자주 끊깁니다. 그러면 「빠짐없이」가 무너집니다.
이 부담을 줄이는 길은 계측을 소수의 공용 라이브러리에만 넣는 것입니다. 이 길은 모든 애플리케이션이 같은 스레드 모델과 제어 흐름 라이브러리와 원격 호출 체계를 쓸 때 열립니다. 그런 환경을 갖춘 Google 에서도 직접 손으로 전파를 넣어야 했던 애플리케이션이 C++ 40개, Java 33개였습니다. 전체 수천 개 가운데 작은 몫이었습니다.
요청마다 붙는 처리 비용
스팬을 만들고 없애는 일, 스팬에 내용을 덧붙이는 일, 로컬 디스크에 적는 일이 요청마다 붙습니다. Google 의 추적 시스템 Dapper 를 2.2GHz x86 서버에서 잰 평균은 아래와 같습니다.
| 작업 | 평균 |
|---|---|
| 루트 스팬 만들고 없애기 | 204나노초 |
| 루트가 아닌 스팬 만들고 없애기 | 176나노초 |
| 기록하지 않는 스팬에 내용 덧붙이기 | 약 9나노초 |
| 기록하는 스팬에 문자열 덧붙이기 | 40나노초 |
루트 스팬이 더 드는 까닭은 전역에서 겹치지 않는 트레이스 ID를 새로 만드는 비용입니다.
가장 비싼 일은 로컬 디스크에 쓰는 일입니다. 쓰기 여러 번을 묶어 애플리케이션과 비동기로 처리합니다. 그래서 눈에 보이는 부담은 많이 줄어듭니다. 그래도 처리량이 높은 애플리케이션에서는 느껴질 수 있습니다. 요청을 전부 추적할 때 특히 그렇습니다.
기록하는 비율을 바꿔 가며 잰 결과도 있습니다. Google 웹 검색 클러스터에 고정 비율 샘플링을 걸고 평균 지연과 평균 처리량의 변화를 쟀습니다. 실험 오차는 지연 2.5%, 처리량 0.15%입니다.
| 기록하는 비율 | 평균 지연 변화 | 평균 처리량 변화 |
|---|---|---|
| 1/1 (전부) | 16.3% | −1.48% |
| 1/2 | 9.40% | −0.73% |
| 1/4 | 6.38% | −0.30% |
| 1/8 | 4.12% | −0.23% |
| 1/16 | 2.12% | −0.08% |
| 1/1024 | −0.20% | −0.06% |
처리량 쪽 영향은 그리 크지 않았습니다. 지연이 눈에 띄게 늘어나는 것을 피하려면 샘플링이 필요했습니다. 1/16보다 낮은 비율에서는 지연과 처리량의 손해가 모두 실험 오차 안에 들었습니다. 트래픽이 많은 서비스는 1/1024까지 낮춰도 쓸 만큼의 트레이스가 남았습니다.
모으는 쪽 부담도 쟀습니다. 일부러 비현실적으로 무겁게 건 부하 시험에서도 수집 데몬은 CPU(Central Processing Unit) 코어 하나의 0.3%를 넘지 않았습니다. 저장소에 쌓인 스팬 하나는 평균 426바이트였습니다. 추적 데이터를 모으는 데 든 트래픽은 Google 프로덕션 네트워크 트래픽의 0.01% 미만이었습니다.
모으고 쌓는 인프라
트레이스를 모으고 저장하는 데는 상당한 인프라가 들 수 있습니다. 수집기와 추적 백엔드는 애플리케이션과 별개로 운영해야 하는 시스템입니다.
양은 금방 불어납니다. 2010년 무렵 Google 프로덕션 클러스터는 샘플링을 하고도 하루 1테라바이트가 넘는 트레이스를 만들었습니다. 쓰는 사람들은 기록이 적어도 2주는 남아 있기를 바랐습니다. 비율을 올리면 저장소의 쓰기 처리량 한계에 가까워졌습니다. 기록을 더 촘촘히 남기는 이득은 저장소 기계와 디스크 비용과 맞바꿔야 합니다.
적게 남기면 놓치는 요청
비율을 낮추면 앞의 비용이 줄어드는 대신 기록이 비어 갑니다.
Google 은 경험상 트래픽이 많은 서비스라면 비율을 크게 낮춰도 대부분의 중요한 분석에 지장이 없다고 봅니다. 눈여겨볼 실행 패턴이 한 번 나타나면 수천 번 나타나기 때문입니다. 트래픽이 적은 서비스는 다릅니다. 같은 낮은 비율이면 중요한 사건을 놓칠 수 있습니다.
헤드 샘플링만으로는 오류가 난 트레이스를 빠짐없이 남길 수 없습니다. 트레이스 전체를 보기 전에 정하기 때문입니다.
샘플링 자체에도 비용이 붙습니다.
- 샘플링을 돌리는 계산 비용. 테일 샘플링을 맡는 프록시를 돌리는 비용이 그 예입니다
- 애플리케이션과 데이터가 늘수록 샘플링 방식을 손보는 엔지니어링 비용
- 잘못 고른 방식 때문에 꼭 필요한 정보를 놓치는 기회비용
트레이스·메트릭·로그처럼 시스템을 들여다보려고 남기는 데이터를 통틀어 관측 데이터라고 합니다. 샘플링을 잘못 하면 위와 같은 예상 밖의 비용이 생길 수 있습니다. 그래서 샘플링에 공을 들이기보다 관측 데이터를 받는 쪽에 자원을 더 들이는 편이 쌀 수도 있습니다. 직접 운영하면 계산 자원을, 업체에 맡기면 업체 몫을 늘리는 것입니다. 어느 쪽이 싼지는 백엔드와 데이터의 성질, 샘플링을 얼마나 잘 하느냐에 달렸습니다.
| 많이 남기면 | 적게 남기면 |
|---|---|
| 요청 지연이 늘어납니다. 웹 검색 클러스터에서 전부 남기면 평균 지연 변화가 16.3% | 트래픽이 적은 서비스의 드문 사건을 놓칠 수 있습니다 |
| 저장소 용량과 쓰기 처리량이 한계에 가까워집니다 | 고르지 않은 요청은 나중에 원인을 찾을 기록이 없습니다 |
분산 추적 대신 다른 길을 고르는 조건
이 결정이 겨누는 것은 요청 하나가 서비스 여럿을 지나는 구조입니다. 한 요청의 로그가 여러 서비스의 로그에 흩어지는 문제를 풉니다.
프로그램을 고칠 수 없으면 블랙박스 방식이 남습니다. 주고받은 메시지 기록만 놓고 통계로 묶으니 계측을 넣지 않아도 됩니다. 대신 정확해지려면 데이터가 더 많이 필요합니다.
작은 오버헤드도 견디기 어려운 서비스가 있습니다. 아주 잘 다듬은 서비스에서는 감시 오버헤드가 작아도 쉽게 눈에 띕니다. 그러면 배포 팀이 추적을 끄는 쪽을 고를 수도 있습니다.
예시
W3C Trace Context 의 traceparent
호출하는 쪽이 이 요청을 샘플링하기로 했을 때 실리는 헤더 한 줄입니다.
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
줄표로 네 칸이 나뉩니다.
| 칸 | 값 | 뜻 |
|---|---|---|
| version | 00 |
헤더 형식의 판 |
| trace-id | 4bf92f3577b34da6a3ce929d0e0e4736 |
트레이스 ID. 16바이트. 0만 있으면 틀린 값입니다 |
| parent-id | 00f067aa0ba902b7 |
부른 쪽의 스팬 ID. 8바이트. 받는 쪽에서는 부모 ID가 됩니다 |
| trace-flags | 01 |
샘플링 표시. 01은 부른 쪽이 기록했을 수 있음, 00은 기록하지 않음 |
tracestate 는 도구별 정보를 덧붙이는 헤더입니다. 아래는 Congo 라는 도구로 추적하는 클라이언트가 Rojo 라는 도구로 추적하는 서버를 부르는 경우입니다. 클라이언트가 보내는 헤더는 다음과 같습니다.
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: congo=t61rcWkgMzE
서버는 다음 서비스를 부를 때 이렇게 바꿔 보냅니다.
traceparent: 00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01
tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
trace-id는 바뀌지 않았습니다. parent-id만 서버 자신의 스팬 ID로 바뀌었습니다. tracestate 는 받은 항목을 지우지 않고 맨 왼쪽에 자기 항목을 더했습니다. Congo 의 항목 t61rcWkgMzE 는 parent-id b7ad6b7169203331 을 Base64 로 바꾼 것입니다. 꼭 그렇게 해야 하는 것은 아닙니다.
Zipkin 의 B3 헤더
같은 트레이스 컨텍스트가 다른 헤더 이름으로 실린 모습입니다. B3(B3 Propagation)는 Zipkin 쪽에서 쓰는 전파 형식입니다. b3 헤더와 x-b3- 로 시작하는 헤더 이름들을 정하는 규격입니다. 여러 헤더로 나눠 보내면 다음과 같습니다.
X-B3-TraceId: 80f198ee56343ba864fe8b2a57d3eff7
X-B3-ParentSpanId: 05e3ac9a4f6e3b90
X-B3-SpanId: e457b5a2e4d86bd1
X-B3-Sampled: 1
B3 가 싣는 것은 일 하나를 트레이스 나무 안에 놓는 식별자들입니다. X-B3-SpanId 는 그 일의 스팬 ID입니다. X-B3-ParentSpanId 는 그 스팬의 부모 스팬 ID입니다. 앞 「상세」의 표에서 스팬 ID와 부모 ID 두 칸을 같이 실은 꼴입니다.
같은 상태를 헤더 하나로 접을 수도 있습니다.
b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90
순서는 트레이스 ID, 스팬 ID, 샘플링 상태, 부모 스팬 ID입니다. 뒤의 둘은 빼도 됩니다. JMS(Java Message Service)는 X-B3- 로 시작하는 헤더 이름을 허용하지 않습니다. 그래서 JMS 에서는 한 줄짜리 b3 만 쓰기를 권합니다.
AWS X-Ray 의 추적 헤더
AWS(Amazon Web Services)의 추적 서비스 X-Ray 는 같은 내용을 헤더 하나에 키와 값으로 적습니다.
X-Amzn-Trace-Id: Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1
Root 가 트레이스 ID, Parent 가 부른 쪽 기록의 ID, Sampled 가 샘플링 결정입니다. X-Ray 는 이 기록을 세그먼트라고 부릅니다. Parent 칸은 부모 세그먼트 ID입니다. 요청이 처음 닿은 X-Ray 연동 서비스가 이 헤더를 붙입니다.
세 형식을 나란히 놓으면 트레이스 ID와 샘플링 결정은 모두 한 칸씩 싣습니다. 갈리는 것은 부모 관계를 잇는 칸입니다. W3C 와 X-Ray 는 부른 쪽의 ID 하나만 싣습니다. 받는 쪽이 그것을 부모 ID로 적습니다. B3 는 스팬 ID와 부모 스팬 ID 둘을 싣습니다.
| 싣는 것 | W3C Trace Context | B3 | X-Ray |
|---|---|---|---|
| 트레이스 ID | traceparent 의 trace-id |
X-B3-TraceId |
Root |
| 부모 관계를 잇는 칸 | traceparent 의 parent-id 하나 |
X-B3-SpanId · X-B3-ParentSpanId 둘 |
Parent 하나 |
| 샘플링 결정 | traceparent 의 trace-flags |
X-B3-Sampled |
Sampled |
OpenTelemetry 문서의 스팬 세 벌
OpenTelemetry 는 추적 데이터를 만들고 넘기는 방법을 정해 두는 프로젝트입니다. 언어별 SDK(Software Development Kit)와, 서비스가 내보낸 데이터를 받아 처리하는 수집기(OpenTelemetry Collector)도 함께 냅니다. 「운영」 절의 설정 키와 샘플러는 이 SDK와 수집기의 것입니다.
아래는 헤더가 넘어간 결과로 남는 기록입니다. 이 JSON(JavaScript Object Notation)은 OpenTelemetry 문서가 설명용으로 적은 것입니다. 특정 전송 형식이 아닙니다. 필드 일부만 옮겼습니다.
{ "name": "hello",
"context": { "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "051581bf3cb55c13" },
"parent_id": null,
"start_time": "2022-04-29T18:52:58.114201Z",
"end_time": "2022-04-29T18:52:58.114687Z" }
{ "name": "hello-greetings",
"context": { "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "5fb397be34d26b51" },
"parent_id": "051581bf3cb55c13" }
{ "name": "hello-salutations",
"context": { "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "93564f51e1abe1c2" },
"parent_id": "051581bf3cb55c13" }
세 스팬의 trace_id 가 같습니다. hello 는 parent_id 가 없으니 루트 스팬입니다. 나머지 둘은 parent_id 가 hello 의 span_id 라서 그 아래에 매달립니다. 각 스팬은 생김새가 구조화된 로그 한 줄과 비슷합니다. 다른 점은 이 줄들이 서로 다른 프로세스와 서버에서 나와도 번호로 이어진다는 것입니다.
실패
분산 추적이 깨지는 모양은 둘입니다. 요청 하나가 트레이스 여러 개로 쪼개지거나, 남은 트레이스가 실제와 다른 그림을 보입니다.
| 조건 | 보이는 것 |
|---|---|
| 컨텍스트를 안 넘기는 구간이 끼었다 | 그 뒤가 새 트레이스로 시작합니다 |
받은 traceparent 가 형식에 안 맞는다 |
받은 쪽이 새 트레이스를 엽니다 |
| 메시지에 컨텍스트를 안 실었다 | 보낸 쪽과 받은 쪽 트레이스가 남남이 됩니다 |
| 서비스마다 샘플링 결정이 갈린다 | 트레이스 일부 스팬만 남습니다 |
| 서버마다 시계가 다르다 | 서버 쪽 시각의 앞뒤가 뒤집혀 보일 수 있습니다 |
| 여러 요청을 묶어 처리한다 | 묶음의 일이 요청 하나에 몰려 보입니다 |
아래에서 괄호로 붙는 MUST · SHOULD · SHOULD NOT 은 규칙의 세기를 나타내는 표시입니다. MUST 는 「반드시 그래야 한다」, SHOULD 는 「그러기를 권한다」, SHOULD NOT 은 「그러지 않기를 권한다」입니다.
컨텍스트를 안 넘기는 구간
계측이 안 된 통신 라이브러리를 쓰는 프로그램은 추적을 지원하지 못합니다. 원시 TCP(Transmission Control Protocol) 소켓으로 직접 주고받는 프로그램이 그런 예입니다. 표준이 아닌 제어 흐름 도구를 쓰는 코드에서도 추적이 경로를 놓칩니다. 거꾸로 관계없는 사건 둘을 인과로 잘못 잇는 경우도 있습니다.
이런 구간을 지난 요청은 다음 서비스에 traceparent 없이 도착합니다. 헤더를 못 받은 서비스는 지금 요청을 가리킬 트레이스 ID와 부모 ID를 새로 만듭니다. tracestate 만 홀로 왔다면 틀린 것으로 보고 버려야 합니다(MUST).
flowchart TD
subgraph T1["트레이스 ①"]
A["앞단 서비스"] --> B["중간 서비스"]
B --> X["헤더를 안 넘기는 구간"]
end
subgraph T2["트레이스 ② · 새로 시작"]
C["뒷단 서비스"]
end
X -- "traceparent 가 안 실림" --> C
한 벌이어야 할 요청에 트레이스 ID가 둘 생겼습니다. 조회 화면에서 둘을 따로 펴 보게 됩니다. 둘이 같은 요청이라고 알려 줄 공통 번호도 없습니다.
헤더가 와도 형식이 틀리면 결과가 같습니다. 판(version)을 읽을 수 없거나 trace-id·parent-id·trace-flags 가운데 하나라도 틀린 값이면 받은 쪽은 새 traceparent 를 만들고 tracestate 를 지웁니다.
추적 도구가 서로 다른 구간도 위험합니다. 도구마다 전파를 제각각 구현하면 서로 다른 도구가 모은 트레이스는 공유하는 식별자가 없어 이을 수 없습니다. 중간 장비가 도구별 메타데이터를 버릴 수도 있습니다. tracestate 에서 남의 항목을 지우는 것도 다른 시스템의 연결을 깰 수 있습니다. 그래서 자기가 만든 키가 아니면 지우지 말아야 합니다(SHOULD NOT).
메시지 큐 같은 비동기 경계
메시지 큐를 거치는 일은 보내는 때와 받는 때가 떨어져 있습니다. 메시지 시스템이 자기 구간을 추적하더라도 그것은 운반 구간만 덮습니다. 생산자와 소비자를 잇지는 못합니다.
둘을 이으려면 생산자가 메시지를 만들 때의 컨텍스트를 메시지에 실어야 합니다(SHOULD). 이 컨텍스트가 메시지에 안 붙어 있으면 소비자의 트레이스를 생산자의 트레이스에 바로 이을 수 없습니다. 할 수 있다면 중간 경유지가 이 컨텍스트를 못 바꾸게 붙여야 합니다(SHOULD).
이을 때는 부모 관계 대신 스팬 링크(span link)를 씁니다. 스팬 링크는 한 스팬을 다른 스팬 하나 이상과 인과로 묶는 표시입니다. 스팬은 부모를 하나만 가질 수 있습니다. 메시지 여럿을 한 번에 처리하는 소비자는 생산자 여럿과 이어져야 합니다. 부모 하나로는 이것을 나타낼 수 없습니다.
flowchart TD
subgraph P1["생산자 트레이스 1"]
A1["메시지 만들기 · 컨텍스트를 싣는다"]
end
subgraph P2["생산자 트레이스 2"]
A2["메시지 만들기 · 컨텍스트를 싣는다"]
end
subgraph Q["소비자 트레이스 · 따로 시작"]
C1["메시지 둘을 한 번에 처리"]
end
A1 -. "스팬 링크" .-> C1
A2 -. "스팬 링크" .-> C1
트레이스는 따로 남습니다. 대신 소비자 스팬이 링크로 생산자 스팬들을 가리켜서 서로 인과로 묶입니다. OpenTelemetry 의 메시징 규약은 컨텍스트를 메시지에 붙이고 꺼내는 구체 방식을 정하지 않습니다.
서비스마다 갈린 샘플링 결정
요청 일부만 기록하는 방식은 그 자체로 트레이스를 끊을 수 있습니다. 구성 요소마다 무작위로, 또는 제 사정대로 기록을 정하면 모든 트레이스의 데이터가 조각납니다. 앞 서비스는 남기고 뒤 서비스는 버리면 나무의 한 가지가 전부 빕니다.
조각남을 줄이려고 권하는 규칙이 있습니다. 기록을 정해야 하는 구성 요소는 받은 샘플링 표시를 따릅니다(SHOULD). 결정을 미루거나 늦춘 구성 요소는 표시를 바꾸지 않고 넘깁니다.
비율로 정하는 샘플러를 모든 스팬에 걸면 이 규칙이 깨질 수 있습니다. OpenTelemetry 의 TraceIdRatioBased 샘플러는 부모의 샘플링 표시를 무시합니다(MUST). 트레이스 ID를 해시하는 정확한 방법도 정해져 있지 않습니다. 그래서 언어별 SDK가 다르거나 같은 언어의 판이 달라도 같은 입력에 다른 답을 낼 수 있습니다.
이런 까닭에 이 샘플러는 루트 스팬에만 쓰기를 권합니다. 나머지 스팬은 부모 결정을 따르는 ParentBased 샘플러로 감쌉니다.
샘플링 표시는 명령이 아니라 권고입니다. 받는 쪽은 부른 쪽을 믿을 수 없거나, 부른 쪽에 버그가 있거나, 자기 부하가 더 커서 비율을 더 낮춰야 할 수 있습니다.
서버마다 다른 시계
원격 호출 하나의 스팬에는 대개 클라이언트와 서버 두 호스트의 기록이 같이 들어갑니다. 두 시각은 서로 다른 기계의 시계에서 나옵니다. 시계가 어긋나 있으면 서버 쪽 일이 클라이언트가 요청을 보내기도 전에 시작한 것처럼 보일 수 있습니다.
이 어긋남은 순서로 걸러 냅니다. 클라이언트는 언제나 서버가 받기 전에 보냅니다. 서버는 언제나 클라이언트가 응답을 받기 전에 보냅니다. 이 두 사실이 서버 쪽 시각의 하한과 상한이 됩니다.
sequenceDiagram
participant Cl as 클라이언트
participant Sv as 서버
Note over Cl: 요청을 보낸 시각 · 하한
Cl->>Sv: 요청
Note over Sv: 받은 시각과 답한 시각은<br/>하한과 상한 사이에 있어야 한다
Sv-->>Cl: 응답
Note over Cl: 응답을 받은 시각 · 상한
묶음 처리와 앞선 요청
추적은 하위 시스템이 한 번에 요청 하나를 위해 일한다고 가정합니다. 디스크 쓰기를 모아 한 번에 하듯 요청 몇 개를 쌓았다가 한꺼번에 처리하면 이 가정이 깨집니다. 추적 중인 요청 하나가 실제보다 훨씬 큰 일의 책임을 진 것처럼 보일 수 있습니다. 추적 중인 요청 여럿이 한 묶음에 들어가면 트레이스 ID가 하나뿐이라 그중 하나만 그 스팬의 주인으로 보입니다.
시간이 걸린 곳을 짚는 것과 원인을 찾는 것도 다릅니다. 어떤 요청은 자기 탓이 아니라 앞에 줄 선 다른 요청들 때문에 늦어질 수 있습니다. 트레이스는 어디서 시간이 걸렸는지는 보여도 그 까닭까지 늘 보여 주지는 않습니다.
운영
굴릴 때 만지는 손잡이는 넷입니다. 얼마나 남길지, 어디서 고를지, 얼마나 오래 둘지, 바깥 컨텍스트를 믿을지입니다.
샘플러 설정 키와 기본값
OpenTelemetry SDK 는 서비스 프로세스의 환경 변수로 샘플러와 전파 형식을 정합니다.
| 환경 변수 | 기본값 | 하는 일 |
|---|---|---|
OTEL_TRACES_SAMPLER |
parentbased_always_on |
쓸 샘플러 |
OTEL_TRACES_SAMPLER_ARG |
없음 | 샘플러에 넘기는 인자. OTEL_TRACES_SAMPLER 를 정했을 때만 씁니다 |
OTEL_PROPAGATORS |
tracecontext,baggage |
쓸 전파 형식. 쉼표로 여럿 |
기본값 parentbased_always_on 은 루트 스팬을 전부 기록하고 나머지는 부모의 결정을 따른다는 뜻입니다. 아무것도 안 바꾸면 이 서비스에서 시작하는 요청을 전부 남깁니다.
OTEL_PROPAGATORS 기본값의 baggage 는 W3C Baggage 형식입니다. 배기지(baggage)는 트레이스 ID·스팬 ID 말고도 컨텍스트에 함께 실려 다음 서비스로 넘어가는 항목입니다. 기본값이 두 형식을 다 켜 둡니다. 손대지 않으면 트레이스 컨텍스트와 배기지가 함께 넘어갑니다.
OTEL_TRACES_SAMPLER 에 넣을 수 있는 이름은 아래 표와 같습니다.
| 이름 | 뜻 |
|---|---|
always_on · always_off |
전부 남김 · 전부 버림 |
traceidratio |
트레이스 ID 비율로 정함. 부모 결정을 무시합니다 |
parentbased_always_on · parentbased_always_off |
루트는 전부 남김·전부 버림, 나머지는 부모를 따름 |
parentbased_traceidratio |
루트는 비율로, 나머지는 부모를 따름 |
parentbased_jaeger_remote · jaeger_remote |
Jaeger 원격 샘플러 |
xray |
AWS X-Ray 중앙 샘플링 |
비율 샘플러를 쓰면 OTEL_TRACES_SAMPLER_ARG 에 0부터 1 사이의 확률을 적습니다. "0.25" 처럼 적습니다. 비워 두면 1.0입니다. 알아볼 수 없는 인자를 넣으면 로그를 남기고 무시합니다. 인자를 안 넣은 것처럼 동작합니다.
ParentBased 샘플러는 부모가 어디 있는지와 부모가 기록했는지로 네 경우를 가릅니다. 경우마다 쓸 샘플러를 따로 바꿔 끼울 수 있습니다. 기본은 아래와 같아서 두 줄이 같습니다.
| 부모 | 부모가 기록함 | 부모가 기록 안 함 |
|---|---|---|
| 다른 프로세스에서 온 부모 | 기록 | 버림 |
| 같은 프로세스 안의 부모 | 기록 | 버림 |
OTEL_PROPAGATORS 에는 tracecontext 말고도 b3 · b3multi · xray 를 넣을 수 있습니다. 앞에서 본 B3 한 줄 헤더, B3 여러 헤더, X-Ray 헤더입니다. jaeger 와 ottrace 는 폐기 예정(Deprecated) 상태입니다. 형식이 다른 도구와 이어야 하는 구간이 있으면 이 목록에 그 형식을 더합니다.
샘플링을 켜는 조건
샘플링은 들여다볼 것을 잃지 않으면서 관측 데이터에 드는 비용을 줄이는 가장 효과적인 방법 가운데 하나로 꼽힙니다. 데이터를 걸러 내거나 미리 집계하는 다른 방법은 표본이 전체를 대표한다는 성질을 지키지 못합니다. 데이터가 많을수록 대표 표본에 필요한 비율은 낮아집니다. 양이 많은 시스템이면 1% 이하로도 나머지 99%를 꽤 정확히 대표하는 일이 흔합니다.
| 샘플링을 고려할 때 | 샘플링하지 않을 때 |
|---|---|
| 초당 트레이스가 1000개 이상 나온다 | 데이터가 아주 적다. 초당 작은 트레이스 수십 개 이하 |
| 트레이스 대부분이 변화 적은 정상 트래픽이다 | 관측 데이터를 집계로만 쓴다. 미리 집계하면 된다 |
| 규제처럼 데이터를 버리면 안 되는 사정이 있다. 샘플링에서 빠진 데이터를 값싼 저장소로 돌릴 수도 없다 |
비율을 하나로 고정하면 트래픽이 적은 서비스가 손해를 봅니다. 확률 대신 「단위 시간당 몇 개를 남길지」를 정하는 적응형 방식이 그 대안입니다. 트래픽이 적은 서비스는 비율이 저절로 올라가고 많은 서비스는 내려갑니다. 이때 쓴 확률은 트레이스와 함께 적어 두어 나중에 빈도를 셈할 수 있게 합니다. Dapper 는 고정 비율 1/1024로 시작했습니다. 2010년 무렵에는 이 방식으로 옮겨 가는 중이었습니다.
테일 샘플링을 굴릴 때
OpenTelemetry Collector 에는 정해 둔 정책에 따라 트레이스를 고르는 tail sampling 프로세서가 있습니다. 이 프로세서의 안정도는 베타입니다.
이 프로세서에는 지켜야 할 조건이 있습니다. 한 트레이스의 스팬은 전부 같은 수집기 인스턴스에 도착해야 합니다(MUST). 그래야 트레이스 전체를 보고 정할 수 있습니다. 수집기를 여러 대로 늘리면 같은 트레이스의 스팬이 같은 인스턴스로 가도록 따로 맞춰야 합니다.
맞추는 방법은 수집기를 두 겹으로 두는 것입니다. 앞 겹 수집기는 로드 밸런싱 익스포터로 스팬을 내보냅니다. 익스포터는 수집기가 받은 데이터를 다음 목적지로 내보내는 부품입니다. 이 익스포터가 같은 트레이스의 스팬을 뒤 겹의 같은 수집기로 모읍니다. 뒤 겹 수집기가 테일 샘플링을 합니다.
flowchart TD
S1["서비스 1 · 트레이스 X 의 스팬"]
S2["서비스 2 · 트레이스 X 의 스팬"]
subgraph F["앞 겹 · 로드 밸런싱 익스포터"]
L1["수집기 1"]
L2["수집기 2"]
end
subgraph B["뒤 겹 · tail sampling 프로세서"]
TA["수집기 A · 트레이스 X 를 모아 판정"]
TB["수집기 B · 다른 트레이스"]
end
S1 --> L1
S2 --> L2
L1 -- "트레이스 X" --> TA
L2 -- "트레이스 X" --> TA
| 설정 | 기본값 | 뜻 |
|---|---|---|
decision_wait |
30s |
트레이스마다 타이머 처리까지 기다리는 시간. sampling_strategy 가 trace-complete 면 판정 시점을, span-ingest 면 대기분을 정리하는 시점을 정합니다 |
decision_wait_after_root_received |
0s |
루트 스팬을 받은 뒤 더 일찍 정하게 하는 시간. 0s 면 끔 |
num_traces |
50000 |
메모리에 들고 있는 트레이스 수 |
expected_new_traces_per_sec |
0 |
초당 새 트레이스 예상 수. 자료 구조를 미리 잡는 데 씁니다 |
메모리의 트레이스는 고리 버퍼(circular buffer)에 담깁니다. 새 트레이스가 오면 가장 오래된 트레이스가 밀려납니다. decision_wait 가 지나기 전에 밀려난 트레이스는 샘플링 판정 없이 버려집니다. 이 일을 줄이려면 num_traces 를 늘리거나 decision_wait 를 줄입니다. 대가로 메모리를 더 씁니다.
테일 샘플링을 맡는 구성 요소는 많은 데이터를 받아 들고 있어야 하는 상태 저장 시스템입니다. 트래픽에 따라 계산 노드가 수십에서 수백 대 들 수 있습니다. 들어오는 양을 못 따라가면 계산이 덜 드는 방식으로 물러서야 할 수도 있습니다.
스팬이 서비스에서 수집기를 거쳐 백엔드로 가는 길을 텔레메트리 파이프라인이라고 합니다. 양이 아주 많은 서비스는 앞에서 헤드 샘플링으로 작은 비율만 거릅니다. 그 뒤 파이프라인 뒤쪽에서 테일 샘플링을 한 번 더 하기도 합니다. 파이프라인이 넘치지 않게 하려는 것입니다.
저장 직전에 한 번 더 거르는 방법도 있습니다. Dapper 는 수집 단계에서 스팬마다 트레이스 ID를 0과 1 사이의 수로 해시했습니다. 그 수가 정해 둔 수집 계수보다 작을 때만 저장소에 썼습니다. 트레이스 ID로 정하니 트레이스 안의 스팬 일부가 아니라 트레이스 전체가 남거나 버려집니다.
보존 기간과 조회까지 걸리는 시간
Jaeger 는 들어온 트레이스를 모두 받는 기본 저장소를 대개 짧은 TTL(Time To Live, 보존 수명)로 둡니다. 저장 비용을 아끼려는 것입니다. 예로 드는 기간은 2주입니다.
사고와 엮인 트레이스처럼 오래 둘 것은 보관 저장소에 따로 넣습니다. 보관 저장소에는 저절로 들어가는 트레이스가 없습니다. 사람이 화면에서 직접 저장을 눌러야 합니다. 저절로 쌓이지 않으니 보존 기간을 훨씬 길게, 무기한으로까지 잡을 수 있습니다.
기록이 조회되기까지는 시간이 걸립니다. Dapper 에서 서비스가 남긴 기록이 중앙 저장소에 닿기까지 중앙값은 15초 미만이었습니다. 98번째 백분위는 두 갈래로 갈렸습니다. 약 75%의 시간에는 2분 미만이었습니다. 나머지 약 25%의 시간에는 여러 시간까지 늘어날 수 있었습니다. 방금 난 장애의 트레이스가 바로 안 보일 수 있다는 뜻입니다.
바깥에서 들어오는 컨텍스트
밖에서 온 추적 헤더는 위조될 수 있습니다. 악의적인 상대가 헤더를 꾸며 추적 데이터를 흔들거나 헤더 해석 코드의 약점을 노릴 수 있습니다. 믿을 수 없는 곳에서 온 컨텍스트는 무시하거나 걸러 내는 방법을 고려할 수 있습니다. X-Ray 를 쓰는 애플리케이션은 들어오는 요청에서 X-Amzn-Trace-Id 를 지워, 사용자가 트레이스 ID나 샘플링 결정을 마음대로 넣는 일을 막을 수 있습니다.
나가는 쪽도 봅니다. 바깥 서비스로 넘기는 트레이스 ID, 스팬 ID, 배기지 항목이 내부 구조나 업무 논리를 드러낼 수 있습니다.
추적 전체를 끄는 손잡이도 있습니다. Dapper 는 프로덕션 안전장치로 추적을 끌 수 있게 해 두었습니다. 안정성과 낮은 오버헤드에 확신이 서기 전까지는 기본이 꺼짐이었습니다. 수집과 분석은 기록 생성보다 비상시에 끄기가 더 쉽습니다.
관련 항목
분산 추적이 남기는 기록의 구성 요소
트레이스 · 스팬 · 루트 스팬 · 트레이스 ID · 스팬 ID · 부모 스팬 · 스팬 속성 · 스팬 이벤트 · 스팬 링크
요청 번호를 서비스 사이로 넘기는 수단
컨텍스트 전파 · Trace Context · traceparent · tracestate · 배기지 · B3 전파 · 상관 ID · 스레드 로컬 저장소
분산 추적과 나란히 쓰이는 관측 신호
로그 · 메트릭 · 텔레메트리 · 프로파일링 · 로그 집계
분산 추적을 굴리는 처리 단계
계측 · 자동 계측 · 샘플링 · 헤드 샘플링 · 테일 샘플링 · 적응형 샘플링 · 수집기 · 익스포터 · OTLP
분산 추적을 구현한 도구와 표준
OpenTelemetry · Jaeger · Zipkin · AWS X-Ray · OpenTracing · OpenCensus · APM
분산 추적에 앞서거나 이를 정식화한 추적 시스템
Dapper · X-Trace · Pinpoint · Magpie
분산 추적이 속하는 상위 분류
관측성 · 모니터링 · 대시보드 · 사이트 신뢰성 공학
분산 추적으로 찾아내는 장애와 증상
지연 · 병목 · 연쇄 장애 · 타임아웃 · 부분 실패 · N+1 문제 · 꼬리 지연
분산 추적이 전제하는 시스템 구조
마이크로서비스 · 분산 시스템 · API 게이트웨이 · 메시지 큐 · 서비스 메시 · 원격 프로시저 호출 · 미들웨어
분산 추적이 재는 값을 받아 쓰는 지표
서비스 수준 지표 · 서비스 수준 목표 · 백분위 · 응답 시간
분산 추적의 기록을 틀어지게 하는 원인
시계 동기화 · 클럭 스큐 · 배치 처리 · 큐잉 지연
분산 추적과 이름이 헷갈리는 이웃
스택 트레이스 · 실행 추적 · 감사 로그 · strace
다른 이름: distributed tracing · 분산 트레이싱