ข้ามไปยังเนื้อหา

Metrics without Limits และ Tag Cardinality

ตัวขับ cost จริงของ custom metric คือ tag cardinality จำนวน combination ของ tag-value ที่ถูก index และ query ได้ ไม่ใช่จำนวน data point ดิบที่ ingest เข้ามา และ proof จริงของบทก่อนหน้าที่เห็น time series แยก 30 ตัว คือสิ่งที่ Metrics without Limits™ ของบทนี้มีไว้ให้กู้คืนได้โดยไม่ต้อง re-instrument

หลายคนคิดว่า cost ของ metric เหมือน log หรือ trace คือยิ่ง data point เยอะยิ่งแพง แต่ custom metric ไม่ได้ทำงานแบบนั้นเป๊ะ ๆ ตาม docs.datadoghq.com/account_management/billing/custom_metrics/: “A custom metric is uniquely identified by a combination of a metric name and tag values (including the host tag)” — ตรงกับหน่วยเดียวกับที่บทก่อนหน้าวัดว่าระเบิดเป็น 30 แถวแยกจาก metric name เดียวกับ tag ที่ไม่ซ้ำกันต่อ call ตัวเดียว การคิด billing เองก็คำนวณแบบเดียวกัน: “Your monthly billable custom metrics usage is calculated by taking the total of all distinct custom metrics (also known as timeseries) for each hour in a given month, and dividing it by the number of hours in the month” — และที่สำคัญ “Your billable usage is not impacted by data point submission frequency or the number of queries you run on your metrics.” ความถี่ในการ submit ฟรี แต่จำนวน series ที่แยกกันไม่ฟรี

Metrics without Limits™ (ชื่อ product ที่จดทะเบียนเครื่องหมายการค้า ตาม docs.datadoghq.com/metrics/metrics-without-limits/) แยก “ส่ง metric นี้ด้วย tag อะไรบ้าง” ออกจาก “tag ไหนถูก index และ query ได้จริงสำหรับ metric นี้ใน Datadog” ยกคำพูดตรง ๆ: “Decouple custom metric ingestion and indexing to control volumes and costs by selecting which tags remain queryable.” docs หน้าเดียวกันตั้งชื่อสอง volume ที่มันแยก billing ออกเป็น: Ingested Custom Metrics (“the original volume of custom metrics based on all ingested tags”) และ Indexed Custom Metrics (“the volume of custom metrics that remains queryable in the Datadog platform”) ในทางปฏิบัติคือ ยังส่ง metric พร้อม tag cardinality สูงอย่าง pod_name จาก application หรือ DogStatsD client ได้ต่อไป — instrumentation ไม่ต้องเปลี่ยนอะไรเลย และตัวอย่าง request_id จริงของบทก่อนหน้าคือ tag ประเภทเป๊ะ ๆ ที่ feature นี้ถูกสร้างมาเพื่อควบคุมย้อนหลัง — แล้วไป config metric ตัวนั้นเฉพาะให้ index แค่ subset ของ tag ที่เลือกไว้ เช่น service กับ env Datadog ยังคง ingest ค่าดิบเหมือนเดิม แต่ time series ที่ query ได้ของ metric นั้นจะยุบลงเหลือแค่ combination cardinality ต่ำตามที่ config ไว้ และนั่นคือสิ่งที่ถูกคิด billing ฝั่ง indexed

จุดสำคัญคือ config นี้เป็นแบบ ต่อ custom metric และ ไม่ทำลายข้อมูลและย้อนกลับได้ ถ้าอีกไม่กี่สัปดาห์ต่อมากำลัง debug ปัญหาแล้วต้องการแบ่ง metric ตัวเดียวกัน by pod_name เพื่อสืบสวนลึกขึ้น ก็เปิด tag นั้นกลับมาให้ query ได้ — ไม่ได้หายไปตลอดกาลเพียงเพราะเมื่อวานไม่ได้ index ไว้ และไม่ต้องแตะ application code หรือ deploy ใหม่เพื่อเปลี่ยนแปลงนี้เลย

Metric: myservice.request.duration
Submitted tags: env, service, pod_name, availability_zone
Indexed tags (configured today): env, service
-> queryable combinations: small, low cost
Indexed tags (turned on later, for an investigation): env, service, pod_name
-> queryable combinations: large, higher cost, but only while you need it

shape ที่ API/Terraform จัดการ config ของ Metrics without Limits คือ MetricTagConfigurationCreateRequest ยืนยันว่ามีอยู่จริงในชื่อนี้เป๊ะภายใต้ module datadog_api_client.v2 ที่ติดตั้งอยู่ (ไม่ใช่ v1 — resource นี้มีแค่ใน v2) ตัวอย่างจริง validate แล้ว:

// myservice-request-duration-tags.json -- metric_tag_configurations is not
// one of this harness's collected --json directories (monitors/dashboards/
// slos/service-catalog/logs-pipelines only); validated by hand below against
// datadog_api_client.v2.model.metric_tag_configuration_create_request.
{
"data": {
"type": "metric_tag_configurations",
"id": "myservice.request.duration",
"attributes": {
"metric_type": "distribution",
"tags": ["env", "service"],
"include_percentiles": true,
"exclude_tags_mode": false
}
}
}
$ python3 -c "
from datadog_api_client.v2.model.metric_tag_configuration_create_request import MetricTagConfigurationCreateRequest
MetricTagConfigurationCreateRequest(**data, _check_type=True)
print('OK: valid')
"
OK: valid

สอง error จริงที่จับได้ ควรรู้ไว้ก่อนที่จะเสียเวลา debug — ทั้งคู่เป็นความผิดพลาดที่ AI-generated code มีโอกาสทำได้จริง เพราะนี่คือ envelope แบบ JSON:API:

# ขาด "id" -- ผิดพลาดได้ธรรมชาติมาก เพราะชื่อ metric อ่านดูเหมือนควรเป็น
# field "attributes.name" ไม่ใช่ "id" แบบ JSON:API
$ python3 -c "
from datadog_api_client.v2.model.metric_tag_configuration_create_request import MetricTagConfigurationCreateRequest
MetricTagConfigurationCreateRequest(data={'type': 'metric_tag_configurations', 'attributes': {...}}, _check_type=True)
"
TypeError: MetricTagConfigurationCreateData.__init__() missing 1 required positional argument: 'id'
# "tags" เป็น string คั่นด้วย comma แทนที่จะเป็น JSON array -- สับสนได้ง่าย
# กับ syntax tag-filter query ของ Datadog เองที่คั่นด้วย comma
$ python3 -c "
...
MetricTagConfigurationCreateRequest(data={..., 'attributes': {'tags': 'env,service', ...}}, _check_type=True)
"
ApiTypeError: Invalid type for variable 'tags'. Required value type is list and passed type was str at ['data']['attributes']['tags']

Percentile aggregation เป็น opt-in และปลดล็อกชุดที่กว้างขึ้นเมื่อขอเท่านั้น

หัวข้อที่มีชื่อว่า “Percentile aggregation เป็น opt-in และปลดล็อกชุดที่กว้างขึ้นเมื่อขอเท่านั้น”

field include_percentiles ใน JSON ด้านบนเป็น field จริง มีอยู่จริงบน model และคุมสิ่งที่ document ไว้ชัดเจนสำหรับ metric DISTRIBUTION ที่ docs.datadoghq.com/metrics/distributions/: โดย default distribution metric มีแค่ count, min, max, sum และ avg เป็น aggregation ตอน query การ opt-in (ตาม docs ปัจจุบันทำผ่านหน้า Metrics Summary ใน UI หรือ field include_percentiles ที่เห็นตรงนี้ผ่าน API/Terraform) ปลดล็อก “p50, p75, p90, p95, and p99 or any user-defined percentile of your choosing (with up to two decimal points such as 99.99).” นี่คือไอเดียเดียวกับ “เลือก aggregation ตอน query ไม่ใช่ตอน submit” ที่บท DogStatsD แนะนำไว้สำหรับ DISTRIBUTION — ความพร้อมใช้งานของ percentile เองเป็น opt-in แยกอีกชั้นบนนั้น ไม่ใช่อัตโนมัติแค่เพราะ metric เป็น DISTRIBUTION

อีกแนวคิดที่เกี่ยวข้องและมักทำให้สับสนคู่กับ cardinality คือสำหรับ metric type DISTRIBUTION ไม่ได้ล็อก “avg” หรือ “p95” ไว้ตอน submit — เลือก aggregation ตอนที่ query หรือ graph metric จริง ๆ ต่างหาก (เมื่อ percentile ถูกปลดล็อกตาม section ด้านบนแล้ว) ข้อมูล distribution ชุดเดียวกันดูเป็น avg, max, p50, p95, หรือ p99 ได้ตามที่เลือกใน query editor หรือ dashboard widget โดยไม่ต้อง resubmit อะไรเลย นี่คือคนละมิติกับ tag cardinality cardinality ควบคุมว่า tag combination ไหน query ได้ ส่วน aggregation ควบคุมว่าค่าตัวเลขภายใน combination ที่เลือกไว้ถูกสรุปยังไงตอนที่ดู

flowchart LR
  A[App / DogStatsD submits high-cardinality tags: pod_name, az, env, service] --> B[Raw ingestion: cheap, all tags accepted]
  B --> C{Metrics without Limits config}
  C -->|indexed today: env, service| D[Queryable time series: low cardinality, lower cost]
  C -->|can enable later: + pod_name| E[Queryable time series: higher cardinality, for deep investigation]
Raw ingestion เทียบกับ tag ที่ index และ query ได้

🐛 หา bug ในโค้ดนี้

// AI comment: "Configure Metrics without Limits for myservice.request.duration"
{
"data": {
"type": "metric_tag_configurations",
"attributes": {
"metric_type": "distribution",
"tags": "env,service",
"include_percentiles": true
}
}
}
เฉลย

ดูสมเหตุสมผลแวบแรก — envelope ระดับบนสุดที่ shape ถูกต้อง, metric type จริง, value ของ tags ที่ดูน่าเชื่อ สอง bug จริง ทั้งคู่พิสูจน์ด้วย model จริงด้านบน: อันดับแรก data.id — ชื่อ metric เอง — หายไปทั้งหมด; ใน payload แบบ JSON:API นี้ชื่อ metric คือ resource id ไม่ใช่ attribute และ client จริงโยน TypeError: ... missing 1 required positional argument: 'id' ทันที อันดับสอง "tags": "env,service" คือ string คั่นด้วย comma ไม่ใช่ JSON array — ผิดพลาดได้ธรรมชาติมากเพราะ syntax tag-filter query ของ Datadog เองที่อื่นใช้ comma คั่น แต่ field นี้ต้องการ JSON array จริง (["env", "service"]) และ client จริงโยน ApiTypeError จริงที่ระบุ field และ path ตรงตัว ทั้งคู่คือความล้มเหลวจริงที่จับได้ทันที — ไม่ใช่ misconfiguration แบบเงียบ ๆ ซึ่งเป็นข่าวดีอย่างเดียวตรงนี้

ลองนึกภาพ submission ดิบของ custom metric เป็น สินค้าที่มาถึงท่าเรือ และ Metrics without Limits เป็น ศุลกากรที่ตัดสินว่าอะไรเข้าคลังสินค้าเทียบกับแค่บันทึกแล้วปล่อยผ่าน ทุกอย่างที่มาถึงถูกตรวจและนับ (ingestion — ฟรี ตามคำพูดจาก billing ด้านบน ไม่ว่าสินค้าจะมาถึงบ่อยแค่ไหน) แต่มีแค่สิ่งที่ศุลกากรตัดสินใจเก็บเข้าคลัง (index) เท่านั้นที่กินพื้นที่จัดเก็บที่ต้องจ่ายเงิน และกลายเป็นสิ่งที่ดึงออกมา query ได้จริงทีหลัง การเปลี่ยนสิ่งที่ศุลกากรเก็บเข้าคลังพรุ่งนี้ไม่ต้องให้ผู้ส่งเปลี่ยนอะไรเกี่ยวกับสิ่งที่ส่งเลย — เป็นการตัดสินใจที่ชายแดนทั้งหมด หลังจากที่มาถึงแล้ว และย้อนกลับได้ทั้งสองทาง

  • Config Metrics without Limits แบบ proactive เทียบกับ reactive — config cardinality ต่ำตั้งแต่วันแรกทำให้ cost ที่ index ทำนายได้ตั้งแต่ต้น แต่ต้องเดาให้ถูกว่า tag ไหนจะไม่ต้อง query เลย; ปล่อยให้ index ทุกอย่างแล้วค่อยลดทีหลังปลอดภัยกว่าสำหรับระบบที่ยังไม่เข้าใจดี แต่เสี่ยงที่ cost ที่ index จะพุ่งขึ้นจริงก่อนที่ใครจะสังเกตเห็น
  • include_percentiles: true เทียบกับชุด aggregation default — ชุด default count/min/max/sum/avg (พิสูจน์ด้านบนผ่าน docs) ครอบ dashboard ส่วนใหญ่แบบถูก; การ opt-in percentile ปลดล็อก query p95/p99 ระดับ global ที่แม่นยำ ซึ่งมักเป็นจุดประสงค์จริงของการเลือก DISTRIBUTION เหนือ HISTOGRAM ตั้งแต่แรก แลกกับ cost เพิ่มเติมที่ pricing ปัจจุบันของ Datadog ผูกไว้ด้วย (claim ที่บทนี้ไม่พูดโดยไม่มี URL ตามกฎของคอร์สนี้เอง)
  • การ validate แบบ strict-type-แต่-loose-enum ของ client นี้ (พิสูจน์ด้านบน) เทียบกับการ call API จริง — การ validate แบบ local offline ที่บทนี้ (และ harness) พึ่งพา จับความผิดพลาดเชิง structure (field required หายไป, JSON type ผิด) ได้ทันทีและฟรี; มันจับ value ที่ผิดเชิงความหมายทุกตัวไม่ได้ (พิสูจน์ด้านบนด้วย "histogram" ที่ผ่านฉลุย) ดังนั้น API call จริงหรือ --tf Terraform validate ยังเป็นแนวป้องกันสุดท้ายสำหรับ bug ประเภทนั้น
  • “cost ของ custom metric scale ตามความถี่ที่ส่ง data point” ผิด — ยกคำพูดตรงจาก billing docs ของ Datadog เองด้านบน: “Your billable usage is not impacted by data point submission frequency.” cost scale ตามจำนวน series ที่แยกกันของ metric-name-บวก-tag-value พิสูจน์ในบทก่อนหน้า
  • “Metrics without Limits เปลี่ยนสิ่งที่ application ส่ง” ผิด — พิสูจน์ด้านบนด้วย shape ของ model เอง: config อยู่ทั้งหมดใน resource metric_tag_configuration แยกต่างหาก key ด้วยชื่อ metric; การ submit ของ DogStatsD/check ของ application ไม่ถูกแตะเลย
  • “JSON metric_tag_configuration ที่ validate ผ่านสะอาดกับ model จริง รับประกันว่าถูกต้อง” ไม่เชิง — พิสูจน์ด้านบน: client นี้ validate field required กับ type อย่างเข้มงวด แต่ value ของ metric_type อย่าง "histogram" ที่ไม่อยู่ใน allowed_values จริงยังผ่าน _check_type=True โดยไม่ error ช่องว่างจริง document ไว้ที่ README ของ harness คอร์สนี้เองระบุเหตุผลเดียวกันเป๊ะ
  • “Percentile aggregation พร้อมใช้อัตโนมัติบน DISTRIBUTION metric ไหนก็ได้” ผิด — ตาม docs ที่ยกมาด้านบน: ชุด aggregation default ของ distribution คือ count/min/max/sum/avg; percentile (p50…p99 หรือ custom) เป็น opt-in แยกชัดเจนอีกชั้น
อะไรคือตัวขับหลักของ cost custom metric ใน Datadog ตาม billing docs ของ Datadog เอง
Metrics without Limits™ ให้ทำอะไรได้ ยกคำพูดตรงจาก docs ของมันเอง
พิสูจน์ด้านบนด้วย MetricTagConfigurationCreateRequest จริง เกิดอะไรขึ้นเมื่อ data.id (ชื่อ metric) ถูกละเว้นจาก payload แบบ JSON:API
พิสูจน์ด้านบนด้วยการ inspect allowed_values จริงของ model ที่ติดตั้งอยู่ metric_type ไหนที่หายไปอย่างเห็นได้ชัดจาก {gauge, rate, count, distribution} -- และทำไมมันสมเหตุสมผลตามบทก่อนหน้า
พิสูจน์ด้านบนด้วย ApiTypeError จริง อะไรผิดกับการส่ง "tags": "env,service" (string คั่นด้วย comma) ใน payload metric_tag_configuration
ตาม docs.datadoghq.com/metrics/distributions/ aggregation อะไรพร้อมใช้บน DISTRIBUTION metric โดย default ก่อนจะ opt-in percentile