การทำความเข้าใจ Data Model ของระบบเก็บ metric เป็นพื้นฐานสำคัญที่วิศวกรระบบและนักพัฒนาต้องรู้ก่อนเริ่มใช้งาน Prometheus ให้เกิดประโยชน์สูงสุด เพราะโครงสร้างข้อมูลแบบ time series ที่ใช้นั้นแตกต่างจากฐานข้อมูลทั่วไปโดยสิ้นเชิง การออกแบบ metric, การตั้งชื่อ, และการใช้ label อย่างเหมาะสมมีผลโดยตรงต่อประสิทธิภาพของระบบ การ query ข้อมูล และการสร้าง dashboard ที่เข้าใจง่าย
บทความนี้จะพาไปทำความรู้จักกับ Data Model ของระบบ metric แบบละเอียด ตั้งแต่โครงสร้างพื้นฐานของ time series, ประเภทของ metric ทั้ง 4 แบบ (Counter, Gauge, Histogram, Summary), การใช้งาน label ให้มีประสิทธิภาพ ไปจนถึง best practices ในการออกแบบ metric ให้ scale ได้ดีในระบบ production
Time Series คืออะไร และทำงานอย่างไร
Time series คือชุดข้อมูลที่ประกอบด้วยค่าตัวเลขเรียงตามลำดับเวลา โดยแต่ละจุดข้อมูล (sample) ประกอบด้วย timestamp และค่า value ระบบเก็บข้อมูลจะบันทึกตัวอย่างทุก ๆ ช่วงเวลาที่กำหนด (scrape interval) ซึ่งปกติอยู่ที่ 15-60 วินาที ทำให้ได้ข้อมูลย้อนหลังที่สามารถนำมา query, วิเคราะห์แนวโน้ม และสร้าง alert ได้
ในระบบนี้ time series แต่ละเส้นจะถูกระบุเฉพาะด้วย “ชื่อ metric + ชุดของ label” ที่ไม่ซ้ำกัน เช่น metric http_requests_total ที่มี label method="GET" และ status="200" จะเป็นคนละเส้น time series กับ metric ชื่อเดียวกันที่มี method="POST" แม้จะมีชื่อ metric เดียวกันก็ตาม การรวมกันของชื่อและ label แต่ละชุดที่ไม่ซ้ำจึงเรียกว่า “series identity”
# ตัวอย่าง time series ที่มีชื่อ metric เดียวกันแต่ label ต่างกัน
http_requests_total{method="GET", status="200", endpoint="/api/users"} → series #1
http_requests_total{method="GET", status="404", endpoint="/api/users"} → series #2
http_requests_total{method="POST", status="201", endpoint="/api/users"} → series #3
http_requests_total{method="GET", status="200", endpoint="/api/orders"} → series #4
# แต่ละเส้นเก็บ sample แยกกัน:
# series #1: (1713456789, 1523), (1713456804, 1534), (1713456819, 1547), ...
# series #2: (1713456789, 42), (1713456804, 43), (1713456819, 45), ...
โครงสร้างของ Metric
Metric ในระบบ monitoring นี้ประกอบด้วย 3 ส่วนหลัก ได้แก่ ชื่อ metric (metric name), ชุด label (key-value pairs), และค่าตัวเลข (sample value) พร้อม timestamp ที่ระบบบันทึกให้อัตโนมัติ รูปแบบ text ที่ exporter ส่งออกมาจะมีลักษณะดังนี้
# HELP http_requests_total จำนวน HTTP requests ทั้งหมดที่ได้รับ
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200",endpoint="/api/users"} 1523
http_requests_total{method="POST",status="201",endpoint="/api/users"} 87
http_requests_total{method="GET",status="404",endpoint="/api/users"} 12
# HELP node_memory_MemAvailable_bytes หน่วยความจำที่ยังใช้ได้ (bytes)
# TYPE node_memory_MemAvailable_bytes gauge
node_memory_MemAvailable_bytes 2147483648
การตั้งชื่อ Metric (Naming Convention)
การตั้งชื่อ metric ที่ดีจะช่วยให้ทีมอื่น ๆ เข้าใจความหมายได้ง่าย และทำให้ dashboard อ่านง่ายขึ้น ควรทำตามหลักดังนี้
- ใช้ snake_case — ใช้ตัวพิมพ์เล็กและขีดล่าง เช่น
http_requests_totalไม่ใช่httpRequestsTotal - ใส่ prefix ของ subsystem — เช่น
node_cpu_seconds_total,http_request_duration_secondsเพื่อให้รู้ว่า metric มาจากที่ไหน - ใส่หน่วยใน suffix — เช่น
_bytes,_seconds,_totalเพื่อให้รู้หน่วยทันทีโดยไม่ต้องเปิด documentation - Counter ลงท้ายด้วย _total — เป็น convention ที่ทำให้เห็นชัดว่าเป็น metric ประเภท counter
- ใช้หน่วย base — ใช้วินาทีไม่ใช่ millisecond, ใช้ bytes ไม่ใช่ KB/MB เพื่อให้การคำนวณใน query ง่ายและไม่ต้องแปลงหน่วย
Metric Types: 4 ประเภทที่ต้องรู้
ระบบ metric แบ่งประเภท metric ออกเป็น 4 แบบตามลักษณะของข้อมูลที่เก็บและวิธีการใช้งาน การเลือกใช้ประเภทที่เหมาะสมเป็นสิ่งสำคัญมาก เพราะแต่ละประเภทมีพฤติกรรมและ query function ที่ต่างกัน
1. Counter — ค่าที่เพิ่มขึ้นอย่างเดียว
Counter คือ metric ที่ค่าจะเพิ่มขึ้นเท่านั้น (monotonically increasing) และจะ reset กลับเป็น 0 เมื่อ process restart เท่านั้น ใช้สำหรับนับเหตุการณ์สะสม เช่น จำนวน request ทั้งหมด, จำนวน error, จำนวน job ที่ทำเสร็จ การ query counter มักจะใช้กับฟังก์ชัน rate() หรือ increase() เพื่อดูอัตราการเพิ่มขึ้นต่อวินาที
# ตัวอย่าง counter metric
http_requests_total{method="GET",status="200"} 15234
api_errors_total{service="payment"} 47
tasks_completed_total{worker="worker-1"} 8921
# Query ที่นิยมใช้กับ counter:
rate(http_requests_total[5m]) # requests ต่อวินาที ใน 5 นาทีล่าสุด
increase(api_errors_total[1h]) # จำนวน error เพิ่มขึ้นใน 1 ชั่วโมง
sum by (status) (rate(http_requests_total[5m])) # rate แยกตาม status
2. Gauge — ค่าที่เพิ่มหรือลดได้
Gauge คือ metric ที่ค่าสามารถเพิ่มหรือลดได้ตามเวลา ใช้สำหรับข้อมูลที่เป็นค่า snapshot ณ ช่วงเวลาหนึ่ง เช่น อุณหภูมิ, การใช้ memory, จำนวน connection ที่ active, ขนาดคิวงาน การใช้งาน gauge จะตรงไปตรงมากว่า counter เพราะค่าที่ query ออกมาคือค่าปัจจุบัน ไม่ต้องคำนวณ rate
# ตัวอย่าง gauge metric
memory_usage_bytes{instance="web-01"} 3221225472
active_connections{service="postgres"} 47
queue_size{queue="email"} 128
cpu_temperature_celsius{instance="server-01"} 65.5
# Query ที่นิยมใช้กับ gauge:
memory_usage_bytes # ค่าปัจจุบัน
avg_over_time(cpu_temperature_celsius[1h]) # เฉลี่ย 1 ชั่วโมง
max_over_time(queue_size[24h]) # สูงสุดใน 24 ชั่วโมง
deriv(queue_size[10m]) # อัตราการเปลี่ยนแปลง
3. Histogram — การกระจายของค่า
Histogram ใช้เก็บการกระจายตัวของค่า เช่น latency ของ request, ขนาดของ response body โดยจะแบ่งค่าออกเป็น bucket ตามช่วง (bucket boundaries) และนับจำนวน sample ที่ตกในแต่ละ bucket Histogram 1 metric จริง ๆ แล้วจะสร้าง time series หลายเส้น คือ _bucket (นับต่อ bucket), _sum (ผลรวม), และ _count (จำนวน sample ทั้งหมด)
# ตัวอย่าง histogram metric ที่ expose ออกมา
http_request_duration_seconds_bucket{le="0.005"} 234
http_request_duration_seconds_bucket{le="0.01"} 489
http_request_duration_seconds_bucket{le="0.025"} 789
http_request_duration_seconds_bucket{le="0.05"} 1234
http_request_duration_seconds_bucket{le="0.1"} 1456
http_request_duration_seconds_bucket{le="0.5"} 1523
http_request_duration_seconds_bucket{le="1"} 1540
http_request_duration_seconds_bucket{le="+Inf"} 1543
http_request_duration_seconds_sum 87.3
http_request_duration_seconds_count 1543
# คำนวณ percentile (เช่น p95 ของ latency ใน 5 นาทีล่าสุด)
histogram_quantile(0.95,
rate(http_request_duration_seconds_bucket[5m])
)
4. Summary — คำนวณ Quantile ที่ Client
Summary คล้ายกับ histogram แต่คำนวณ quantile (เช่น p50, p90, p99) ที่ฝั่ง client แล้ว expose ออกมาเป็น metric โดยตรง ข้อดีคือได้ค่า quantile ที่แม่นยำโดยไม่ต้องคิด bucket boundary ข้อเสียคือไม่สามารถรวม quantile ข้าม instance ได้ เพราะ math ของ quantile ไม่สามารถ aggregate ข้ามเครื่องได้ ทำให้ histogram มักเป็นที่นิยมมากกว่าใน production
# ตัวอย่าง summary metric
http_request_duration_seconds{quantile="0.5"} 0.023
http_request_duration_seconds{quantile="0.9"} 0.067
http_request_duration_seconds{quantile="0.99"} 0.145
http_request_duration_seconds_sum 87.3
http_request_duration_seconds_count 1543
# Query — เข้าถึงตรง ๆ ได้เลย
http_request_duration_seconds{quantile="0.99"}
Labels: เครื่องมือมิติหลายมุมของ Metric
Label คือ key-value pair ที่ผูกติดกับ metric เพื่อระบุมิติของข้อมูล (dimension) ที่เก็บ เช่น method, status, endpoint, instance, job การใช้ label ทำให้สามารถ filter, group, และ aggregate ข้อมูลใน query ได้อย่างยืดหยุ่น โดยไม่ต้องสร้าง metric แยกหลาย ๆ ตัว
Labels ที่ระบบเพิ่มอัตโนมัติ
เมื่อระบบ scrape metric จาก target จะมีการเพิ่ม label อัตโนมัติเพื่อระบุแหล่งที่มา ได้แก่ instance (IP:port ของ target), job (ชื่อ job ใน config) และ label อื่น ๆ ตาม relabeling rules ที่ตั้งไว้ ทำให้สามารถ group ข้อมูลตาม service หรือ instance ได้ง่าย
# เมื่อ scrape จาก 2 instances
# Target 1: 10.0.0.10:9100 (job=node)
# Target 2: 10.0.0.11:9100 (job=node)
node_cpu_seconds_total{instance="10.0.0.10:9100",job="node",cpu="0",mode="idle"} 12345.67
node_cpu_seconds_total{instance="10.0.0.10:9100",job="node",cpu="0",mode="user"} 234.56
node_cpu_seconds_total{instance="10.0.0.11:9100",job="node",cpu="0",mode="idle"} 11234.56
node_cpu_seconds_total{instance="10.0.0.11:9100",job="node",cpu="0",mode="user"} 345.67
Cardinality: กับดักที่ต้องระวัง
Cardinality คือจำนวน time series ที่ไม่ซ้ำกัน ซึ่งเกิดจากการรวมของชื่อ metric และ label values ทั้งหมด การมี cardinality สูงเกินไปเป็นปัญหาใหญ่ที่สุดในระบบ metric เพราะจะทำให้หน่วยความจำและ disk ระเบิด และ query ช้าลงอย่างมาก
ตัวอย่างเช่น ถ้าใส่ user_id เป็น label ในระบบที่มีผู้ใช้ 1 ล้านคน จะสร้าง time series 1 ล้านเส้นต่อ metric ซึ่งจะทำให้ระบบล่มได้ทันที ดังนั้นจึงควรเลือก label ที่มีค่าไม่กี่แบบเท่านั้น (low cardinality) เช่น HTTP method (GET, POST, PUT, DELETE), HTTP status class (2xx, 4xx, 5xx)
# ❌ BAD: cardinality สูงมาก
http_requests_total{user_id="12345"} 1 # user_id มีล้านค่า
http_requests_total{user_id="12346"} 1
http_requests_total{request_id="abc-123-def-456"} 1 # unique ทุก request
# ✅ GOOD: cardinality ต่ำ
http_requests_total{method="GET",status_class="2xx",endpoint="/api/users"} 15234
http_requests_total{method="POST",status_class="4xx",endpoint="/api/users"} 42
หลักการใช้ Label ให้ดี
- เลือก label ที่มีค่าจำกัด — ควรมี unique values ต่อ label ไม่เกินหลักร้อย ไม่ควรมีหลักพันขึ้นไป
- ไม่ใช้ค่าที่ไม่จำกัด — ห้ามใช้ user_id, request_id, email, URL query string, IP address เป็น label
- ใช้ชื่อ label สั้นและสื่อความหมาย — เช่น
method,status,regionไม่ใช่http_method_name - รวมค่าที่คล้ายกัน — แทนที่จะเก็บทุก HTTP status code ให้เก็บ
status_class(2xx, 4xx, 5xx) แทน - แยกข้อมูล high cardinality ไปที่ระบบ log — ข้อมูลแบบ trace ID, user ID ควรเก็บใน logging หรือ tracing system แทน metric
Exposition Format: รูปแบบข้อมูลที่ Exporter ส่งออก
Exporter จะ expose metric ผ่าน HTTP endpoint (ปกติคือ /metrics) ในรูปแบบ text ที่มนุษย์อ่านได้ โดยมีโครงสร้างเป็น 3 บรรทัดต่อ metric ได้แก่ # HELP (คำอธิบาย), # TYPE (ประเภท metric), และ sample lines (ชื่อ metric + labels + value)
# HELP node_filesystem_avail_bytes Filesystem space available to non-root users in bytes.
# TYPE node_filesystem_avail_bytes gauge
node_filesystem_avail_bytes{device="/dev/sda1",fstype="ext4",mountpoint="/"} 5.3687091e+10
node_filesystem_avail_bytes{device="/dev/sda2",fstype="ext4",mountpoint="/home"} 1.0737418e+11
# HELP node_load1 1m load average.
# TYPE node_load1 gauge
node_load1 0.42
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 1234.56
Best Practices ในการออกแบบ Metric
การออกแบบ metric ที่ดีจะช่วยให้ระบบ scale ได้ในระยะยาวและทีมใช้งานได้อย่างมีประสิทธิภาพ หลักการที่ควรทำตามมีดังนี้
- เริ่มจาก RED หรือ USE method — RED (Rate, Errors, Duration) เหมาะกับ service ส่วน USE (Utilization, Saturation, Errors) เหมาะกับ resource
- ใช้ histogram แทน summary — histogram aggregate ข้าม instance ได้ ส่วน summary ทำไม่ได้ ทำให้ flexibility ต่ำกว่าในระบบ distributed
- อย่าเก็บ metric ที่ไม่ใช้ — เก็บเฉพาะที่ช่วยตอบคำถามการ operate จริง ๆ เพื่อประหยัด storage และลด cardinality
- ใช้ recording rules สำหรับ query ที่ซับซ้อน — ถ้า query ใช้บ่อยและคำนวณนาน ให้สร้างเป็น recording rule เพื่อ pre-compute
- ตั้ง scrape interval ให้เหมาะสม — 15 วินาทีเป็นค่าเริ่มต้นที่ดี metric บางตัวที่เปลี่ยนช้าอาจใช้ 60 วินาทีก็ได้เพื่อลด load
- ทดสอบ cardinality ก่อน deploy — ดูจำนวน series ที่จะสร้างโดยประมาณก่อนเปิดใน production ด้วย query
count({__name__=~"metric_name.*"})
ตัวอย่างการประยุกต์ใช้กับ Web Application
สำหรับ web application ทั่วไป metric พื้นฐานที่ควรมีและสามารถนำไปใช้ทำ RED dashboard ได้ทันทีมีดังนี้
# Rate — จำนวน request ต่อวินาที
sum by (service) (rate(http_requests_total[5m]))
# Errors — อัตรา error
sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
/
sum by (service) (rate(http_requests_total[5m]))
# Duration — p95 latency
histogram_quantile(0.95,
sum by (service, le) (
rate(http_request_duration_seconds_bucket[5m])
)
)
# Saturation — queue/connection ที่ใช้อยู่
max by (service) (db_connections_active / db_connections_max)
สรุป
Data Model ของระบบ metric แบบ time series นี้ประกอบด้วยชื่อ metric, label ที่เป็น key-value pairs, และ sample ที่มี timestamp + value ประเภท metric 4 แบบ (Counter, Gauge, Histogram, Summary) ถูกออกแบบให้เหมาะกับลักษณะข้อมูลที่ต่างกัน การเลือกใช้ให้ถูกประเภทและการออกแบบ label ที่มี cardinality ต่ำ เป็นหัวใจของระบบที่ scale ได้ดี
การเข้าใจ concept เหล่านี้อย่างลึกซึ้งจะช่วยให้เขียน query ได้อย่างมีประสิทธิภาพ ออกแบบ dashboard ที่อ่านง่าย และสร้าง alert ที่แม่นยำ ซึ่งเป็นรากฐานสำคัญของ observability ที่มีคุณภาพในระบบ production

