Ingestion Sampling เทียบกับ Retention Filters
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”“trace sampling” จริง ๆ แล้วคือ สอง control ที่แยกกันแล้วต่อกันเป็นชั้น — อันหนึ่งตัดสินว่าอะไรถูกส่งเข้า Datadog ตั้งแต่แรก อีกอันตัดสินว่าอะไรยัง searchable ได้หลังจากที่เข้ามาแล้ว การมองว่าเป็น setting เดียวกันคือความผิดพลาดเรื่อง sampling ที่พบบ่อยที่สุดใน APM
ชั้นที่ 1: ingestion-time sampling
หัวข้อที่มีชื่อว่า “ชั้นที่ 1: ingestion-time sampling”ชั้นนี้อยู่ใน 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 bothDD_TRACE_SAMPLING_RULES='[{"sample_rate": 0.2}]' # sample 20% of traces at ingestion timeAgent-side ก็มี ingestion control ของตัวเองเช่นกัน (sampling rule ที่ apply ตอน trace ผ่าน Agent ก่อนส่งขึ้นไป) แต่ concept เหมือนกันไม่ว่าจะตัดสินใจตรงไหน — อะไรก็ตามที่ไม่ถูก ingest จะไม่มีทางไปถึง Datadog เลย จบแค่นั้น ไม่มีขั้นตอนไหนทีหลังที่จะกู้ trace ที่ชั้นนี้ทิ้งไปแล้วกลับมาได้
ชั้นที่ 2: APM Retention Filters
หัวข้อที่มีชื่อว่า “ชั้นที่ 2: APM Retention Filters”ทุกอย่างที่รอดจากชั้นที่ 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
Pipeline เต็ม ในไดอะแกรมเดียว
หัวข้อที่มีชื่อว่า “Pipeline เต็ม ในไดอะแกรมเดียว”“ชั้นที่ 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
ตาม 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 (ทุกอย่างหลังจากนั้น)
Single-span sampling: เก็บ span เดียวไว้แม้ทั้ง trace จะถูกทิ้ง
หัวข้อที่มีชื่อว่า “Single-span sampling: เก็บ span เดียวไว้แม้ทั้ง trace จะถูกทิ้ง”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 ที่ไม่มีผล
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 50max_per_second สำคัญตรงนี้เพราะ single-span sampling มีไว้เพื่อกู้ span ที่น่าสนใจจาก trace ที่ถูกทิ้งไปแล้วโดยเฉพาะ — ถ้าไม่มี cap ของตัวเอง rule ที่ match กับ operation ที่ volume สูงจะทำลายจุดประสงค์ทั้งหมดของ ingestion sampler ที่มันพยายามข้ามไปเลย
พิสูจน์ว่า env var ของ sampling ถูกอ่านจริง
หัวข้อที่มีชื่อว่า “พิสูจน์ว่า env var ของ sampling ถูกอ่านจริง”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 มีอยู่แม้ไม่ได้ตั้งค่าอะไรเลย — รายละเอียดเพิ่มเติมด้านล่าง
Rate limiter คือ cap ตัวที่สองที่อิสระจากกัน
หัวข้อที่มีชื่อว่า “Rate limiter คือ cap ตัวที่สองที่อิสระจากกัน”การตั้ง 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)_dd.rule_psr กับ _sampling_priority_v1 บน span จริงที่จับได้
หัวข้อที่มีชื่อว่า “_dd.rule_psr กับ _sampling_priority_v1 บน span จริงที่จับได้”ทุก 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 ที่คุณเขียนบอกให้เก็บ”
ฝั่ง Agent: target_traces_per_second ไม่ใช่ max_traces_per_second ที่ deprecated แล้ว
หัวข้อที่มีชื่อว่า “ฝั่ง Agent: target_traces_per_second ไม่ใช่ max_traces_per_second ที่ deprecated แล้ว”สเปคของบทนี้ชี้ไปที่ 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 ที่ปิดอยู่ ไม่มีอะไรส่งไปไหนเลย)
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 โดยไม่บ่นคือหลักฐานที่บทนี้พูดได้อย่างตรงไปตรงมา
Retention filter JSON model ที่ validate แล้ว — และการแก้ชื่อให้ตรง
หัวข้อที่มีชื่อว่า “Retention filter JSON model ที่ validate แล้ว — และการแก้ชื่อให้ตรง”สเปคของบทนี้คาดว่ามี 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 RetentionFilterCreateRequestpayload = {'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)Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”🐛 หา bug ในโค้ดนี้
# 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}]'
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”คิดว่า 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 นั้นอยู่
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”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 สูงกว่าตามสัดส่วน
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “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 ที่เล็กลงไม่มีทางทำได้