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

Ingestion Sampling เทียบกับ Retention Filters

“trace sampling” จริง ๆ แล้วคือ สอง control ที่แยกกันแล้วต่อกันเป็นชั้น — อันหนึ่งตัดสินว่าอะไรถูกส่งเข้า Datadog ตั้งแต่แรก อีกอันตัดสินว่าอะไรยัง searchable ได้หลังจากที่เข้ามาแล้ว การมองว่าเป็น setting เดียวกันคือความผิดพลาดเรื่อง sampling ที่พบบ่อยที่สุดใน APM

ชั้นนี้อยู่ใน tracer (และบางครั้งใน Agent) ตอบคำถามเดียวว่า trace ที่ application สร้างขึ้นกี่ % ที่ถูกส่งเข้า Datadog ตั้งแต่แรก ทำงานก่อนที่ trace ใด ๆ จะออกจาก infrastructure ของคุณด้วยซ้ำ ถ้าตั้ง rate ไว้ที่ 0.2 แปลว่า 80% ของ trace ไม่ถูกส่งเลย — ไม่มีอยู่ใน Datadog ในรูปแบบไหนทั้งสิ้น ไม่ว่าจะ searchable หรือไม่ก็ตาม

# Tracer-side config — controls what even reaches Datadog at all
# DD_TRACE_SAMPLE_RATE is not deprecated, but ddtrace 4.15.2 (Python) ignores it; rules work in both
DD_TRACE_SAMPLING_RULES='[{"sample_rate": 0.2}]' # sample 20% of traces at ingestion time

Agent-side ก็มี ingestion control ของตัวเองเช่นกัน (sampling rule ที่ apply ตอน trace ผ่าน Agent ก่อนส่งขึ้นไป) แต่ concept เหมือนกันไม่ว่าจะตัดสินใจตรงไหน — อะไรก็ตามที่ไม่ถูก ingest จะไม่มีทางไปถึง Datadog เลย จบแค่นั้น ไม่มีขั้นตอนไหนทีหลังที่จะกู้ trace ที่ชั้นนี้ทิ้งไปแล้วกลับมาได้

ทุกอย่างที่รอดจากชั้นที่ 1 จะเข้ามาอยู่ใน Datadog เป็น ingested trace สิ่งที่เกิดขึ้นต่อจากนั้นเป็นการตัดสินใจคนละเรื่องเลย เกิดขึ้นฝั่ง server โดย APM Retention Filters (filter_type: spans-sampling-processor) retention filter ตัดสินว่า trace ที่ ingest ไปแล้วอันไหนยัง searchable และเปิดดูได้ในระยะยาว — ingestion กับ retention ไม่ใช่ประตูเดียวกัน

retention filter หนึ่งตัวมีปุ่มปรับสองตัวที่อิสระจากกัน

  • rate — สัดส่วนของ span ที่ match ที่จะเก็บไว้
  • trace_rate — สัดส่วนแยกต่างหากของ ทั้ง trace ที่จะเก็บไว้ สำหรับ trace ใดก็ตามที่มี span match อยู่อย่างน้อยหนึ่งตัว
{
"data": {
"attributes": {
"name": "keep-checkout-errors",
"enabled": true,
"filter_type": "spans-sampling-processor",
"filter": { "query": "service:checkout status:error" },
"rate": 1.0,
"trace_rate": 1.0
}
}
}

ตัวอย่างนี้บอกว่า สำหรับ span ใดก็ตามที่ match service:checkout status:error เก็บ span พวกนั้นไว้ 100% (rate: 1.0) และเก็บ trace ทั้งชุดที่มี span แบบนั้นอยู่ไว้ 100% เช่นกัน (trace_rate: 1.0) — คุณเลยไม่ได้เก็บแค่ error span เดี่ยว ๆ แต่เก็บ trace ทั้งชุดรอบ ๆ ไว้ด้วย เพื่อดูว่า request มาถึงจุดนั้นได้ยังไง

นอกเหนือจากที่คุณตั้งค่าเอง Datadog ยังรัน Intelligent Retention Filter ที่ทำงานตลอดเวลาอยู่แล้ว — โดยเก็บสัดส่วนตัวแทนของ trace ทั่วทั้ง traffic ไว้อัตโนมัติ เพื่อให้เห็นภาพสุขภาพของ application โดยรวม แยกจากและเพิ่มเติมจาก retention filter ที่คุณกำหนดเอง

ถ้าคุณคิดถึงแค่ “sampling rate ของฉัน” คุณกำลังคิดถึงแค่ชั้นที่ 1 เท่านั้น สิ่งนี้ทิ้งจุดพลาดสองแบบไว้ให้เห็นชัด ๆ

  • คุณ ingest 100% ของ trace ได้ แต่ยัง เสีย trace ที่คุณสนใจจริง ๆ ไปทีหลังได้ ถ้าไม่มี retention filter ตั้งค่าไว้ให้เก็บ trace พวกนั้น — ingestion rate สูงไม่ได้แปลว่า retention รับประกันได้
  • คุณ ingest แค่เศษเสี้ยวเล็ก ๆ ของ traffic ทั้งหมดได้ แต่ยัง เก็บครบ trace ที่สำคัญได้ — เช่น retention filter ที่เจาะจงเก็บ error trace ทุกตัว — เพราะ retention filter ทำงานกับสิ่งที่ผ่านชั้นที่ 1 มาแล้ว และ rate/trace_rate เท่ากับ 1.0 บน query แคบ ๆ เป็นการรับประกันคนละแบบกับ % ingestion โดยรวมของคุณ

สอง layer นี้ถูกออกแบบให้เป็นอิสระจากกัน — ingestion control ต้นทุนและ volume ที่หน้าประตู ส่วน retention filter ควบคุมว่าอะไรจะ searchable ได้ในระยะยาวจากสิ่งที่ผ่านประตูนั้นเข้ามาแล้ว

flowchart LR
  App[Instrumented app generates traces] --> Layer1[Layer 1: ingestion-time sampling in tracer/Agent]
  Layer1 -->|dropped: never reaches Datadog| Gone[Not ingested]
  Layer1 -->|sampled subset ingested| Backend[Datadog APM backend: all ingested traces]
  Backend --> Layer2[Layer 2: APM Retention Filters rate + trace_rate]
  Backend --> IRF[Intelligent Retention Filter: always-on baseline]
  Layer2 -->|kept| Searchable[Searchable, inspectable traces]
  IRF --> Searchable
สอง sampling layer ที่ต่อกันเป็นชั้น

“ชั้นที่ 1” ด้านบนจริง ๆ แล้วเป็นสามกลไกที่ทำงานร่วมกัน ไม่ใช่แค่อันเดียว และยังมี sampling อีกแบบที่บทนี้ยังไม่ได้พูดถึง — single-span sampling — ที่เก็บ span แต่ละตัวไว้ได้แม้ trace-level sampler จะทิ้งทั้ง trace รอบ ๆ มันไปแล้ว ไดอะแกรมเดียวสำหรับเส้นทางเต็มที่ span เดินทางผ่าน ตั้งแต่ tracer จนถึง searchable

flowchart LR
  App["App code creates a span"] --> Sampler["Tracer sampler: DD_TRACE_SAMPLING_RULES + DD_TRACE_RATE_LIMIT"]
  Sampler -->|trace dropped| Gone1["Trace never sent"]
  Sampler -->|trace kept| AgentRL["Agent rate limiter: apm_config.target_traces_per_second"]
  SingleSpan["DD_SPAN_SAMPLING_RULES: single-span sampling"] -.keeps this one span even if its trace was dropped.-> AgentRL
  AgentRL -->|over target, dropped| Gone2["Excess traces dropped at the Agent"]
  AgentRL -->|kept| Intake["Datadog intake: trace ingested"]
  Intake --> RF["APM Retention Filters: rate + trace_rate"]
  Intake --> IRF["Intelligent Retention Filter: always-on baseline"]
  RF -->|kept| Searchable["Searchable, inspectable traces"]
  IRF --> Searchable
Ingestion sampling, Agent rate limiting และ retention filter ครบวงจร

ตาม docs.datadoghq.com/tracing/trace_pipeline/ingestion_mechanisms/: sampling เริ่มจาก head-based sampling — การตัดสินใจที่ “made at the start of the root span” แล้ว propagate ไปทั้ง trace — ตั้งค่าผ่าน DD_TRACE_SAMPLING_RULES (แบบ rule-based) หรือ single-span sampling ผ่าน DD_SPAN_SAMPLING_RULES จากนั้น Agent จะ “continuously sends sampling rates to SDKs to apply at the root of traces” ปรับไปเรื่อย ๆ เข้าหา throughput เป้าหมาย แยกต่างหาก Agent ยังรัน sampler ของตัวเองอีกสองตัวซ้อนบน head-based sampling — error sampler (ดัก error span ที่ rate ตั้งค่าได้ DD_APM_ERROR_TPS) กับ rare sampler (ดัก combination ของ span ที่ไม่เคยเห็นมาก่อน) ทุก span ที่ ingest จะได้ tag ingestion_reason บันทึกว่ากลไกไหนใน 3 อันนี้เป็นเหตุผลที่มันผ่านมาได้ เอกสารเองก็แบ่งชั้นเหมือนบทนี้ทำ แค่แยกเป็นสองหน้า: Ingestion Controls (ทุกอย่างก่อนถึง intake) กับ Trace Retention (ทุกอย่างหลังจากนั้น)

DD_SPAN_SAMPLING_RULES เป็นกลไกที่บทนี้ยังไม่ได้พูดถึง และมันตอบช่องว่างจริงของเรื่องสองชั้นด้านบน: ถ้าtraceถูก ingestion sampling ทิ้งไป แต่ span หนึ่งตัวข้างในนั้น — database call ที่ช้า, call ไปหา downstream service ที่สำคัญเป็นพิเศษ — ยังคุ้มค่าที่จะเก็บไว้ล่ะ single-span sampling จะ ingest span ตัวนั้นแยกอิสระจากการตัดสินใจระดับ trace พิสูจน์ด้วย config dump จริงของ dd-trace — rule ถูก parse และ apply จริง ไม่ใช่ key ที่ไม่มีผล

src/span-sampling-config.js
require('dd-trace').init({
service: 'checkout-web',
logInjection: false,
});
$ DD_SPAN_SAMPLING_RULES='[{"service":"checkout-web","name":"checkout.apply_discount","sample_rate":1.0,"max_per_second":50}]' \
node -e "const t=require('dd-trace').init({service:'probe'}); console.log(JSON.stringify(t._tracer._config.spanSamplingRules))"
[{"service":"checkout-web","name":"checkout.apply_discount","sampleRate":1,"maxPerSecond":50}]

และฝั่ง Python ก็เหมือนกัน ตรวจสอบ rule object จริง (ddtrace.internal.sampling.get_span_sampling_rules()) ยืนยันว่าทั้งสอง field มาถึงจริง

$ DD_SPAN_SAMPLING_RULES='[{"service":"checkout-web","name":"checkout.apply_discount","sample_rate":1.0,"max_per_second":50}]' \
python -c "from ddtrace.internal.sampling import get_span_sampling_rules as g; r=g()[0]; print(r._sample_rate, r._max_per_second)"
1.0 50

max_per_second สำคัญตรงนี้เพราะ single-span sampling มีไว้เพื่อกู้ span ที่น่าสนใจจาก trace ที่ถูกทิ้งไปแล้วโดยเฉพาะ — ถ้าไม่มี cap ของตัวเอง rule ที่ match กับ operation ที่ volume สูงจะทำลายจุดประสงค์ทั้งหมดของ ingestion sampler ที่มันพยายามข้ามไปเลย

env var สี่ตัวต่างอ้างว่าควบคุม sampling ในแบบใดแบบหนึ่ง แทนที่จะเชื่อชื่อ ลอง dump config จริงที่ merge แล้วของแต่ละ tracer หลังตั้งค่าทีละตัว — เทคนิคเดียวกัน (และ harness mode เดียวกัน --options ที่เช็คทุก key กับ .d.ts ของ dd-trace ที่ pin ไว้) ที่จับ bug logInjection/log_injection ใน module correlating-signals ได้

$ DD_TRACE_SAMPLE_RATE=0.3 node -e "const t=require('dd-trace').init({service:'probe'}); console.log(JSON.stringify(t._tracer._config.sampler))"
{"rules":[],"rateLimit":100,"sampleRate":0.3}
$ DD_TRACE_SAMPLING_RULES='[{"service":"checkout-web","sample_rate":0.5}]' node -e "const t=require('dd-trace').init({service:'probe'}); console.log(JSON.stringify(t._tracer._config.sampler))"
{"rules":[{"service":"checkout-web","sampleRate":0.5}],"rateLimit":100}
$ DD_TRACE_RATE_LIMIT=25 node -e "const t=require('dd-trace').init({service:'probe'}); console.log(JSON.stringify(t._tracer._config.sampler))"
{"rules":[],"rateLimit":25}

ทั้งสามตัวถูกอ่านและ merge เข้า sampler config จริงของ tracer ใน dd-trace (Node) จริง ๆ ค่า default rateLimit: 100 ยืนยันประเด็นของ review ว่า rate limiter มีอยู่แม้ไม่ได้ตั้งค่าอะไรเลย — รายละเอียดเพิ่มเติมด้านล่าง

การตั้ง sample rate เป็น 1.0 (หรือไม่ตั้ง rule เลย ซึ่งก็แปลว่า “เก็บทุกอย่างเป็น default” เหมือนกัน) ไม่ได้แปลว่าทุก trace จะถูกส่งจริง — DD_TRACE_RATE_LIMIT/rateLimit cap จำนวน trace ต่อวินาทีที่ sampler จะปล่อยผ่าน ทับซ้อนกับที่ rate บอกไว้อีกที ddtrace (Python) print คำเตือนจริงอธิบายความสัมพันธ์นี้เมื่อตั้งสองอันไม่สอดคล้องกัน

$ DD_TRACE_RATE_LIMIT=25 python -c "import ddtrace; print(ddtrace.tracer._sampler.limiter)"
DD_TRACE_RATE_LIMIT is set to 25 and DD_TRACE_SAMPLING_RULES is not set. Tracer rate limiting is only applied to spans that match tracer sampling rules. All other spans will be rate limited by the Datadog Agent via DD_APM_MAX_TPS.
RateLimiter(rate_limit=25, tokens=25, last_update_ns=..., effective_rate=1.0)

คำเตือนนั้นคือเรื่องสองชั้นตั้งแต่ต้นบทนี้ พูดออกมาโดย tracer เอง: ถ้าไม่มี sampling rule ให้ tracer-side rate limit ไปผูกด้วย rate limiter ของ Agent เอง (apm_config.target_traces_per_second ดูด้านล่าง) คือสิ่งที่ cap throughput จริง ๆ ตั้งทั้ง rule และ rate limit พร้อมกัน tracer-side cap จะ apply กับ span ที่ match rule นั้นโดยเฉพาะ

$ DD_TRACE_RATE_LIMIT=25 DD_TRACE_SAMPLING_RULES='[{"service":"checkout-web","sample_rate":1.0}]' \
python -c "import ddtrace; print(ddtrace.tracer._sampler.limiter)"
RateLimiter(rate_limit=25, tokens=25, last_update_ns=..., effective_rate=1.0)

ทุก span พกหลักฐานของการตัดสินใจ sampling ที่ปล่อยมันผ่านมา ในรูป metrics จับด้วยเทคนิค fake-trace-agent ในเครื่องเดียวกับที่พิสูจน์ใน Traces, Spans, and the Service Map (listener HTTP ธรรมดาที่ decode payload msgpack จริง — ไม่มีอะไรส่งเข้า Datadog) เมื่อไม่ตั้ง sampling rule เลย metrics ของ root span จริงมีแค่ priority tag พื้นฐาน

"metrics": { "_dd.measured": 1, "_sampling_priority_v1": 1 }

ตั้ง DD_TRACE_SAMPLING_RULES='[{"service":"checkout-web","sample_rate":1.0}]' request เดิมสร้าง span จริงที่ metrics มี field ใหม่และค่า priority เปลี่ยนไป

"metrics": { "_dd.measured": 1, "_dd.rule_psr": 1, "_sampling_priority_v1": 2 }

_dd.rule_psr (“rule priority sample rate”) จะโผล่มาแค่ตอนที่ rule จริง ๆ match เท่านั้น — มันคือ rate ที่ rule ตั้งไว้เอง (1.0 ตรงนี้) แปะไว้บน span เป็นหลักฐานว่า rate ไหนถูก apply _sampling_priority_v1 เปลี่ยนจาก 1 (AUTO_KEEP การตัดสินใจ default ของ tracer เอง) เป็น 2 (USER_KEEP มี rule บอกให้เก็บชัดเจน) — ความต่างจริง ที่มองเห็นได้ ระหว่าง “ไม่มีอะไรบอก tracer ให้เก็บ” กับ “rule ที่คุณเขียนบอกให้เก็บ”

สเปคของบทนี้ชี้ไปที่ apm_config.max_traces_per_second ให้เป็น Agent-level cap ที่ต้องพิสูจน์ผ่าน configcheck — การรัน Agent 7.83.3 จริงและอ่าน config template ที่มากับ image เอง (datadog.yaml.example) เจอสิ่งที่สเปคเข้าใจผิด

$ docker run --rm datadog/agent:7 sh -c \
"grep -A5 'max_traces_per_second - float' /etc/datadog-agent/datadog.yaml.example"
# # @param max_traces_per_second - float - optional - default: 10
# # @env DD_APM_MAX_TPS - float - optional - default: 10
# # @env DD_MAX_TPS - float - optional - default: 10
# # DEPRECATED - please use `target_traces_per_second` instead.

apm_config.max_traces_per_second มีจริง ยังทำงานได้ และ default เป็น 10 (ไม่ใช่ 100) — แต่ template ที่มากับ Agent เองระบุว่า deprecated แล้วให้ใช้ apm_config.target_traces_per_second แทน (ความหมายเดียวกัน default เดียวกัน) datadog.yaml ที่ใช้ key ใหม่ ผ่าน agent configcheck กับ container datadog/agent:7 ที่ pull มาสด ๆ จริง (DD_API_KEY ปลอม DD_DD_URL ชี้ไป local port ที่ปิดอยู่ ไม่มีอะไรส่งไปไหนเลย)

conf.d/../datadog.yaml
apm_config:
enabled: true
target_traces_per_second: 20
errors_per_second: 10
$ docker run -d --name dd-apm -e DD_API_KEY=00000000000000000000000000000fa \
-e DD_DD_URL=http://127.0.0.1:1 -e DD_HOSTNAME=dd-apm-lesson \
-v $PWD/datadog.yaml:/etc/datadog-agent/datadog.yaml:ro datadog/agent:7
$ docker exec dd-apm agent configcheck
=== cloud_hostinfo check ===
...

configcheck รันจบด้วย default check ทุกตัวและไม่มี error/warning เกี่ยวกับ apm_config เลย — Agent ยอมรับ key ที่ deprecated ด้วยเงียบ ๆ (แค่ชอบ key ใหม่มากกว่า) trace-agent process จริงของ Agent ไม่ reachable ภายในช่วงเวลาของการรันแบบ sandbox นี้ (section APM Agent ของ agent status รายงาน “Not running or unreachable on localhost:5012” — คาดไว้แล้ว เพราะ DD_DD_URL ชี้ไป port ที่ปิดอยู่ ไม่มี network path จริงในนี้เลย) configcheck ที่ยอมรับ config โดยไม่บ่นคือหลักฐานที่บทนี้พูดได้อย่างตรงไปตรงมา

สเปคของบทนี้คาดว่ามี model ชื่อ ApmRetentionFilterCreateRequest datadog_api_client==2.61.0 ที่ติดตั้งจริงไม่มี module แบบนั้น ลอง list model retention ทั้งหมดที่มันมีจริง

$ python -c "import pkgutil, datadog_api_client.v2.model as m; \
print([n for _, n, _ in pkgutil.iter_modules(m.__path__) if n.startswith('retention_filter')])"
['retention_filter', 'retention_filter_all', ..., 'retention_filter_create_attributes',
'retention_filter_create_data', 'retention_filter_create_request', 'retention_filter_create_response', ...]

class จริงคือ RetentionFilterCreateRequest (ไม่มี prefix Apm) — client ตัวนี้จัดกลุ่ม APM กับ RUM retention filter แยกกันคนละแบบ: rum_retention_filter_create_request คือฝั่ง RUM ที่คู่กัน validate ตัวอย่าง JSON ก่อนหน้าของบทนี้กับ model จริง

$ python -c "
from datadog_api_client.v2.model.retention_filter_create_request import RetentionFilterCreateRequest
payload = {'data': {'type': 'apm_retention_filter', 'attributes': {
'name': 'keep-checkout-errors', 'enabled': True,
'filter_type': 'spans-sampling-processor',
'filter': {'query': 'service:checkout status:error'},
'rate': 1.0, 'trace_rate': 1.0}}}
print(RetentionFilterCreateRequest(**payload))
"
validated OK

เวอร์ชันที่พังไป ขาด field ที่จำเป็น filter_type ให้ error จริงที่บอกตรง ๆ ว่าขาดอะไร

# @expect-error RetentionFilterCreateAttributes.__init__() missing 1 required positional argument: 'filter_type'
from datadog_api_client.v2.model.retention_filter_create_request import RetentionFilterCreateRequest
payload = {"data": {"type": "apm_retention_filter", "attributes": {
"name": "broken-filter", "enabled": True,
"filter": {"query": "service:checkout"}, "rate": 1.0
}}}
RetentionFilterCreateRequest(**payload)

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

app/configure_sampling.py
# AI comment: "Sample 20% of traces to control APM costs"
import os
os.environ["DD_TRACE_SAMPLE_RATE"] = "0.2"
# ... ddtrace-run picks this up before the app starts ...
เฉลย

นี่คือ pattern เดียวกันกับตัวอย่าง Node.js ก่อนหน้าในบทนี้ ย้ายมาเป็น Python — และไม่มีผลอะไรเลยแบบเงียบ ๆ พิสูจน์ด้านบน: grep -rn "DD_TRACE_SAMPLE_RATE" ddtrace/ ทั่วทั้ง ddtrace==4.15.2 ที่ pin ไว้เจอแค่ใน list SUPPORTED_CONFIGURATIONS สำหรับ telemetry ไม่เคยถูกอ่านเข้า sampler จริงเลย (ddtrace.tracer._sampler.rules ยังเป็น [] แม้ตั้ง var นี้ไว้) service ยัง sample ที่ default ที่ไม่ได้ตั้งค่า — จริง ๆ แล้วคือ 100% คุมแค่ด้วย rate limiter ของ Agent — ในขณะที่ทีมเชื่อว่าคุม cost ไว้ที่ 20% แล้ว ทางแก้ที่พิสูจน์แล้วด้านบนว่าใช้ได้จริงใน version นี้: DD_TRACE_SAMPLING_RULES='[{"sample_rate": 0.2}]'

คิดว่า pipeline ทั้งหมดเป็นประตูเรียงต่อกัน แต่ละประตูทำให้สิ่งต่าง ๆเล็กลงได้อย่างเดียว ไม่มีทางเอาสิ่งที่ประตูก่อนหน้าปิดไปแล้วกลับมาได้ sampler ของ tracer คือประตูแรก พิสูจน์ด้านบน: การตัดสินใจของมัน (_sampling_priority_v1, _dd.rule_psr) ถูกแปะบน span ก่อนที่มันจะออกจาก process ด้วยซ้ำ rate limiter ของ Agent คือประตูที่สอง บังคับ cap แข็ง ๆ ไม่ว่าประตูแรกจะตัดสินใจยังไง retention filter คือชุดประตูที่สาม แยกกันสมบูรณ์ แต่เปิดรับเฉพาะ traffic ที่ ingest แล้วเท่านั้น — มันไม่มีทางเห็น หรือกู้ อะไรที่สองประตูแรกปิดไปแล้ว single-span sampling คือข้อยกเว้นเดียวของ “ประตูทำให้เล็กลงอย่างเดียว” ใน pipeline นี้: มันคือประตูข้างที่เก็บ span เดียวไว้ได้แม้ประตูหลัก (trace-level sampling) จะปิดไปแล้วรอบ ๆ trace ที่ span นั้นอยู่

  • DD_TRACE_SAMPLING_RULES เทียบกับ DD_TRACE_SAMPLE_RATE — sampling rules แม่นยำกว่า (พิสูจน์ด้านบน: แยกตาม service, name, resource ได้) และพิสูจน์แล้วว่าเป็นกลไกเดียวที่ยืนยันว่าทำงานเหมือนกันทั้ง Node และ Python ส่วน flat sample rate เข้าใจง่ายกว่าแต่ (พิสูจน์ด้านบน) ไม่มีผลอะไรเลยแบบเงียบ ๆ ใน Python client ที่ pin ไว้
  • Tracer-side rate limit เทียบกับ Agent-side rate limit — พิสูจน์ด้านบนผ่านคำเตือนของ ddtrace เอง: DD_TRACE_RATE_LIMIT ฝั่ง tracer apply กับ span ที่ match sampling rule เท่านั้น ที่เหลือถูก cap ทีหลังโดย target_traces_per_second ของ Agent พึ่งแค่อันเดียวจากสองอันนี้จะเหลือช่องว่างจริงสำหรับ traffic ที่ไม่ match rule ของคุณ
  • Single-span sampling เทียบกับ trace-level rule ที่กว้างกว่า — พิสูจน์ด้านบนว่ากู้ span ที่สำคัญตัวหนึ่งจาก trace ที่ถูกทิ้งไปแล้วได้จริง แลกกับการเสีย context ที่เหลือทั้งหมดของ trace นั้นไป ส่วน trace-level rule เก็บเรื่องราวทั้งหมดไว้ แต่ต้นทุน volume ที่ ingest สูงกว่าตามสัดส่วน
  • “sample rate 1.0 (หรือไม่ตั้ง rule เลย) รับประกันว่า trace ทุกตัวถูก ingest” ไม่ใช่ — พิสูจน์ด้านบน: target_traces_per_second ของ Agent (default 10 จาก config template ของ Agent 7.83.3 จริง) ยัง cap throughput อยู่ดีไม่ว่า tracer-side จะตัดสินใจยังไง
  • “DD_TRACE_SAMPLE_RATE ทำงานเหมือนกันในทุก Datadog tracing library” ไม่ใช่ — พิสูจน์ด้านบน: มันถูกอ่านเข้า sampler config จริงใน dd-trace (Node) แต่ไม่มีผลอะไรเลยใน ddtrace (Python) ที่ pin ไว้ — ยืนยันด้วยการ grep ทั้ง package ที่ติดตั้งหา string นั้น
  • “apm_config.max_traces_per_second คือ Agent setting ปัจจุบันที่แนะนำ” ไม่ใช่ — พิสูจน์ด้านบนจาก config template ของ Agent 7.83.3 จริง: มันถูกระบุชัดว่า DEPRECATED - please use target_traces_per_second instead แม้จะยังทำงานได้และ default เป็น 10
  • “Single-span sampling ก็แค่ trace-level sampling ที่ scope เล็กลง” ไม่ใช่ — พิสูจน์ด้านบน: มันเป็นกลไกที่แยกจริง ๆ ที่เก็บ span เดียวได้แม้ trace-level sampler จะทิ้งทั้ง trace รอบ ๆ ไปแล้ว ซึ่ง trace-level rate ที่เล็กลงไม่มีทางทำได้
ingestion-time sampling ใน tracer/Agent ควบคุมอะไร
ใน APM Retention Filter หนึ่งตัว rate กับ trace_rate ต่างกันยังไง
trace ตัวหนึ่งถูก ingestion-time sampling ทิ้งไปก่อนถึง Datadog เกิดอะไรขึ้นต่อ
ทีมหนึ่ง ingest แค่ 5% ของ traffic ทั้งหมด แต่มี retention filter ที่ match status:error ตั้ง rate: 1.0 และ trace_rate: 1.0 ข้อไหนถูกต้อง
test จริง: ตั้ง DD_TRACE_SAMPLE_RATE=0.4 แล้ว print ddtrace.tracer._sampler.rules บน ddtrace==4.15.2 (Python) ที่ pin ไว้ ได้ผลยังไง แล้ว test เดียวกันบน dd-trace (Node) ได้ผลยังไง
apm_config.target_traces_per_second ใน datadog.yaml.example ของ Agent 7.83.3 จริง มาแทนอะไร และทำไม