사전 스팬
개념

스팬

gabury1

스팬은 프로그램이 한 가지 일을 시작해서 끝낼 때까지를 재서 한 건으로 남깁니다. 요청 하나를 처리하는 동안 어느 일에 시간이 얼마나 들었는지가 이 기록으로 드러납니다. 스팬 여러 개를 부모와 자식으로 이어 붙인 것이 트레이스입니다. 웹 문서의 <span> 태그와는 다른 말입니다.

쉽고 빠른 이해

스팬은 「무슨 일을 언제 시작해 언제 끝냈나」를 한 건으로 적어 둔 기록입니다. 주문 요청을 받아 처리한 것, 그 안에서 재고를 조회한 것이 각각 스팬 하나가 됩니다.

로그만 남기면 사건이 일어난 시점만 남습니다. 어떤 일이 얼마나 걸렸고 그 안에서 무엇이 또 돌았는지는 사람이 시각을 맞춰 가며 짜 맞춰야 합니다. 스팬은 시간 구간과 부모 자식 관계를 처음부터 함께 적습니다.

어떻게 도는가:

  1. 일을 시작할 때 스팬을 열면 시작 시각과 번호가 붙습니다
  2. 일하는 동안 알게 된 값을 그 스팬에 꼬리표로 붙입니다
  3. 일이 끝나면 스팬을 닫습니다. 닫힌 스팬은 모으는 쪽으로 나갑니다

대가는 양입니다. 잘게 열수록 요청 하나가 만드는 스팬이 불어나고 보관할 양도 같이 늘어납니다.

서비스 하나 안에서 끝나는 일이라면 스팬까지 열 것은 없습니다. 스팬은 일이 서버를 옮겨 다닐수록 값이 커집니다.

상세

병원에 진료를 받으러 가면 접수부터 나올 때까지가 한 덩어리로 흘러갑니다. 그 덩어리 안에 다른 층 검사실에 엑스레이를 찍으러 다녀온 시간이 통째로 들어 있습니다. 전체에서 그 시간을 덜어 내야 의사와 마주 앉아 있던 시간이 남습니다.

스팬은 한 가지 일을 한 번 한 것의 기록입니다. 그 일이 언제 시작해 언제 끝났는지, 무슨 일이었는지, 어느 요청에 속한 일인지가 함께 담깁니다. 웹 요청 하나를 받아 처리한 것, 데이터베이스 쿼리 하나를 보낸 것, 일을 나중에 처리하려고 쌓아 두는 메시지 큐에서 메시지 하나를 꺼내 처리한 것이 각각 스팬 하나가 됩니다. 한 스팬 안에 다른 스팬이 들어가기도 합니다.

로그는 그때 무슨 일이 있었는지를 시점 하나에 찍습니다. 그래서 어떤 작업이 얼마나 걸렸는지 알려면 시작 줄과 끝 줄을 찾아 시각을 빼야 합니다. 그 작업이 품고 있던 하위 작업은 사람이 짜 맞춰야 합니다. 스팬은 시간 구간과 「누가 시킨 일인가」를 처음부터 같이 적어서 그 짜 맞추는 일을 없앱니다.

서비스를 여러 개로 쪼갠 마이크로서비스 구조에서 이 차이가 커집니다. 요청 하나가 서비스 넷을 거치면 로그도 네 군데에 흩어지지만, 스팬은 어느 요청에 속한 일인지를 스스로 들고 다닙니다.

이 절은 먼저 스팬 하나에 무엇이 담기는지 봅니다. 이어서 스팬이 열리고 닫히는 과정, 스팬끼리 겹쳐 이루는 나무, 원격 호출에서 스팬이 둘로 나뉘는 대목을 짚습니다. 끝으로 로그와 갈리는 지점, 스팬을 어디까지 잘게 열지를 가릅니다.

스팬 하나에 담기는 것

스팬 하나는 대개 이런 것을 담습니다.

담는 것 무엇인가
이름 무슨 일이었나. GET /orders 나 재고 조회 같은 이름
시작 시각과 끝 시각 언제 시작해 언제 끝났나. 둘의 차이가 걸린 시간
스팬 ID 이 스팬 자신의 번호
부모 스팬 ID 이 일을 시킨 바로 위 스팬의 번호
트레이스 ID 이 스팬이 어느 요청의 기록에 속하나
속성 이 일에 대해 알게 된 값을 이름과 함께 붙인 꼬리표
이벤트 이 일이 도는 동안 한 시점에 일어난 것
상태 성공으로 끝났나 실패로 끝났나

표에서 스팬을 스팬답게 만드는 것은 시각 두 개입니다. 시점 하나만 적으면 그것은 로그 한 줄입니다. 시작과 끝을 함께 적어야 「이 일이 얼마나 걸렸다」는 구간이 생깁니다.

표의 ID 셋은 흩어진 스팬을 다시 잇는 실입니다. 셋 다 이름이 붙은 번호일 뿐입니다. 트레이스 ID 는 같은 요청에 속한 스팬이 모두 똑같이 나눠 가지므로 「이 스팬들은 한 요청의 기록이다」를 말해 줍니다. 부모 스팬 ID 는 누가 이 일을 시켰는지를 가리켜 스팬들을 나무 모양으로 세웁니다.

속성과 이벤트는 둘 다 덧붙임이지만 시간이 다릅니다. 속성은 구간 전체에 붙는 꼬리표입니다. 응답 코드나 조회한 건수처럼 그 일 전체를 설명하는 값이 여기 붙습니다. 이벤트는 구간 안의 한 시점에 찍히는 기록이라 「재시도를 한 번 했다」처럼 도중에 일어난 것을 남깁니다.

스팬이 열리고 닫히는 과정

스팬은 저절로 생기지 않고 코드가 열고 닫습니다. 이렇게 기록을 내보내도록 코드를 심는 일을 계측이라고 부릅니다. 아래는 그 모양만 보이는 의사 코드입니다.

스팬 열기("주문 조회")    // 시작 시각 찍힘
  속성 붙이기("건수", 3)  // 스팬의 꼬리표
스팬 닫기()               // 끝 시각 찍힘

여는 순간 시작 시각과 스팬 ID 가 정해집니다. 그 사이에 붙인 속성과 이벤트는 모두 이 스팬에 딸립니다. 닫는 순간 끝 시각이 찍힙니다. 그때서야 한 건이 완성되어, 스팬을 모아 두고 화면으로 보여 주는 서버로 나갑니다.

닫지 않으면 그 기록은 나가지 않습니다. 예외가 나서 닫는 줄을 건너뛰면 스팬이 열린 채로 남습니다. 그 일은 없었던 것처럼 보입니다. 계측 라이브러리들이 블록을 벗어날 때 알아서 닫아 주는 문법을 같이 주는 까닭입니다.

스팬이 거치는 상태는 이렇게 갈립니다.

stateDiagram-v2
    state "열린 채로 남음" as 미완
    [*] --> 열림: 스팬 열기
    열림 --> 닫힘: 스팬 닫기
    닫힘 --> [*]: 모으는 서버로 나감
    열림 --> 미완: 예외로 닫는 줄을 건너뜀
    미완 --> [*]: 기록이 안 나감

스팬끼리 겹쳐 이루는 나무

한 일을 하는 동안 다른 일을 시작하면 스팬이 겹쳐 열립니다. 주문을 받아 처리하는 동안 재고를 조회하고 결제를 부르면 스팬 넷이 생깁니다. 이 그림에서 아래로 뻗은 선은 일이 도는 차례가 아니라 부모와 자식 관계입니다.

flowchart TD
    subgraph T["트레이스 ID = T1"]
    R["주문 접수<br/>스팬 ID 1 · 부모 없음"]
    R --> I["재고 조회<br/>스팬 ID 2 · 부모 1"]
    R --> P["결제 요청<br/>스팬 ID 3 · 부모 1"]
    P --> C["카드사 호출<br/>스팬 ID 4 · 부모 3"]
    end

재고 조회 스팬은 자기 부모로 주문 접수 스팬의 번호를 적습니다. 그림에서 아래로 뻗은 선이 그 부모 스팬 ID 입니다. 네 스팬이 트레이스 ID 를 똑같이 나눠 가진 것도 그림에 함께 적혀 있습니다.

부모가 없는 맨 위 스팬을 루트 스팬이라고 부릅니다. 루트 스팬은 요청이 시스템에 처음 들어온 곳에서 생깁니다.

같은 네 스팬을 시간축 위에 막대로 놓으면 겹치는 모양이 드러납니다. 막대의 가로 폭이 걸린 시간입니다. 왼쪽에서 오른쪽으로 시간이 흐릅니다.

block-beta
columns 8
  t["시간 →"]:8
  r["주문 접수"]:8
  space:1 i["재고 조회"]:2 space:5
  space:3 p["결제 요청"]:4 space:1
  space:4 c["카드사 호출"]:2 space:2

자식 구간은 대개 부모 구간 안에 들어 있습니다. 부모가 걸린 시간에서 자식들이 걸린 시간을 빼면 그 서비스가 자기 일에 쓴 시간이 남습니다. 막대에서 자식이 안 덮은 빈칸이 그 시간입니다.

결제 요청이 오래 걸렸습니다. 그 안의 카드사 호출이 짧았다면 남는 시간은 결제 서비스가 자기 안에서 쓴 것입니다.

부르는 쪽과 받는 쪽의 스팬 두 개

한 서비스가 다른 서비스를 부르면 스팬은 하나가 아니라 둘 생깁니다. 앞 그림의 재고 조회가 그런 호출입니다. 부르는 쪽인 주문 서비스는 요청을 보내고 응답을 기다린 구간을 자기 스팬으로 남깁니다. 받는 쪽인 재고 서비스는 그 요청을 처리한 구간을 또 하나의 스팬으로 남깁니다.

앞 그림에는 부르는 쪽 스팬만 그려 두었습니다. 둘을 함께 놓으면 이런 모양입니다.

sequenceDiagram
    participant 주문 as 주문 서비스
    participant 재고 as 재고 서비스
    activate 주문
    Note over 주문: 재고 조회 스팬 열림
    주문->>재고: 요청 · 트레이스 ID 와 스팬 ID 를 헤더에 싣는다
    activate 재고
    Note over 재고: 받는 쪽 스팬 열림
    재고-->>주문: 응답
    deactivate 재고
    deactivate 주문
    Note over 주문,재고: 두 구간의 차이 = 네트워크를 오간 시간 + 차례를 기다린 시간
    Note over 주문,재고: 중간에 두 ID 를 안 넘기는 서비스가 끼면 여기서 실이 끊긴다

두 스팬을 이으려면 부르는 쪽이 트레이스 ID 와 자기 스팬 ID 를 요청에 실어 보내야 합니다. 이 일을 컨텍스트 전파라고 부릅니다.

HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 규약)로 부를 때는 두 ID 를 요청 헤더에 싣습니다. 헤더는 본문과 따로 붙여 보내는 이름표 줄이라 받는 쪽이 본문을 건드리지 않고 읽을 수 있습니다.

스팬이 둘이라 알 수 있는 것이 하나 더 생깁니다. 부르는 쪽이 잰 구간이 받는 쪽이 잰 구간보다 훨씬 길면, 그 차이는 네트워크를 오간 시간이거나 받는 쪽에서 차례를 기다린 시간입니다. 한쪽 기록만 있으면 이 둘을 가를 수 없습니다.

이 두 ID 를 넘기지 않는 서비스가 중간에 하나 끼면 거기서 실이 끊깁니다. 그 뒤의 스팬들은 부모를 못 찾아 새 요청의 기록으로 시작해 버립니다.

로그 한 줄과 갈리는 대목

로그와 스팬, 메트릭 셋은 같은 시스템을 보되 답하는 질문이 다릅니다. 메트릭은 초당 요청 수나 평균 응답 시간처럼 시간에 따라 모아 둔 숫자입니다.

무엇을 남기나 답하는 질문
로그 한 시점에 일어난 사건 그때 무슨 일이 있었나
스팬 시작과 끝이 있는 작업 한 건 이 일이 얼마나 걸렸고 그 안에서 무엇이 돌았나
메트릭 시간에 따라 모은 숫자 전체로 봐서 얼마나 자주, 얼마나 걸리나

이 셋을 묶어 텔레메트리라고 부릅니다. 텔레메트리는 시스템이 돌아가면서 스스로 내보내는 기록을 통틀어 부르는 말입니다. 이 기록들을 모아 두고 시스템 속사정을 밖에서 살피는 일이 관측성입니다.

로그와 스팬을 이어 붙이는 방법도 있습니다. 로그 한 줄마다 트레이스 ID 를 같이 적어 두면, 오래 걸린 스팬을 찾은 다음 그 구간의 로그로 바로 넘어갈 수 있습니다.

스팬을 여는 경계와 양 조절

스팬은 경계에서 엽니다. 요청을 받는 곳, 다른 서비스를 부르는 곳, 데이터베이스나 캐시에 나가는 곳, 큐에서 메시지를 꺼내 처리하는 곳입니다. 시간을 잡아먹는 곳이 대개 이 경계들이라 여기만 열어도 어디서 막혔는지가 드러납니다.

함수마다 스팬을 열면 수가 빠르게 불어납니다. 반복문 안에서 여는 것이 특히 그렇습니다. 스팬이 늘면 요청마다 드는 일과 보관할 양이 같이 늘어납니다. 화면에 펼쳤을 때 사람이 읽기도 어려워집니다.

이름에는 값을 넣지 않습니다. 주문 조회 대신 주문 4821 조회 로 이름을 지으면 스팬 이름이 요청 수만큼 생겨서 같은 종류의 일을 모아 셀 수 없게 됩니다. 요청마다 달라지는 값은 이름이 아니라 속성으로 붙입니다.

양이 문제가 되면 요청 가운데 일부만 골라 남깁니다. 이것을 샘플링이라고 합니다. 이때 한 요청의 스팬은 전부 남기거나 전부 버려야 합니다. 앞 서비스는 남기고 뒤 서비스는 버리면 나무가 중간에 잘립니다.

일이 프로세스 하나 안에서 끝난다면 스팬으로 얻는 것이 줄어듭니다. 그 안에서 어느 함수가 시간을 얼마나 썼는지는 그것을 재는 도구인 프로파일러가 더 잘 보여 줍니다. 스팬은 일이 경계를 넘어 다닐수록 값이 커집니다.

관련 항목

스팬이 모여 이루는 상위 기록

트레이스 · 루트 스팬 · 분산 트레이싱 · 관측성

스팬 하나를 이루는 구성 요소

스팬 ID · 트레이스 ID · 부모 스팬 ID · 스팬 속성 · 스팬 이벤트 · 스팬 상태 · 스팬 종류 · 스팬 링크

스팬과 나란히 쓰이는 다른 신호

로그 · 메트릭 · 프로파일 · 텔레메트리 · 이벤트

스팬을 만들어 내보내는 단계

계측 · 컨텍스트 전파 · 샘플링 · 익스포터 · 수집기 · 배기지

스팬의 모양을 정하는 표준

OpenTelemetry · W3C 트레이스 컨텍스트 · 시맨틱 컨벤션 · OTLP

스팬을 모아 보여 주는 제품

Jaeger · Zipkin · Grafana Tempo · Datadog

다른 이름: span · spans