📡 Obs · Zero→Hero
หน้าแรก/🔬 4 · Traces/แนวคิด Distributed Tracing
📖 บทเรียน⏱ ~13 นาที

แนวคิด Distributed Tracing

เสาที่สามและซับซ้อนที่สุด แต่ทรงพลังมากในโลก microservices ลองนึกภาพปัญหานี้:

ลูกค้าบ่นว่า "กดสั่งซื้อแล้วช้า 3 วินาที" — request วิ่งผ่าน 8 service คุณจะรู้ได้ยังไงว่า service ตัวไหน ที่ทำให้ช้า? metric บอกแค่ว่า "ระบบช้า", log แต่ละ service ก็กระจัดกระจาย...

นี่คือปัญหาที่ distributed tracing เกิดมาแก้

ทำไม metrics + logs ไม่พอ

  • Metrics บอกว่า ระบบโดยรวม ช้า แต่ไม่บอกว่า request ไหน หรือ service ไหน (มันเป็นค่า aggregate)
  • Logs มีรายละเอียดแต่กระจัดกระจายไปตาม service ต้องมานั่งต่อจิ๊กซอว์เองว่า log ไหนของ request เดียวกัน

Tracing เติมช่องว่างนี้: ตามรอย 1 request ตลอดเส้นทาง ข้ามทุก service พร้อมเวลาที่ใช้ในแต่ละจุด

2 คำที่ต้องเข้าใจ: Trace และ Span

Span = หน่วยงานหนึ่งชิ้น (operation เดียว) มี:

  • ชื่อ (เช่น GET /order, SELECT users)
  • เวลาเริ่ม + ระยะเวลา (duration)
  • attributes (metadata: http.method, db.statement, ...)
  • สถานะ (ok / error)

Trace = การเดินทางทั้งหมดของ 1 request = ชุดของ span ที่เชื่อมกันเป็น tree ด้วยความสัมพันธ์ parent-child

Trace: "สั่งซื้อสินค้า"  (trace_id: a1b2c3, รวม 820ms)
│
├─ span: POST /checkout          [gateway]         ├──────────────────────┤  820ms
│  ├─ span: verify auth          [auth-service]    ├─┤                        12ms
│  ├─ span: create order         [order-service]   │ ├──┤                     40ms
│  │  └─ span: INSERT orders     [postgres]        │  ├─┤                     30ms
│  └─ span: charge payment       [payment-service] │      ├──────────────┤  760ms ⚠️
│     └─ span: call bank API     [external]        │       ├─────────────┤  740ms ⚠️

พอเห็น waterfall แบบนี้ ตอบได้ทันทีว่าช้าที่ call bank API (740ms) ไม่ใช่โค้ดเรา — นี่คือพลังของ tracing

แต่ละ span มี trace_id เดียวกัน (ผูกทั้ง trace) + span_id ของตัวเอง + parent_span_id ชี้ไป span แม่ โครงสร้าง 3 id นี้แหละที่ประกอบขึ้นเป็น tree

Context Propagation — กุญแจของทั้งระบบ

คำถามสำคัญ: service B จะรู้ได้ยังไงว่า request ที่มันได้รับ เป็นส่วนหนึ่งของ trace เดียวกับที่ service A เริ่มไว้?

คำตอบคือ context propagation — ส่ง trace context (trace_id, span_id ปัจจุบัน) ต่อไปกับทุก request ข้าม service ผ่าน HTTP header:

service A เรียก service B  →  แนบ header:
  traceparent: 00-a1b2c3d4...-e5f6a7b8...-01
               │  └─trace_id┘  └span_id┘  └flags (เช่น sampled)
               └─version

service B อ่าน header นี้ → รู้ว่าตัวเองอยู่ใน trace a1b2c3 และ span ที่มันสร้างมี parent เป็น span จาก A

traceparent เป็นมาตรฐาน W3C Trace Context — ทำให้ tool ต่างเจ้าคุยกันได้ นี่คือสิ่งที่ทำให้ tracing ข้ามภาษา/ข้ามระบบได้ และเป็นสิ่งที่ OpenTelemetry (บทหน้า) จัดการให้อัตโนมัติ

ถ้า context propagation ขาดตอนแม้แต่ service เดียว (เช่น service เก่าที่ไม่ได้ instrument, หรือผ่าน message queue โดยไม่ส่ง context ต่อ) — trace จะ "ขาด" เป็นสองท่อน หา root cause ได้ยากขึ้น การ propagate ให้ครบทุก hop จึงสำคัญมาก

Sampling — เก็บทุก trace ไม่ไหว

จำจากบทที่ 01: traces แพงที่สุดในสามเสา ถ้าเก็บทุก request ในระบบ traffic สูง = ข้อมูลมหาศาล จึงต้อง sampling (เก็บแค่บางส่วน):

กลยุทธ์ ทำงานยังไง ข้อดี/ข้อเสีย
Head-based ตัดสินใจตั้งแต่ต้น trace (เช่น สุ่มเก็บ 10%) ง่าย, เบา / อาจพลาด trace ที่ error
Tail-based รอ trace จบแล้วค่อยตัดสิน (เก็บถ้า error/ช้า) เก็บของสำคัญครบ / ซับซ้อน, ต้อง buffer

กลยุทธ์ยอดนิยม: sample แบบ head 1-10% สำหรับ trace ปกติ แต่เก็บ 100% ของ trace ที่ error หรือช้าผิดปกติ (tail-based) — เพราะ trace ที่มีปัญหาคือของที่เราอยากดูที่สุด

Tracing เชื่อมกับ pillar อื่น

  • Trace ↔ Log: ใส่ trace_id ลงใน log (บทที่ 30) → จาก span กระโดดไปอ่าน log ของ span นั้นได้
  • Trace ↔ Metric: metric อย่าง RED duration บอกว่า "ช้า" → เปิด trace ดูว่าช้าตรงไหน (flow บทที่ 01)
  • Exemplars: จุดบนกราฟ metric ที่ลิงก์ตรงไปยัง trace ตัวอย่าง (บทที่ 50)

สรุป

  • Tracing ตามรอย 1 request ตลอดเส้นทางข้าม service — เติมช่องว่างที่ metric/log ทำไม่ได้
  • Span = 1 operation, Trace = ชุด span เชื่อมเป็น tree (ผ่าน trace_id/span_id/parent_span_id)
  • Waterfall view ชี้ได้ทันทีว่าช้าที่ service ไหน
  • Context propagation (W3C traceparent header) ส่ง trace context ข้าม service — ขาดไม่ได้แม้ hop เดียว
  • Sampling จำเป็นเพราะ trace แพง — เก็บ error/slow 100%, ปกติ sample
  • ผูกกับ log ผ่าน trace_id

บทหน้า: OpenTelemetry — มาตรฐานกลางที่ทำเรื่องพวกนี้ให้อัตโนมัติ และรวม 3 เสาเป็นหนึ่งเดียว