[Spring Boot] Micrometer Tracing ๋ถ„์‚ฐ ํŠธ๋ ˆ์ด์‹ฑ TraceID, SpanID

2025. 10. 28. 15:49ใ†Spring/[2025] Spring Boot

728x90

 

๐ŸŽฏ ์˜ค๋Š˜ ํ•™์Šต ๋ชฉํ‘œ

๋ถ„์‚ฐ ํŠธ๋ ˆ์ด์‹ฑ์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ (TraceID, SpanID) ๋ฅผ ์ดํ•ดํ•˜๊ณ  ์‹ค์ œ Spring Boot ๋กœ๊ทธ์—์„œ TraceID ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด๊ธฐ !

 

 

1๏ธโƒฃ TraceID / SpanID ๊ฐœ๋…

๐Ÿงฉ ๊ธฐ๋ณธ ์šฉ์–ด ์ •๋ฆฌ

์šฉ์–ด ์˜๋ฏธ ๋น„์œ 

Trace ํ•˜๋‚˜์˜ ์š”์ฒญ ์ „์ฒด ํ๋ฆ„ ์‚ฌ์šฉ์ž๊ฐ€ “์•ฑ์—์„œ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์š”์ฒญ์„ ๋ณด๋‚ธ ์ „์ฒด ์—ฌ์ •”
Span Trace ์•ˆ์˜ ํ•œ ๋‹จ๊ณ„ (์ž‘์—… ๋‹จ์œ„) Controller → Service → DB ๊ฐ๊ฐ ํ•œ “์Šคํ…”
TraceID Trace ์ „์ฒด๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๊ณ ์œ  ID “ํƒ๋ฐฐ ์†ก์žฅ๋ฒˆํ˜ธ”์ฒ˜๋Ÿผ ์ „์ฒด ์—ฌ์ •์„ ์ถ”์ ํ•˜๋Š” ๋ฒˆํ˜ธ
SpanID ๊ฐ๊ฐ์˜ ์ž‘์—…(Span)์„ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ณ ์œ  ID ์†ก์žฅ ์•ˆ์˜ “์„ธ๋ถ€ ๊ตฌ๊ฐ„ ๋ฒˆํ˜ธ” (์˜ˆ: ํ—ˆ๋ธŒ ์ด๋™ ๋‹จ๊ณ„)

TraceID ๋Š” ๋ณดํ†ต ‘ํƒ๋ฐฐ ์†ก์žฅ๋ฒˆํ˜ธ’๋กœ ๋น„์œ ๊ฐ€ ๋งŽ์ด ๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ์†ก์žฅ๋ฒˆํ˜ธ๋กœ ๋ฐฐ์†ก ๊ณผ์ •์„ ์ถ”์ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ TraceID๋กœ ํ•˜๋‚˜์˜ ์š”์ฒญ ํ๋ฆ„์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค. ํƒ๋ฐฐ์˜ ์†ก์žฅ๋ฒˆํ˜ธ = TraceID ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

SpanID๋Š” Trace ์•ˆ์˜ ๊ฐ๊ฐ์˜ ์ž‘์—…์„ ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์œผ๋กœ ํƒ๋ฐฐ ์†ก์žฅ ์•ˆ์˜ ์„ธ๋ถ€ ๊ตฌ๊ฐ„ ๋ฒˆํ˜ธ ? ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

 

  • TraceID ์™€ SpanID ์˜ ์ฐจ์ด
    ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์š”์ฒญ (TraceID: abc123)
    โ”‚
    โ”œโ”€ [span-001] API Gateway ํ†ต๊ณผ
    โ”‚  โ”‚
    โ”‚  โ””โ”€ [span-002] ์ธ์ฆ ์„œ๋น„์Šค
    โ”‚     โ”‚
    โ”‚     โ””โ”€ [span-003] DB ์กฐํšŒ
    โ”‚        โ”‚
    โ”‚        โ””โ”€ [span-004] Redis ์บ์‹œ ํ™•์ธ
    โ”‚
    โ””โ”€ ์‘๋‹ต ์™„๋ฃŒ
    
  • TraceID ๋Š” ์ „์ฒด ์š”์ฒญ์„ ์ถ”์ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹œ์ž‘๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋™์ผํ•˜์ง€๋งŒ, SpanID๋Š” ๊ฐ ๋‹จ๊ณ„๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ID๋กœ ์„œ๋น„์Šค๋งˆ๋‹ค ๋‹ค๋ฅด๋‹ค. Trace๋Š” ์—ฌ๋Ÿฌ Span์œผ๋กœ ์ด๋ฃจ์–ด์ง„ ๊ฒƒ !

 

๐Ÿ˜ฑ TraceID๊ฐ€ ์—†์œผ๋ฉด ๋ฌด์Šจ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ• ๊นŒ ?

์„œ๋น„์Šค์—๋Š” ํ•œ ๊ณ ๊ฐ๋งŒ ์š”์ฒญํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๊ณ ๊ฐ๋“ค์ด ๋™์‹œ์— ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค. ๋”ฐ๋ผ์„œ TraceID ์—†์ด ๋กœ๊ทธ๋ฅผ ์ฐ๊ฒŒ ๋˜๋ฉด ์–ด๋–ค ๊ณ ๊ฐ์ด ์–ด๋–ค ์š”์ฒญ์„ ํ–ˆ๋Š”์ง€ ์ถ”์ ํ•˜๊ธฐ ์–ด๋ ต๋‹ค. (์‹ค์ œ๋กœ ํ˜„์žฌ ๋‚ด๊ฐ€ ์šด์˜ํ•˜๋˜ ์ž๋…€ํฐ์ง€ํ‚ด์ด ์„œ๋น„์Šค๋Š” TraceID ๊ฐ€ ์—†์–ด์„œ ๋กœ๊ทธ ์ถ”์ ์ด ๋„ˆ๋ฌด ๋„ˆ๋ฌด ์–ด๋ ค์›€.. ๋ฏผ์› ๋“ค์–ด์˜ค๋ฉด ๊ฐ€๋ฒˆ์œผ๋กœ ์ถ”์ ํ•ด์•ผ ํ•ด์„œ ์‹œ๊ฐ„์ด ๊ต‰์žฅํžˆ ์˜ค๋ž˜๊ฑธ๋ฆฌ๋Š” ํŽธ)

 

๋กœ๊ทธ๊ฐ€ ์ด๋ ‡๊ฒŒ ์„ž์—ฌ์žˆ๋‹ค๋ฉด?

[15:30:01] UserService - ๋กœ๊ทธ์ธ ์‹œ์ž‘
[15:30:01] OrderService - ์ฃผ๋ฌธ ์ƒ์„ฑ ์‹œ์ž‘
[15:30:02] UserService - DB ์กฐํšŒ ์™„๋ฃŒ
[15:30:02] OrderService - ์žฌ๊ณ  ํ™•์ธ
[15:30:03] UserService - ๋กœ๊ทธ์ธ ์™„๋ฃŒ
[15:30:03] OrderService - ์ฃผ๋ฌธ ์™„๋ฃŒ

→ ๋™์‹œ์— 100๋ช…์ด ์š”์ฒญํ•˜๋ฉด ๊ตฌ๋ถ„์ด ์–ด๋ ค์›€,, ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์–ด๋–ค ์š”์ฒญ์—์„œ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ์ธ์ง€๋„ ์ฐพ๊ธฐ ์–ด๋ ค์›€

 

TraceID, SpanID๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด:

[15:30:01] [trace-001,span-A] UserService - ๋กœ๊ทธ์ธ ์‹œ์ž‘
[15:30:01] [trace-002,span-X] OrderService - ์ฃผ๋ฌธ ์ƒ์„ฑ ์‹œ์ž‘
[15:30:02] [trace-001,span-A] UserService - DB ์กฐํšŒ ์™„๋ฃŒ
[15:30:02] [trace-002,span-X] OrderService - ์žฌ๊ณ  ํ™•์ธ
[15:30:03] [trace-001,span-A] UserService - ๋กœ๊ทธ์ธ ์™„๋ฃŒ
[15:30:03] [trace-002,span-X] OrderService - ์ฃผ๋ฌธ ์™„๋ฃŒ

→ ์ด๋Ÿฐ์‹์œผ๋กœ TraceID์™€ SpanID๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด grep ์œผ๋กœ “trace-001” ๋งŒ ๊ฒ€์ƒ‰ํ•˜๋ฉด ๋˜๋‹ˆ๊นŒ ์ถ”์ ํ•˜๊ธฐ ํ›จ์”ฌ ํŽธํ•˜๋‹ค.

 

 

๐ŸŒ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ TraceID

TraceID ๋Š” MSA ๊ตฌ์กฐ์—์„œ ์‚ฌ์šฉํ•˜๋ฉด ๊ทธ ์ง„๊ฐ€๋ฅผ ๋ฐœํœ˜ํ•˜๋Š”๋ฐ ๊ทธ ์ด์œ ๋Š” ๋‹จ์ผ ์„œ๋ฒ„์—์„œ๋Š” ๋น„๊ต์  ๋กœ๊ทธ ์ถ”์ ์ด ์‰ฝ์ง€๋งŒ MSA ๊ตฌ์กฐ์—์„œ๋Š” ์„œ๋น„์Šค๋งˆ๋‹ค ์„œ๋ฒ„๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋กœ๊ทธ๊ฐ€ ๋ถ„์‚ฐ๋ผ์„œ ๋กœ๊ทธ ์ถ”์ ์ด ์–ด๋ ค์›Œ์ง„๋‹ค.

 

์ด๋•Œ, TraceID ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด

[์„œ๋ฒ„1] [trace-001,span-A] Gateway - ์š”์ฒญ ๋ฐ›์Œ (0ms)
[์„œ๋ฒ„2] [trace-001,span-X] Auth - ์ธ์ฆ ์‹œ์ž‘ (5ms)
[์„œ๋ฒ„3] [trace-001,span-B] User - ์‚ฌ์šฉ์ž ์กฐํšŒ (15ms) โš ๏ธ ๋А๋ฆผ!
[์„œ๋ฒ„4] [trace-001,span-Y] Notify - ์•Œ๋ฆผ ๋ฐœ์†ก (2ms)

์ด๋Ÿฐ์‹์œผ๋กœ trace-001 ๋กœ ๊ฒ€์ƒ‰ํ•˜๋ฉด 4๊ฐœ ์„œ๋ฒ„์—์„œ ๋ชจ๋‘ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , ์„œ๋ฒ„ 3์—์„œ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

โš™๏ธ Trace Context ์ „ํŒŒ (Propagation)

๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ๋Š” ์„œ๋น„์Šค๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋‚˜๋‰˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— (MSA) TraceID์™€ SpanID๊ฐ€ ๋‹ค์Œ ์„œ๋ฒ„๋กœ ์ „๋‹ฌ๋˜์–ด์•ผ ํ•œ๋‹ค. ์ „ํŒŒ ๋ฐฉ์‹์€ ์ฃผ๋กœ HTTP Header ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

traceparent ๊ตฌ์กฐ

00-{traceId}-{parentSpanId}-{flags}

 

์˜ˆ์‹œ

GET /api/users HTTP/1.1
Host: service-b.com
traceparent: 00-1234abcd5678ef90-7890abcd1234ef56-01
             โ”‚โ”‚  โ”‚                   โ”‚       โ””โ”€ flags (01=sampled)
             โ”‚โ”‚  โ”‚                   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ parent spanId
             โ”‚โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ traceId (128bit)
             โ”‚โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ version (00)
             
tracestate: vendor1=value1,vendor2=value2
  • 00 : Version
  • 1234abcd5678ef90: TraceID
  • 7890abcd1234ef56 : SpanID
  • 01 : Trace flags

์„œ๋น„์Šค A → ์„œ๋น„์Šค B๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, HTTP ํ—ค๋”์— Trace ์ •๋ณด๋ฅผ ์‹ค์–ด ๋ณด๋‚ด๋ฉด ์„œ๋น„์Šค B์—์„œ๋„ ๊ฐ™์€ TraceID ๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธธ ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋‹ค.

 

 

2๏ธโƒฃ ์˜ˆ์ œ ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค์–ด๋ณด๋ฉฐ TraceID, SpanID ์‹ค์Šตํ•ด๋ณด๊ธฐ

์„ค์ • (build.gradle, application.yml)

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.5.7'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
description = 'Demo project for Spring Boot'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-actuator'

    // Micrometer Tracing (OpenTelemetry bridge)
    implementation 'io.micrometer:micrometer-tracing-bridge-otel'
    implementation 'io.opentelemetry:opentelemetry-exporter-zipkin'

    // For @Observed (AOP-based observations around methods)
    implementation 'org.springframework.boot:spring-boot-starter-aop'

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

 

application.yml

spring:
  application:
    name: spring-tracing-quickstart

server:
  port: 8085

management:
  tracing:
    enabled: true
    sampling:
      probability: 1.0 # sample 100% for practice
  zipkin:
    tracing:
      endpoint: <http://localhost:9411/api/v2/spans>
  endpoints:
    web:
      exposure:
        include: health,info

logging:
  pattern:
    console: '[%d{yyyy-MM-dd HH:mm:ss}] [%X{traceId},%X{spanId}] [%thread] %highlight(%-5level) %logger %msg%n'
  file:
    path: /var/www/log/tracing-study
    name: console.log
  level:
    root: info

 

Tracing ๊ธฐ๋ณธ ์„ค์ • (Micrometer Tracing)

management:
  tracing:
    enabled: true
    sampling:
      probability: 1.0 # sample 100% for practice
  • managment.tracing.enabled : Micrometer Tracing ๊ธฐ๋Šฅ์„ ํ‚ค๋Š” ๊ฒƒ
    • Micrometer Tracing ์ด TraceID๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฑธ ์•ˆํ‚ค๋ฉด TraceID๊ฐ€ ์ƒ์„ฑ๋˜์ง€๋„, ์ „ํŒŒ๋˜์ง€๋„ ์•Š์Œ !
  • sampling.probability : 1.0 : ํŠธ๋ ˆ์ด์Šค๋ฅผ ๋ช‡ % ์ˆ˜์ง‘ํ• ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ์ƒ˜ํ”Œ๋ง ๋น„์œจ
    • 1.0 ์ด๋ฉด 100% ์ˆ˜์ง‘ํ•˜๊ฒ ๋‹ค๋Š” ๊ฒƒ์œผ๋กœ ํ•™์Šต, ๊ฐœ๋ฐœ์— ์œ ์šฉํ•จ
    • ์šด์˜์—์„  ๋ณดํ†ต 0.01 ~ 0.2 ์ •๋„๋กœ ๋‚ฎ์ถ˜๋‹ค๊ณ  ํ•จ. ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์„์ˆ˜๋ก ๋‚ฎ์ถฐ์•ผ ํ•จ

 

โœ๏ธ Micrometer Tracing ์ด๋ž€?

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์š”์ฒญ ํ๋ฆ„(Trace/Span) ์„ ์ถ”์ ํ•˜๋Š” ๊ด€์ธก์„ฑ ํ”„๋ ˆ์ž„์›Œํฌ

 

 

์„œ๋น„์Šค ์•ˆ์—์„œ “์ด ์š”์ฒญ์ด ์–ด๋””์„œ ์‹œ์ž‘๋˜์–ด, ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฑฐ์ณ, ์–ด๋””์„œ ์ง€์—ฐ์ด ๋ฐœ์ƒํ–ˆ๋Š”๊ฐ€”๋ฅผ ์ถ”์ ํ•˜๋Š” ์‹œ์Šคํ…œ → ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Zipkin, Jaeger, Tempo, Grafana ๋“ฑ์—์„œ ์‹œ๊ฐ์ ์œผ๋กœ ํŠธ๋ ˆ์ด์Šค๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Œ

 

๋™์ž‘ ๋ฐฉ์‹

Client → Controller → Service → DB

1๏ธโƒฃ Controller ์ง„์ž… ์‹œ traceId, spanId ์ƒ์„ฑ

2๏ธโƒฃ ๊ฐ ๋‹จ๊ณ„๋ณ„๋กœ child span ์ƒ์„ฑ

3๏ธโƒฃ MDC(Context)์— trace ์ •๋ณด ์‚ฝ์ž… → ๋กœ๊ทธ์— [traceId][spanId] ์ถœ๋ ฅ

4๏ธโƒฃ Exporter๊ฐ€ Zipkin/Tempo/Jaeger ๋“ฑ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก

 

 

โœ๏ธ ์šด์˜์—์„  ์ƒ˜ํ”Œ๋ง ๋น„์œจ์„ 0.01 ~ 0.2 ์ •๋„๋กœ ๋‚ฎ์ถ”๋Š” ์ด์œ  ?

TraceID, SpanID ๋ฅผ 100% ์ˆ˜์ง‘ํ•˜๋ฉด ๋ถ€ํ•˜๊ฐ€ ์ปค์ง€๊ธฐ ๋•Œ๋ฌธ

 

 

๋ถ„์‚ฐ ํŠธ๋ ˆ์ด์‹ฑ์€ ๋ชจ๋“  ์š”์ฒญ์˜ ์‹คํ–‰ ๊ฒฝ๋กœ๋ฅผ Span ๋‹จ์œ„๋กœ ๊ธฐ๋กํ•˜๊ณ  ์ „์†กํ•˜๊ณ , ์ด ๋ฐ์ดํ„ฐ๋Š” Zipkin, Tempo, Jaeger ๊ฐ™์€ ์ €์žฅ์†Œ์— ์Œ“์ž„. ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์œผ๋ฉด ๋ชจ๋“  Span์ด Zipkin ์„œ๋ฒ„๋กœ ์ „์†ก๋˜๊ณ  ๋””์Šคํฌ/๋„คํŠธ์›Œํฌ/CPU๊ฐ€ ๊ธ‰๊ฒฉํžˆ ์‚ฌ์šฉ๋Ÿ‰์ด ์˜ฌ๋ผ๊ฐ !

๋”ฐ๋ผ์„œ Trace ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋ถ€ ์ˆ˜์ง‘ํ•˜์ง€ ์•Š๊ณ  ์ผ๋ถ€ ์š”์ฒญ๋งŒ ์ถ”์ ํ•˜๋„๋ก ํ™•๋ฅ ์ ์œผ๋กœ ์ œํ•œํ•˜๋Š” ๊ฒƒ์ด ์ƒ˜ํ”Œ๋ง์œผ๋กœ ๋ชจ๋“  ์š”์ฒญ์„ ๊ธฐ๋กํ•  ํ•„์š”๋Š” ์—†๊ณ , ์ผ์ • ๋น„์œจ๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„ํžˆ ํŒจํ„ด๊ณผ ๋ณ‘๋ชฉ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Œ.

 

โžก๏ธ ํ•œ๋งˆ๋””๋กœ ์ƒ˜ํ”Œ๋ง ๋น„์œจ์„ ๋‚ฎ์ถ”๋Š” ์ด์œ ๋Š” ํŠธ๋ ˆ์ด์Šค ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐฉ๋Œ€ํ•˜๊ฒŒ ์Œ“์—ฌ ์‹œ์Šคํ…œ๊ณผ ์ €์žฅ์†Œ์— ๋ถ€๋‹ด์„ ์ฃผ๊ธฐ ๋•Œ๋ฌธ์ด๋ฉฐ, ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์ผ๋ถ€ ์š”์ฒญ๋งŒ ์ถ”์ ํ•ด๋„ ์ถฉ๋ถ„ํžˆ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์„ ์ง„๋‹จํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

 

Zipkin Exporter ์„ค์ •

management:
  zipkin:
    tracing:
      endpoint: <http://localhost:9411/api/v2/spans>
  • Micrometer Tracing์ด ์ˆ˜์ง‘ํ•œ Span ์„ Zipkin ์„œ๋ฒ„๋กœ ์ „์†กํ•  ์ฃผ์†Œ
  • Zipkin์˜ ๊ธฐ๋ณธ ํฌํŠธ๋Š” 9411

 

Actuator ๋…ธ์ถœ (์„ ํƒ)

management: 
  endpoints:
    web:
      exposure:
        include: health,info
  • Actuator์—์„œ ์›น์œผ๋กœ ๋…ธ์ถœํ•  ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ง€์ •ํ•จ
  • ์ตœ์†Œํ•œ health, info ์ •๋„๋งŒ ์—ด์–ด๋‘๋ฉด ๊ธฐ๋ณธ ์ƒํƒœ ์ ๊ฒ€์— ์ถฉ๋ถ„ํ•จ
  • ์šด์˜์—์„  ๋…ธ์ถœ ๋ฒ”์œ„๋ฅผ ์ขํžˆ๊ณ  ๋ณด์•ˆ ์„ค์ •์„ ํ•จ๊ป˜ ๊ณ ๋ คํ•ด์•ผ ํ•จ

 

logging ์„ค์ •

logging:
  pattern:
    console: '[%d{yyyy-MM-dd HH:mm:ss}] [%X{traceId},%X{spanId}] [%thread] %highlight(%-5level) %logger %msg%n'
    file:    '[%d{yyyy-MM-dd HH:mm:ss}] [%X{traceId},%X{spanId}] [%thread] %-5level %logger %msg%n'
  file:
    path: /var/www/log/tracing-study
  level:
    root: info

 

docker-compose.yml

services:
  zipkin:
    image: openzipkin/zipkin
    container_name: zipkin
    ports:
      - "9411:9411"

 

ObservationConfig

@Configuration
public class ObservationConfig {

    @Bean
    public ObservedAspect observedAspect(ObservationRegistry registry) {
        return new ObservedAspect(registry);
    }
}
  • Micrometer Tracing ์ด @Observed ์–ด๋…ธํ…Œ์ด์…˜ ์ธ์‹ํ•˜๊ณ  ๋™์ž‘ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ์„ค์ •
  • ObservationRegistry: Micrometer ์˜ Observation ์„ ๋“ฑ๋ก, ๊ด€๋ฆฌํ•˜๋Š” ์ค‘์•™ ํ—ˆ๋ธŒ ๊ฐ์ฒด
    • @Observed๊ฐ€ ๋‹ฌ๋ฆฐ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ, Micrometer๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ObservationRegistry๋ฅผ ์‚ฌ์šฉํ•ด ์ƒˆ Observation(Span) ์„ ์ƒ์„ฑํ•˜๊ณ  ์‹คํ–‰ ์‹œ๊ฐ„, ์˜ˆ์™ธ, ํƒœ๊ทธ, ์ƒํƒœ ๋“ฑ์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
  • ObservedAspect : Spring AOP ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘๋™ํ•˜๋Š” Aspect

๐Ÿงฉ ์‹คํ–‰ ํ๋ฆ„ ์˜ˆ์‹œ

1๏ธโƒฃ Controller → DemoService.work() ํ˜ธ์ถœ

2๏ธโƒฃ AOP๊ฐ€ @Observed ๊ฐ์ง€

3๏ธโƒฃ ObservedAspect๊ฐ€ ObservationRegistry์— ์ƒˆ Observation ๋“ฑ๋ก

4๏ธโƒฃ ์‹คํ–‰ ์ „ํ›„๋กœ ๋กœ๊ทธ + ํŠธ๋ ˆ์ด์Šค ์ˆ˜์ง‘

5๏ธโƒฃ Exporter(Zipkin ๋“ฑ)์— Span ์ „์†ก

 

 

์‹ค์Šต ์ฝ”๋“œ (Ctl, Service…)

TracingController

package com.example.spring_tracing_quickstart.ctl;

import com.example.spring_tracing_quickstart.service.DemoService;
import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@Slf4j
@RestController
public class TracingController {

    private final Tracer tracer;
    private final DemoService demoService;

    public TracingController(Tracer tracer, DemoService demoService) {
        this.tracer = tracer;
        this.demoService = demoService;
    }

    @GetMapping("/trace-test")
    public Map<String, String> test() {
        Span span = tracer.currentSpan();

        return Map.of(
                "traceId", span.context().traceId(),
                "spanId", span.context().spanId()
        );
    }

    @GetMapping("/trace")
    public Map<String, String> trace() throws InterruptedException {
        Span span = tracer.currentSpan();
        log.info("Controller - handling /trace");
        demoService.work();
        demoService.callDownstreamSimulation();

        return Map.of("Ok", span.context().traceId());
    }

}

 

 

DemoService

package com.example.spring_tracing_quickstart.service;

import io.micrometer.observation.annotation.Observed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.ThreadLocalRandom;

@Slf4j
@Service
public class DemoService {

    @Observed(name = "service.work", contextualName = "service#work")
    public void work() throws InterruptedException {
        log.info("Service - work()");
        Thread.sleep(50 + ThreadLocalRandom.current().nextInt(50));
        log.info("Service - work() done");
    }

    @Observed(name = "service.downstream", contextualName = "service#downstream")
    public void callDownstreamSimulation() throws InterruptedException {
        log.info("Service - callDownstreamSimulation() start");
        // Imagine an external API / DB call here
        Thread.sleep(100 + ThreadLocalRandom.current().nextInt(150));
        log.info("Service - callDownstreamSimulation() end");
    }
}

 

 

@Observed

๋ฉ”์„œ๋“œ ์‹คํ–‰์„ ์ž๋™์œผ๋กœ ํŠธ๋ ˆ์ด์Šค(Span) + ๋ฉ”ํŠธ๋ฆญ(Observation) ์œผ๋กœ ๊ธฐ๋กํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜

  • name : ๋ฉ”ํŠธ๋ฆญ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ๋จ
  • contextualName : ํŠธ๋ ˆ์ด์Šค์— ํ‘œ์‹œ๋  ์ด๋ฆ„

 

์‹คํ–‰

/trace ํ˜ธ์ถœ

  • http://localhost:8080/trace

 

์‹คํ–‰ ๋กœ๊ทธ

[2025-10-28 13:51:01] [749f01246a3dc717d386e80b295c4464,91313b104f8e24da] [http-nio-8085-exec-1] INFO  com.example.spring_tracing_quickstart.ctl.TracingController Controller - handling /trace
[2025-10-28 13:51:01] [749f01246a3dc717d386e80b295c4464,4df6a2ffd774bc93] [http-nio-8085-exec-1] INFO  com.example.spring_tracing_quickstart.service.DemoService Service - work()
[2025-10-28 13:51:01] [749f01246a3dc717d386e80b295c4464,4df6a2ffd774bc93] [http-nio-8085-exec-1] INFO  com.example.spring_tracing_quickstart.service.DemoService Service - work() done
[2025-10-28 13:51:01] [749f01246a3dc717d386e80b295c4464,af04e80269c1aea9] [http-nio-8085-exec-1] INFO  com.example.spring_tracing_quickstart.service.DemoService Service - callDownstreamSimulation() start
[2025-10-28 13:51:01] [749f01246a3dc717d386e80b295c4464,af04e80269c1aea9] [http-nio-8085-exec-1] INFO  com.example.spring_tracing_quickstart.service.DemoService Service - callDownstreamSimulation() end
Trace (ํ•˜๋‚˜์˜ HTTP ์š”์ฒญ)
 โ”œโ”€ [Span 1] Controller (/trace)
 โ”‚   โ”œโ”€ [Span 2] Service.work()
 โ”‚   โ””โ”€ [Span 3] Service.callDownstreamSimulation()
  • TraceID : ์š”์ฒญ ์ „์ฒด์˜ ์‹๋ณ„์ž
  • SpanID : ์ž‘์—… ๋‹จ์œ„๋ณ„ ์‹๋ณ„์ž
  • ParentID : ์–ด๋–ค Span ์•„๋ž˜์— ์†ํ•˜๋Š”์ง€ ๋‚˜ํƒ€๋ƒ„

 

Zipkin UI

 

 

 

Zipkin UI์—์„œ ๋ถ„์„ํ•˜๋Š” ๋ฐฉ๋ฒ•

(1) Duration (์†Œ์š” ์‹œ๊ฐ„) ๋น„๊ต

  • /trace : ์ „์ฒด 314ms
  • service#work : 73ms
  • service#downstream : 192ms

→ ์„œ๋น„์Šค ๋‚ด๋ถ€์˜ ์–ด๋–ค ๋กœ์ง์ด ์ƒ๋Œ€์ ์œผ๋กœ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š”์ง€ ํŒŒ์•… ๊ฐ€๋Šฅํ•จ (์„ฑ๋Šฅ ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ ๋ถ„์„์šฉ์œผ๋กœ ์ž์ฃผ ์‚ฌ์šฉํ•จ)

 

(2) Time Overlap (๊ฒน์นจ ์—ฌ๋ถ€)

  • ๋‘ Span ์ด ๊ฒน์ณ ์žˆ์œผ๋ฉด ๋น„๋™๊ธฐ ํ˜ธ์ถœ, ๋–จ์–ด์ ธ ์žˆ์œผ๋ฉด ๋™๊ธฐ ํ˜ธ์ถœ

(3) ์„œ๋น„์Šค ๊ฐ„ ํ˜ธ์ถœ ํŠธ๋ ˆ์ด์Šค

  • spring-tracing-quickstart ์™ธ์— ๋‹ค๋ฅธ ์„œ๋น„์Šค๋ช…์ด ๋ณด์ด๋ฉด HTTP ์š”์ฒญ ์‹œ traceparent ํ—ค๋”๊ฐ€ ์ž˜ ์ „ํŒŒ๋œ ๊ฒƒ

→ ๋ถ„์‚ฐ ํŠธ๋ ˆ์ด์‹ฑ ํ™•์ธ์šฉ

 

๋“ฑ.. ์‹ค๋ฌด์—์„œ ํ•˜๋Š” Trace ๋ถ„์„ ๋ฃจํ‹ด

๋ถ„์„ ๋ชฉํ‘œ ๋ณด๋Š” ์œ„์น˜ ํ•ด์„ ํฌ์ธํŠธ

๋А๋ฆฐ ์š”์ฒญ ์ฐพ๊ธฐ Trace ๋ฆฌ์ŠคํŠธ Duration ์ •๋ ฌ ์ƒ์œ„ 10๊ฐœ Trace ์—ด์–ด์„œ ๋ณ‘๋ชฉ Span ํ™•์ธ
์™ธ๋ถ€ API ๋А๋ฆผ ๋ถ„์„ ๊ฐ Span์˜ Duration ๋น„๊ต ํŠน์ • ์™ธ๋ถ€ ํ˜ธ์ถœ (ex. payment API)์ด ๋Œ€๋ถ€๋ถ„ ์ง€์—ฐ์ธ์ง€ ํ™•์ธ
๋‚ด๋ถ€ ํ˜ธ์ถœ ๋ณ‘๋ชฉ ํ™•์ธ Controller → Service → Repository ์ˆœ Service, Repository ์ค‘ ์–ด๋””์„œ ์‹œ๊ฐ„์ด ๋งŽ์€์ง€
์žฅ์•  ์‹œ TraceID ๊ธฐ๋ฐ˜ ์—ญ์ถ”์  ๋กœ๊ทธ traceId๋กœ Zipkin ๊ฒ€์ƒ‰ ์š”์ฒญ ํ๋ฆ„ ์ „์ฒด ์žฌํ˜„ ๊ฐ€๋Šฅ

 

 

โš ๏ธ Zipkin ์šด์˜ ํ™˜๊ฒฝ ์ฃผ์˜์‚ฌํ•ญ

1๏ธโƒฃ ์ƒ˜ํ”Œ๋ง ๋น„์œจ ์กฐ์ • (Sampling Rate)

  • ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์€ 1.0 (100%) ๋กœ ๋‘๊ณ , ์šด์˜ ํ™˜๊ฒฝ์€ 0.01 ~ 0.2 ์ •๋„๋กœ ์ค„์—ฌ์•ผ ํ•จ
  • ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์œผ๋ฉด Span ๋ฐ์ดํ„ฐํญ์ฆํ•ด์„œ Zipkin ์„œ๋ฒ„๊ฐ€ ๋ฒ„ํ‹ฐ์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€ํ‘œ ์ƒ˜ํ”Œ๋งŒ ์ˆ˜์ง‘ํ•ด์„œ ๊ฒฝํ–ฅ์„ฑ ํ™•์ธ ํ•„์š”

2๏ธโƒฃ Zipkin ์„œ๋ฒ„ ์ €์žฅ์†Œ(Storage) ๋ถ€ํ•˜ ๊ด€๋ฆฌ

  • ๊ธฐ๋ณธ Zipkin ์„œ๋ฒ„๋Š” in-memory storage ๋กœ ๋œจ๊ธฐ ๋•Œ๋ฌธ์— ์žฌ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ ์‚ฌ๋ผ์ง
  • ๋”ฐ๋ผ์„œ ์šด์˜์—์„œ๋Š” Elasticsearch, MySQL, Cassandra ์ค‘ ํ•˜๋‚˜๋กœ ๊ต์ฒดํ•œ๋‹ค๊ณ  ํ•จ..!

→ ์˜ค๋ž˜๋œ Trace ๋Š” ๋กœ๊ทธ ๋ถ„์„์ด๋‚˜ APM ์œผ๋กœ ๋„˜๊ธฐ๊ณ , Zipkin ์€ ๋‹จ๊ธฐ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์šฉ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์ผ๋ฐ˜์ ์ž„

3๏ธโƒฃ ๋ณด์•ˆ ๋ฐ ๊ฐœ์ธ์ •๋ณด ๋…ธ์ถœ ์ฃผ์˜

  • Trace/Span ๋ฐ์ดํ„ฐ ์•ˆ์—๋Š” URI, ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ, ํ—ค๋” ๊ฐ’์ด ๊ทธ๋Œ€๋กœ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋กœ๊ทธ ์ „์†ก ์ „ ๋งˆ์Šคํ‚น or ์ œ์™ธ ํ•„ํ„ฐ ์ ์šฉ ํ•ด์•ผ ํ•จ

4๏ธโƒฃ TraceID ์—ฐ๋™ ๋กœ๊น… ์ผ๊ด€์„ฑ ์œ ์ง€

  • Zipkin์€ Tracer → MDC(TraceId/ SpanID) → Logback → ๋กœ๊ทธ ์ˆ˜์ง‘๊ธฐ (ELK) ๋กœ ์ด์–ด์ง€๋ฏ€๋กœ ๋กœ๊ทธ ํฌ๋งท์ด ์ผ์ •ํ•˜์ง€ ์•Š์œผ๋ฉด TraceID๋กœ ๊ฒ€์ƒ‰์ด ๋ถˆ๊ฐ€๋Šฅํ•จ !
  • ์‹ค๋ฌด์—์„œ๋Š” Zipkin ๋ณด๋‹ค ELK ์—์„œ traceID๋กœ ๋กœ๊ทธ ํ•„ํ„ฐ๋ง์„ ๋” ์ž์ฃผ ํ•จ.

 

(์ถ”๊ฐ€) ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์—์„œ์˜ ์ „ํŒŒ

ClonedTaskDecorator

์ƒˆ ์Šค๋ ˆ๋“œ์—์„œ๋„ traceId๊ฐ€ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€๋˜๊ฒŒ ํ•˜๋Š” ๋ž˜ํผ

public class ClonedTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        ContextSnapshot snapshot = ContextSnapshot.captureAll();
        return () -> snapshot.wrap(runnable).run();
    }
}
  • Spring ์˜ @Async ๋Š” ์ƒˆ ์Šค๋ ˆ๋“œ์—์„œ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š”๋ฐ traceID, SpanID, MDC ๊ฐ™์€ ์ •๋ณด๋Š” ThreadLocal ์— ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒˆ ์Šค๋ ˆ๋“œ๋กœ ๋„˜์–ด๊ฐ€๋ฉด ์ž๋™์œผ๋กœ ์‚ฌ๋ผ์ง„๋‹ค.
  • ๋”ฐ๋ผ์„œ ํ˜„์ œ ์Šค๋ ˆ๋“œ(Controller) ์—์„œ ๊ฐ€์ง€๊ณ  ์žˆ๋˜ ๊ด€์ธก ์ปจํ…์ŠคํŠธ(Trace + MDC) ๋ฅผ ๋น„๋™๊ธฐ ์Šค๋ ˆ๋“œ(Service) ๋กœ ๋ณต์ œํ•ด์ฃผ๋Š” ์—ญํ• ์ด ํ•„์š”ํ•จ
  • ContextSnapshot.captureAll(); : Micrometer์—์„œ ์ œ๊ณตํ•˜๋Š” API๋กœ ํ˜„์žฌ ์Šค๋ ˆ๋“œ์˜ ThreadLocal ๊ธฐ๋ฐ˜ ์ปจํ…์ŠคํŠธ(MDC, Trace, Observation) ๋ฅผ ํ•œ ๋ฒˆ์— ์‚ฌ์ง„ ์ฐ๋“ฏ ๋ณต์‚ฌํ•จ → snapshot ์€ ๊ทธ ์‹œ์ ์— traceId, spanId๋ฅผ ๋ชจ๋‘ ๋‹ด๊ณ  ์žˆ์Œ
  • snapshot.wrap(runnable) : Mircometer ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ž˜ํ•‘ ํ•จ์ˆ˜๋กœ ์›๋ž˜ ์‹คํ–‰ํ•  Runnable ๋ฅผ ๊ฐ์‹ธ์„œ ์ƒˆ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋  ๋•Œ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ฐ์–ด๋‘” ์ปจํ…์ŠคํŠธ๋ฅผ ๋ณต์›ํ•ด์คŒ
  • ๋ณต์›๋œ ์ƒํƒœ์—์„œ runnable.run()์ด ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋กœ๊ทธ์™€ ํŠธ๋ ˆ์ด์Šค๊ฐ€ ๋ชจ๋‘ ๋ถ€๋ชจ TraceID๋กœ ์—ฐ๊ฒฐ๋จ

 

AsyncConfig ์„ค์ •

@Aysnc๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋น„๋™๊ธฐ ์Šค๋ ˆ๋“œ ํ’€์„ ์ง์ ‘ ๊ตฌ์„ฑํ•˜๋ฉด์„œ TaskDecorator ๋กœ Trace, MDC ๋“ฑ ThreadLocal ์ปจํ…์ŠคํŠธ๋ฅผ ์ž๋™ ๋ณต์ œํ•ด์ฃผ๋Š” ์„ค์ •

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(4);
        taskExecutor.setMaxPoolSize(40);
        taskExecutor.setQueueCapacity(160);
        taskExecutor.setThreadNamePrefix("springTask-");

        taskExecutor.setTaskDecorator(taskDecorator());

        taskExecutor.initialize();
        return taskExecutor;
    }

    @Bean
    public TaskDecorator taskDecorator() {
        return new ClonedTaskDecorator();
    }
}
  • ์Šค๋ ˆ๋“œ ํ’€ ์„ธ๋ถ€ ์„ค์ •์„ค์ • ์˜๋ฏธ ์„ค๋ช…
    corePoolSize ์ตœ์†Œ ์Šค๋ ˆ๋“œ ๊ฐœ์ˆ˜ ๊ธฐ๋ณธ์œผ๋กœ ํ•ญ์ƒ ์œ ์ง€๋˜๋Š” ์ž‘์—… ์Šค๋ ˆ๋“œ ์ˆ˜
    maxPoolSize ์ตœ๋Œ€ ์Šค๋ ˆ๋“œ ๊ฐœ์ˆ˜ ์š”์ฒญ์ด ๋ชฐ๋ฆด ๋•Œ ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ์Šค๋ ˆ๋“œ ์ˆ˜
    queueCapacity ์ž‘์—… ๋Œ€๊ธฐ ํ ํฌ๊ธฐ ์‹คํ–‰ ์ค‘์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฝ‰ ์ฐผ์„ ๋•Œ ๋Œ€๊ธฐํ•  ์ž‘์—… ์ˆ˜
    threadNamePrefix ์Šค๋ ˆ๋“œ ์ด๋ฆ„ ์ ‘๋‘์–ด ๋””๋ฒ„๊น… ์‹œ ๋กœ๊ทธ์— [springTask-1] ์ด๋Ÿฐ ์‹์œผ๋กœ ํ‘œ์‹œ๋จ
    → ๋™์‹œ์— 4๊ฐœ์˜ @Async ์ž‘์—…์ด ๋Œ๊ณ , ํ•„์š”ํ•˜๋ฉด ์ตœ๋Œ€ 40๊ฐœ๊นŒ์ง€ ํ™•์žฅ๋˜๋ฉฐ ๊ทธ ์ด์ƒ์€ 160๊ฐœ๊นŒ์ง€๋งŒ ํ์— ๋Œ€๊ธฐ
  • taskExecutor.setTaskDecorator(taskDecorator()); : TaskDecorator๋Š” ๊ฐ Runnable ์‹คํ–‰ ์ „์— ํŠน์ • ๋กœ์ง์„ ๊ฐ์‹ธ์ฃผ๋Š” ํ›…(Hook)
  • taskExecutor.initialize(); : ์Šค๋ ˆ๋“œ ํ’€ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  Spring Bean์œผ๋กœ ๋“ฑ๋ก, ์ดํ›„ @Async ๋ฉ”์„œ๋“œ๋“ค์€ ์ด Executor๋ฅผ ํ†ตํ•ด ์‹คํ–‰๋จ

 

DemoService

    @Async
    @Observed(name = "service.async", contextualName = "service#async")
    public void workAsync() throws InterruptedException {
        Thread.sleep(100 + ThreadLocalRandom.current().nextInt(5000));
        log.info("Service - workAsync() start");
        Thread.sleep(100 + ThreadLocalRandom.current().nextInt(100));
        log.info("Service - workAsync() end");
    }

 

TracingController

    @GetMapping("/trace/async")
    public Map<String, String> async() throws InterruptedException {
        log.info("Controller - handling /trace/async");
        demoService.work();
        demoService.workAsync();

        return Map.of("Ok", tracer.currentSpan().context().traceId());
    }

 

์‹คํ–‰ ๋กœ๊ทธ

[2025-10-28 15:20:45] [c1ac96986375abf055733ffde0f927dd,33e39c74ab39a193] [http-nio-8085-exec-1] INFO  com.example.spring_tracing_quickstart.ctl.TracingController Controller - handling /trace/async
[2025-10-28 15:20:45] [c1ac96986375abf055733ffde0f927dd,5c6a1a727850edf8] [http-nio-8085-exec-1] INFO  com.example.spring_tracing_quickstart.service.DemoService Service - work()
[2025-10-28 15:20:45] [c1ac96986375abf055733ffde0f927dd,5c6a1a727850edf8] [http-nio-8085-exec-1] INFO  com.example.spring_tracing_quickstart.service.DemoService Service - work() done
[2025-10-28 15:20:49] [c1ac96986375abf055733ffde0f927dd,e8d9e126ad2339a2] [springTask-1] INFO  com.example.spring_tracing_quickstart.service.DemoService Service - workAsync() start
[2025-10-28 15:20:49] [c1ac96986375abf055733ffde0f927dd,e8d9e126ad2339a2] [springTask-1] INFO  com.example.spring_tracing_quickstart.service.DemoService Service - workAsync() end

 

 

๐Ÿ” ์ „์ฒด ์‹คํ–‰ ํ๋ฆ„ ์ •๋ฆฌ

1. Controller Thread
   ↓
   Micrometer Tracing์ด TraceID, SpanID๋ฅผ MDC/ThreadLocal์— ์ €์žฅ
   ↓
2. @Async ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
   ↓
   ThreadPoolTaskExecutor๊ฐ€ ์ƒˆ๋กœ์šด Worker Thread๋ฅผ ๊บผ๋ƒ„
   ↓
   TaskDecorator (ClonedTaskDecorator)๊ฐ€ ์‹คํ–‰๋จ
      - ContextSnapshot.captureAll() → ํ˜„์žฌ ThreadLocal ๋ณต์‚ฌ
      - snapshot.wrap(runnable).run() → ์ƒˆ ์Šค๋ ˆ๋“œ์—์„œ ๋ณต์›
   ↓
3. Service Thread
   ↓
   traceId, spanId๊ฐ€ ๋ณต์›๋œ ์ƒํƒœ๋กœ ์‹คํ–‰
   ↓
   Zipkin / ๋กœ๊ทธ ๋ชจ๋‘ ๋™์ผ traceId๋กœ ์—ฐ๊ฒฐ

 

 

3๏ธโƒฃ์š”์•ฝ

Trace vs Span

๋‹จ์œ„ ์ „์ฒด ์š”์ฒญ ์„ธ๋ถ€ ์ž‘์—…
์‹๋ณ„์ž TraceID SpanID
๊ด€๊ณ„ Span๋“ค์˜ ์ƒ์œ„ ์ปจํ…Œ์ด๋„ˆ Trace ๋‚ด์˜ ๊ฐœ๋ณ„ ๋‹จ์œ„
์ „ํŒŒ ๋ฐฉ์‹ HTTP ํ—ค๋”, Context Parent-Child ๊ตฌ์กฐ
๋ชฉ์  ์š”์ฒญ ์ „์ฒด ์ถ”์  ๊ฐ ๊ตฌ๊ฐ„ ์„ฑ๋Šฅ ์ธก์ •

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90