LabHub
배우기 러닝패스 코스

분산 트레이싱이 끊기는 자리 · 큐를 건너는 추적 · 실습

생산자와 소비자를 부모-자식으로 이었더니 트레이스가 끝나지 않는다

LabHub 에서 이어서 보기

목표

파일 한 개로 된 작은 큐를 놓고, 생산과 소비를 부모-자식으로 이었을 때 생기는 문제를 덤프로 본 뒤 링크로 바꿉니다. 배치 소비·큐 대기 시간·스팬 종류·링크 속성을 차례로 붙이고, 팬아웃까지 적용한 다음 링크를 따라가 한 주문의 여정을 트레이스 너머로 이어 붙입니다.

왜 중요한가

부모-자식은 '부모가 자식을 기다린다' 는 뜻이다. 그런데 생산자는 소비가 끝나기를 기다리지 않는다. 그 둘을 부모-자식으로 이으면 사용자에게 이미 응답이 나갔는데도 루트 스팬이 닫히지 못하고, 큐가 밀린 날에는 트레이스 하나가 몇 분씩 열려 있는다. 팬아웃이 섞이면 주문 하나가 수천 스팬으로 자라 백엔드가 그 트레이스만 특별 취급하기 시작한다. 링크는 바로 이 자리를 위해 있다 — 인과는 남기되 기다리지는 않는다는 관계다. 소비 쪽을 새 트레이스의 루트로 세우고 생산 스팬을 링크로 가리키면 트레이스 하나하나는 작게 유지되고, 전체 여정은 링크를 따라가며 다시 이어 붙일 수 있다. 헤더를 읽어 끊긴 체인을 잇는 일이나 프로세스 안에서 문맥을 넘기는 일과는 다른 판단이다.

단계

1. /root/tp-links/naive.py 를 만드세요. 덤프 경로는 TRACELAB_OUT 에서 먼저 읽고 없으면 /root/tp-links/01-naive.jsonl 을 씁니다. 큐 파일은 덤프와 같은 디렉터리의 01-q.jsonl 로 잡고 시작할 때 비웁니다. order.submit 스팬 하나 안에서 bus.publish 로 메시지 3건(m-1~m-3)을 넣고 time.sleep(0.25) 로 큐에서 기다린 뒤, 같은 스팬 안에서 bus.poll 로 꺼내 메시지마다 자식 스팬 order.handle 을 만들어 bus.handle 을 부릅니다. 그다음 /root/tp-links/01-problem.txt 에 네 줄을 적으세요 — traces= 뒤에 덤프의 트레이스 수, root_span= 뒤에 루트 스팬 이름, wait_ms= 뒤에 루트 스팬 길이에서 자식들이 덮은 구간을 뺀 값을 정수로, problem= 뒤에 이 모양의 문제가 무엇인지 40자 이상으로.
2. /root/tp-links/linked.py 를 만드세요(기본 덤프 경로 /root/tp-links/02-linked.jsonl, 큐 파일 02-q.jsonl). order.submit 안에서 메시지마다 스팬 order.publish 를 만들고, 그 스팬의 trace_idspan_id 를 16진 문자열로 메시지에 실어 보냅니다. 기다린 뒤 꺼내는 쪽은 order.submit 에서 돌려 스팬 order.process 가 트레이스의 루트가 되게 하고, 메시지에 실려 온 아이디로 Link 를 만들어 links= 로 겁니다. 두 스팬 모두 messaging.message.id 속성에 메시지 아이디를 적습니다.
3. /root/tp-links/batch.py 를 만드세요(기본 덤프 경로 /root/tp-links/03-batch.jsonl, 큐 파일 03-q.jsonl). 이번에는 메시지를 5건(m-1~m-5) 넣고, 기다린 뒤 bus.poll(Q, 5)한 번에 꺼냅니다. 꺼낸 배치 전체를 스팬 하나 order.process.batch 로 처리하고, 그 스팬에 꺼낸 메시지 수만큼 링크를 걸고 정수 속성 messaging.batch.message_count 에 건수를 적습니다. 이 스팬도 트레이스의 루트여야 합니다.
4. /root/tp-links/wait.py 를 만드세요(기본 덤프 경로 /root/tp-links/04-wait.jsonl, 큐 파일 04-q.jsonl). 2단계의 모양으로 돌아가되 메시지에 produced_at_ns 칸을 더해 bus.now_ns() 값을 실어 보내고, 소비 스팬 order.process 에 그 시각과 지금의 차이를 밀리초로 계산해 속성 messaging.queue.wait_ms 로 적습니다. 그리고 /root/tp-links/04-wait.tsv 에 메시지마다 한 줄씩 <메시지 아이디><탭><기다린 밀리초 소수 첫째 자리> 를 적으세요(세 줄).
5. /root/tp-links/kinds.py 를 만드세요(기본 덤프 경로 /root/tp-links/05-kinds.jsonl, 큐 파일 05-q.jsonl). 4단계의 흐름에 스팬 종류를 붙입니다 — order.publishSpanKind.PRODUCER, order.processSpanKind.CONSUMER, 감싸는 order.submit 은 그대로 둡니다. 그리고 두 메시징 스팬에 messaging.system(labbusmessaging.destination.name(ordersmessaging.operation.type(생산은 send, 소비는 processmessaging.operation.name·messaging.message.id 를 답니다. 그다음 /root/tp-links/05-kinds.tsv 에 세 줄을 적으세요 — 각 줄은 <스팬 이름><탭><종류><탭><operation.type> 이고 순서는 order.submit, order.publish, order.process 이며 order.submit 의 셋째 칸은 - 입니다.
6. /root/tp-links/linkattrs.py 를 만드세요(기본 덤프 경로 /root/tp-links/06-linkattrs.jsonl, 큐 파일 06-q.jsonl). 5단계와 같은 흐름이되 Link 를 만들 때 두 번째 인자로 속성을 함께 넘깁니다 — link.relationqueue.message, messaging.message.id 에 그 메시지의 아이디, messaging.destination.nameorders 를 적습니다. 덤프의 links 칸마다 이 세 속성이 들어 있어야 합니다.
7. /root/tp-links/fanout.py 를 만드세요(기본 덤프 경로 /root/tp-links/07-fanout.jsonl, 큐 파일 07-q.jsonl). 6단계의 규칙을 그대로 쓰되 흐름을 늘립니다 — order.submit 이 메시지 3건을 만들고, 소비 쪽에서 주문 A-1002 를 처리하는 order.process 스팬 안에서 두 건을 더 만듭니다(스팬 이름 invoice.publishemail.publish, 메시지 아이디 inv-2eml-2). 잠시 기다린 뒤 그 두 건을 꺼내 invoice.processemail.process 로 각각 처리하며, 이들도 새 트레이스의 루트이고 링크로 앞의 생산 스팬을 가리킵니다. 덤프에는 트레이스가 6개 들어 있어야 합니다.
8. /root/tp-links/journey.py 를 만드세요. python3 journey.py <덤프> <메시지아이디> 로 돌리면 그 메시지를 만든 order.publish 스팬에서 출발해, 링크로 이어진 스팬을 홉 단위로 따라가며 한 줄씩 <홉><탭><trace_id><탭><스팬이름> 을 찍습니다. 출발 스팬이 홉 0 이고, 다음 홉은 앞 홉의 스팬 또는 그 스팬의 자손을 링크로 가리키는 스팬들이며 같은 홉 안에서는 스팬 이름 오름차순으로 찍습니다. 더 따라갈 곳이 없으면 멈춥니다. 이 프로그램을 /root/tp-links/07-fanout.jsonlm-2 로 돌린 출력을 /root/tp-links/08-journey.tsv 에 저장하세요.

참고

단계 8개

  1. 생산과 소비를 부모-자식으로 이으면 무슨 일이 나는가
  2. 소비를 새 트레이스의 루트로 세우고 링크로 잇는다
  3. 배치 소비는 링크 여러 개인 스팬 하나로
  4. 큐에서 기다린 시간을 어떻게 잴 것인가
  5. PRODUCER 와 CONSUMER 는 언제 쓰는가
  6. 링크에 왜 이어져 있는지를 적는다
  7. 하나가 여럿을 만드는 팬아웃까지 적용한다
  8. 링크를 따라가 여정을 다시 이어 붙인다