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

Pipeline การ Sampling ของ Trace

trace ผ่าน ห้าด่านตามลำดับคงที่ — head sampler ของ tracer, rate limiter ของ tracer, sampler ของ Agent (ที่ส่วนใหญ่ แนะนำ มากกว่าบังคับ), ingestion controls ที่ backend และสุดท้าย retention filters กับ intelligent retention — แต่ละด่านมีปุ่มปรับของตัวเอง มี tag หลักฐานบน span ของตัวเอง และมีวิธี config ผิดแบบเงียบ ๆ ของตัวเอง

flowchart TB
  S["Root span starts"] --> HS{"1. Head sampler in the tracer: DD_TRACE_SAMPLING_RULES, else DD_TRACE_SAMPLE_RATE, else Agent rate_by_service"}
  HS -->|"priority 2 or 1: keep"| RL{"2. Tracer rate limiter: DD_TRACE_RATE_LIMIT"}
  HS -->|"priority -1 or 0: reject, still sent unless discard"| AG
  RL -->|"over the limit: priority -1"| AG
  RL -->|"within the limit"| AG["3. Agent: samplers and feedback: DD_APM_TARGET_TPS, DD_APM_ERROR_TPS, DD_APM_PROBABILISTIC_SAMPLER_*"]
  AG -->|"kept traces"| IC["4. Ingestion controls: Ingestion Control page, remote configuration"]
  AG -.->|"rate_by_service back to tracers"| HS
  IC --> INGESTED["Ingested spans: APM metrics come from all traffic"]
  INGESTED --> RF["5a. Retention filters: UI or API, first match wins"]
  INGESTED --> IR["5b. Intelligent retention: diversity plus 1 percent flat"]
  RF --> IDX["Indexed spans searchable for the retention period"]
  IR --> IDX
จาก span ถึง trace ที่ค้นหาได้
#ด่านรันที่ไหนตัวควบคุมหลักฐานที่ทิ้งไว้บนข้อมูล
1Head samplertracer, ที่ root spanDD_TRACE_SAMPLING_RULES, DD_TRACE_SAMPLE_RATE, rate จาก Agent_sampling_priority_v1, _dd.p.dm, _dd.rule_psr, _dd.agent_psr
2Rate limitertracerDD_TRACE_RATE_LIMIT_dd.limit_psr
3Agent samplersAgent (trace-agent)DD_APM_TARGET_TPS, DD_APM_ERROR_TPS, DD_APM_PROBABILISTIC_SAMPLER_ENABLEDresponse rate_by_service, _dd.p.dm -9
4Ingestion controlsDatadog backend + remote config ไปที่ SDKหน้า Ingestion Control_dd.p.dm -11 / -12 ใน constant ของ tracer
5RetentionDatadog backendretention filters, intelligent retentionไม่มีบน wire (docs เท่านั้น)

การตัดสินใจเกิดที่ span แรกและเดินทางไปกับ trace docs: “The decision to keep or drop a trace is made at the start of the root span and then propagated to other services as part of their request context” (ingestion mechanisms) ค่ามาจาก tracer ที่ pin ไว้ (node_modules/dd-trace/ext/priority.js): USER_REJECT: -1, AUTO_REJECT: 0, AUTO_KEEP: 1, USER_KEEP: 2 และรหัส mechanism จาก packages/dd-trace/src/constants.js: DEFAULT: 0, AGENT: 1, RULE: 3, MANUAL: 4, APPSEC: 5, SPAN: 8, REMOTE_USER: 11, REMOTE_DYNAMIC: 12, AI_GUARD: 13 tag _dd.p.dm คือ - ตามด้วยรหัสนั้น

สิ่งที่ fake trace-agent พิมพ์สำหรับ root span 40 ถึง 200 ตัวต่อหนึ่ง config (DD_SERVICE=sampling-lab):

== default, no rules (40 traces)
x40 priority=1 dm=-0 rule_psr=None limit_psr=None agent_psr=1
== rule sampleRate 1.0 DD_TRACE_SAMPLING_RULES='[{"service":"sampling-lab","sample_rate":1.0}]'
x40 priority=2 dm=-3 rule_psr=1 limit_psr=1 agent_psr=None
== rule sampleRate 0
x40 priority=-1 dm=None rule_psr=0 limit_psr=None agent_psr=None
== rule 0.5 (200 traces)
x107 priority=-1 dm=None rule_psr=0.5
x93 priority=2 dm=-3 rule_psr=0.5 limit_psr=1
== DD_TRACE_SAMPLE_RATE=0.25 (200 traces)
x154 priority=-1 dm=None rule_psr=0.25
x46 priority=2 dm=-3 rule_psr=0.25 limit_psr=1
== rule with "discard": true
received root spans: 0

เห็นห้าข้อเท็จจริง (1) เมื่อไม่ตั้งอะไร tracer ใช้ mechanism default (dm=-0) และ priority 1 กับ _dd.agent_psr=1 เพราะยังไม่มี Agent บอกเป็นอย่างอื่น (2) rule ที่ตรงเปลี่ยน mechanism เป็น -3 และ priority เป็น 2 (user keep) สำหรับ trace ที่ถูกเก็บ; rate ของ rule เองถูกประทับเป็น _dd.rule_psr (3) DD_TRACE_SAMPLE_RATE=0.25 แบบ global ถูก implement เป็น rule — ทิ้ง _dd.rule_psr=0.25 และ dm=-3 และรอด 46 จาก 200 (23%) (4) trace ที่ถูก reject (priority=-1) ยังถูกส่ง ไปที่ Agent: trace ที่ reject 40 ตัวไปถึง fake agent นี่เป็นการออกแบบ Agent ใช้มันคำนวณ trace metrics (5) มีแค่ "discard": true ใน rule (ใน .d.ts ที่ pin: “a trace chunk rejected by this rule is fully dropped: it is excluded from client-side stats and never sent to the Agent”) ที่หยุดการส่ง: root span ไปถึง 0 ตัว

แหล่งที่สามของ rate คือ Agent ผมรันซ้ำโดยให้ fake agent ตอบ {"rate_by_service":{"service:sampling-lab,env:lab":0.25}} ทุก flush ปล่อย 400 trace ทีละ 20 ทุก 500 ms:

x20 priority=1 dm=-0 agent_psr=1 # before the first response arrives
x99 priority=1 dm=-1 agent_psr=0.25 # kept under the Agent's rate
x281 priority=0 dm=None agent_psr=0.25 # rejected under the Agent's rate

trace 20 ตัวแรกออกไปด้วย default; หลัง response แรก tracer รับ rate ของ Agent (dm=-1, AGENT) และเก็บได้ 99 จาก 380 (26%) ลำดับความสำคัญจึงเป็น rule ก่อน แล้ว rate จาก Agent แล้ว default

DD_TRACE_RATE_LIMIT จำกัดจำนวน trace ต่อวินาทีที่ head sampler เก็บได้ ใช้ หลัง rate docs: “If no value is set, a limit of 100 traces per second is applied.” rule sample_rate: 1.0 กับ DD_TRACE_RATE_LIMIT=5 ปล่อย 60 trace เร็วที่สุดเท่าที่ loop ทำได้:

x5 priority=2 dm=-3 rule_psr=1 limit_psr=1
x1 priority=-1 dm=None rule_psr=1 limit_psr=0.8333333333333334
x1 priority=-1 dm=None rule_psr=1 limit_psr=0.7142857142857143
...
x1 priority=-1 dm=None rule_psr=1 limit_psr=0.08333333333333333

5 ตัวแรกผ่านพร้อม _dd.limit_psr=1 ทุก trace ถัดมาใน วินาทีนั้นถูก reject (priority=-1) และประทับด้วย effective rate ของ limiter ซึ่งลดลงตาม 5 / n (0.833 = 5/6, 0.714 = 5/7, … 0.0833 = 5/60) _dd.limit_psr คือหลักฐานว่าด่านที่ 2 ไม่ใช่ rule ที่ทิ้ง trace — _dd.rule_psr=1 ที่อยู่ข้าง priority=-1 คือลายเซ็นของ rate limiter ที่ทำงาน tracer ของ Node รองรับ cap ต่อ rule ด้วย (maxPerSecond ใน .d.ts, max_per_second ใน env JSON); rule {"sample_rate":1.0,"max_per_second":2} เก็บได้ 2 จาก 60 พอดี ddtrace 4.15.2 ของ Python ไม่รองรับ: ค่า env เดียวกันทำให้ process ล้มตั้งแต่ import

$ DD_TRACE_SAMPLING_RULES='[{"service":"sampling-lab","sample_rate":0.5,"max_per_second":3}]' python -c "import ddtrace; ddtrace.tracer"
File ".../ddtrace/_trace/sampler.py", line 156, in set_sampling_rules
sampling_rules.append(SamplingRule(**rule))
TypeError: SamplingRule.__init__() got an unexpected keyword argument 'max_per_second'

signature ของ Python ที่ introspect ได้คือ SamplingRule(sample_rate, service, name, resource, tags, provenance, discard) — ไม่มี max_per_second รูปแบบที่รับได้พิมพ์ DatadogSampler(agent_rates={}, limiter=RateLimiter(rate_limit=7, ...), rules=[SamplingRule(sample_rate=0.5, service=GlobMatcher(pattern=sampling-lab), ..., discard=False)])

ด่านที่ 3: Agent — ส่วนใหญ่แค่แนะนำ มีไม่กี่อย่างที่บังคับ

หัวข้อที่มีชื่อว่า “ด่านที่ 3: Agent — ส่วนใหญ่แค่แนะนำ มีไม่กี่อย่างที่บังคับ”

ด่านนี้คนเข้าใจผิดบ่อยที่สุด จึงวัดกับ Agent จริง target_traces_per_second ของ Agent (DD_APM_TARGET_TPS, default 10) ไม่ได้ทิ้ง trace ที่ผ่าน head sampling แล้ว มันคำนวณ rate แล้วบอก tracer docs ก็ว่าเช่นเดียวกัน: “The Datadog Agent continuously sends sampling rates to SDKs to apply at the root of traces. The Agent adjusts rates to achieve a target of ten traces per second overall.” ผม POST root span 100 ตัวต่อวินาที (priority 1, service เดียว) ไปที่ receiver จริง แล้วพิมพ์ field rate_by_service ของแต่ละ response ซึ่งเปลี่ยนเฉพาะตอน rate เปลี่ยน:

t= 0.0s sent 100 traces -> {"service:,env:": 1}
t= 5.1s sent 100 traces -> {"service:,env:": 0.125, "service:sampling-lab,env:lab": 0.125}
t=10.1s sent 100 traces -> {"service:,env:": 0.1, "service:sampling-lab,env:lab": 0.1}

เป้า 10 ต่อวินาทีเทียบกับ 100 ที่ส่งไป ลู่เข้า 0.1 หลังราว 10 วินาที แต่ Agent forward ทุกอย่างที่ผมส่ง: priority-1 720 ตัวเข้า, 720 chunk ออกที่ fake intake (chunks x720 priority=1) rate เป็นแค่คำขอถึง tracer เท่านั้น tracer ที่ไม่สนใจมัน (client ที่เขียนเอง หรือ DD_TRACE_SAMPLING_RULES ตายตัว) ก็ยังผ่านไปได้ทั้งหมด template ที่ Agent ส่งมาเองบอกว่า 0 ทำอะไร (อ่านจาก /etc/datadog-agent/datadog.yaml.example): “As of 7.35.0 sampling cannot be disabled and setting ‘max_traces_per_second’ to 0 no longer disables sampling, but instead sends no traces to the intake.” เมื่อตั้ง DD_APM_MAX_TPS=0, rate_by_service เป็น 0, agent status พิมพ์ Priority sampling rate for 'service:sampling-lab,env:lab': 0.0% และบรรทัด Writer เป็น Traces: 0 payloads, 0 traces หลังรับ trace 1,000 ตัว stats writer ยังส่ง payload (Stats: 2 payloads, 2 stats buckets): trace metrics คำนวณจากทุกอย่างที่ Agent รับ ไม่ใช่จากที่มัน forward

มี sampler ฝั่ง Agent สามตัวที่บังคับจริง

== error sampler: 500 traces, priority 0 + error=1, 50/s for 10 s
chunks x105 priority=0 arrived at the intake
== priority 0, no error, same rate
total chunks 0
== probabilistic sampler (DD_APM_PROBABILISTIC_SAMPLER_ENABLED=true, ..._SAMPLING_PERCENTAGE=20): 1000 priority-1 traces
chunks x210 priority=1 _dd.p.dm=-9 # status: "Probabilistic sampling percentage: 20%"

error sampler ช่วย trace ที่ถูก reject แต่มี error ไว้ได้ 105 จาก 500 — ประมาณ errors_per_second: 10 เป็นเวลา 10 วินาที ซึ่ง docs อธิบายว่าจับ “pieces of traces that contain error spans not caught by head-based sampling, at a rate of up to 10 traces per second per Agent.” trace ที่ถูก reject โดยไม่มี error ถูกทิ้งหมด (0 chunk) rare sampler ปิดเป็น default (enable_rare_sampler: false ใน agent config --all; docs: default false ตั้งแต่ 7.41.0) probabilistic sampler เก็บเปอร์เซ็นต์คงที่ด้วยการ hash: 210 จาก 1,000 (21%) เมื่อตั้ง 20% ติด _dd.p.dm=-9 ซึ่งเป็น mechanism ที่มีแต่ Agent สร้าง (ไม่อยู่ใน constant ของ tracer ข้างบน) เมื่อเปิดมัน body ของ response ว่างเปล่า {}: Agent เลิกส่ง rate เมื่อมันทำ sampling เอง

ทุกอย่างข้างบนจบที่ intake ของ Datadog สิ่งที่เกิดต่อไปเป็น config ฝั่ง backend จึงอ้าง docs ไม่ได้รัน หน้า Ingestion Control ให้ “visibility into the ingestion configuration of your applications and services” และให้คุณ “Adjust trace sampling rates for high throughput services or endpoints”; การเปลี่ยนไปถึง tracer ผ่าน Remote Configuration ซึ่งต้องใช้ “Datadog Agent 7.41.1 or higher” และ SDK version เช่น Python v2.9.0 และ Node.js v5.16.0 และ “you do not need to redeploy the service for the change to take effect” (ingestion controls) tracer ที่ pin สะท้อนเรื่องนี้: SAMPLING_MECHANISM_REMOTE_USER: 11 และ SAMPLING_MECHANISM_REMOTE_DYNAMIC: 12 มีอยู่ใน constant trace ที่ถูก sample ด้วย rate จาก UI จึงมี _dd.p.dm=-11 หรือ -12 แทน -3 และข้อควรระวังที่ช่วยรีวิว incident ได้มาก จากหน้าเดียวกัน: “APM Metrics are always calculated based on all traces, and are not impacted by ingestion controls.”

เป็น backend เช่นกัน และอ้าง docs เช่นกัน (trace retention) retention filter index span ที่ ingest แล้วเป็นเวลา 15 วัน (“kept for 15 days according to the retention filters that are set up on your account”) และลำดับสำคัญ: “If a span matches a retention filter early in the list, the span is either kept or dropped. Any matching custom retention filter lower on the list does not catch the already-processed span.” ที่สำคัญคือ “Retention filters do not affect what traces are collected by the Agent and sent to Datadog (‘ingested’).” Intelligent retention คือ baseline ที่เปิดเสมอ: diversity sampling “retains at least one span (and the associated trace) for each combination of environment, service, operation, and resource,” ร่วมกับ “uniform 1% sample of ingested spans.” ด่านที่ 4 และ 5 ไม่มีอะไรให้เห็นใน span บน wire นี่คือเหตุผลที่หลักฐานเดียวที่ซื่อสัตย์แบบ offline คือการอ้าง docs

อ่าน pipeline เป็น กรวยที่มีวาล์วย้อนกลับสองตัวและพื้นแข็งหนึ่งชั้น head sampler ตัดสินต่อ trace ที่ต้นทางและเป็นด่านเดียวที่ประหยัด network กับ CPU rate limiter เป็นวาล์วนิรภัยบนด่านเดียวกัน Agent ดูปริมาณรวมแล้ว ขอ ให้ tracer เปลี่ยน rate — control loop ที่หน่วงหลายวินาที — และบังคับเฉพาะกรณีพิเศษ (error, probabilistic, MAX_TPS=0) ทุกอย่างหลัง ingestion เป็นการตัดสินใจเรื่องการเก็บ span ที่มาถึงแล้วและถูกนับใน metrics แล้ว เมื่อ trace หาย ให้หาว่าด่านไหนทิ้งด้วยลายนิ้วมือ: priority=-1 กับ _dd.rule_psr คือ rule, บวก _dd.limit_psr < 1 คือ limiter, priority=0 กับ _dd.agent_psr < 1 คือ rate จาก Agent

ทางเลือกข้อดีข้อเสีย
rule ใน tracer (DD_TRACE_SAMPLING_RULES)ตัดสินต่อ service/resource/tag; ประหยัด CPU และ bandwidth ที่ต้นทางตายตัวต่อการ deploy เว้นแต่ใช้ remote configuration; syntax ต่างกันตามภาษา (ดู max_per_second)
rate ที่ Agent ขับเคลื่อน (default)ไม่ต้อง config; ปรับตามทราฟฟิกเข้าหาเป้า 10 trace/sหน่วงหลายวินาทีหลังทราฟฟิกกระโดด; ไม่บังคับกับ tracer ที่ไม่สนใจ
discard: true ใน ruleทิ้ง trace และ ส่วนที่มันสร้างใน statstrace metrics นับต่ำกว่าจริง; request rate ในหน้า service ไม่ใช่ rate จริงอีกต่อไป
probabilistic sampler ของ Agentเปอร์เซ็นต์เดียวกันทุก SDK และแหล่ง OTel และบังคับจริงตัดสินที่ Agent หลังจากที่ tracer จ่ายค่าสร้างและส่งทุก trace ไปแล้ว (รับ 1,000 forward 210 ใน run ข้างบน)
error sampler ที่ default 10/serror รอดจาก head sampling ของทราฟฟิกปกติเกิน 10 error/s ต่อ Agent trace error ก็ถูกทิ้งด้วย
ingestion controls (UI)เปลี่ยน rate โดยไม่ต้อง redeployต้องใช้ Agent และ SDK ตั้งแต่ขั้นต่ำของ remote-config; “ใครตั้ง rate นี้” ย้ายออกจาก git
retention filtersเก็บเฉพาะสิ่งที่ค้นหา ตามระยะเวลา retentionเอา trace ที่ไม่ได้ ingest กลับมาไม่ได้
  • “DD_APM_TARGET_TPS=10 แปลว่า Agent forward ไม่เกิน 10 trace ต่อวินาที” วัดแล้ว: priority-1 เข้า 720 ออก 720 มันคือ rate ที่โฆษณาให้ tracer
  • “max_traces_per_second: 0 ปิด sampling” มันไม่ส่ง trace ไป intake เลย (วัดแล้ว: Traces: 0 payloads, 0 traces หลังรับ 1,000)
  • “trace ที่ถูก sample ออกไม่เคยออกจากแอป” trace ที่ถูก reject (priority=-1 หรือ 0) ยังถูกส่งไป Agent เว้นแต่ rule สั่ง discard นี่คือเหตุผลที่ metrics ครบ
  • “_dd.rule_psr บน span แปลว่ามันถูกเก็บ” แปลว่า rule ตรง priority=-1 ที่อยู่ข้างกันแปลว่า rule (หรือ limiter ที่อยู่หลัง) reject
  • “ค่า DD_TRACE_SAMPLING_RULES เดียวใช้ได้ทุกภาษา” Node รับ max_per_second; Python 4.15.2 raise TypeError ตอน import
  • “retention filters ลดต้นทุน ingestion” มันทำงานหลัง ingestion; docs: มัน “do not affect what traces are collected by the Agent and sent to Datadog”

โมเดลที่ถูกขอให้ “sample 10% ของ checkout trace แต่ไม่เกิน 5 ต่อวินาที” เขียน cap ต่อ rule ที่เห็นในตัวอย่าง Node

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

Terminal window
# scripts/checkout-sampling.sh (Python service, ddtrace)
# Sample 10% of checkout traces and cap the rule at 5 traces per second
export DD_SERVICE=checkout
export DD_TRACE_SAMPLING_RULES='[{"service":"checkout","sample_rate":0.1,"max_per_second":5}]'
เฉลย

max_per_second ภายใน rule เป็นฟีเจอร์ของ Node ใน tracer Python ที่ pin ไว้ (ddtrace 4.15.2) SamplingRule.__init__ ไม่มี keyword นั้น และการรันจริงข้างบนจบด้วย TypeError: SamplingRule.__init__() got an unexpected keyword argument 'max_per_second' — service ล้มตอนเริ่มแทนที่จะ sample (ส่วน Node รับ max_per_second:2 และเก็บได้ 2 จาก 60 พอดี) สำหรับ Python ให้ใช้ limiter แบบ global ซึ่ง cap trace ที่ตรงกับ sampling rule:

scripts/checkout-sampling.sh
export DD_SERVICE=checkout
export DD_TRACE_SAMPLING_RULES='[{"service":"checkout","sample_rate":0.1}]'
export DD_TRACE_RATE_LIMIT=5

config ที่โมเดลเขียนอีกอันปิด sampling ใน Agent:

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

datadog.yaml
api_key: <your-api-key>
apm_config:
enabled: true
# keep every trace: 0 means unlimited
max_traces_per_second: 0
เฉลย

template ที่ Agent ส่งมาเอง (datadog.yaml.example ใน container) บอกตรงข้าม: “As of 7.35.0 sampling cannot be disabled and setting ‘max_traces_per_second’ to 0 no longer disables sampling, but instead sends no traces to the intake.” วัดกับ DD_APM_MAX_TPS=0: rate_by_service ตอบ 0, agent status พิมพ์ Priority sampling rate ... 0.0% และ trace 1,000 ตัวที่รับทำให้บรรทัด Writer เป็น Traces: 0 payloads, 0 traces key นี้ยัง deprecated แทนด้วย target_traces_per_second ถ้าต้องการเก็บมากขึ้น ให้ยกเป้าให้สูงกว่าทราฟฟิกช่วง peak:

datadog.yaml
api_key: <your-api-key>
apm_config:
enabled: true
target_traces_per_second: 200
errors_per_second: 10
เมื่อ sampling rule ตั้ง sample_rate 0 (ไม่มี discard) fake trace-agent ยังรับ root span 40 ตัวที่มี _sampling_priority_v1 = -1 ทุกตัว สิ่งนี้แสดงอะไร?
rule ที่ sample_rate 1.0 กับ DD_TRACE_RATE_LIMIT=5 ปล่อย 60 trace พร้อมกัน 5 ตัวแรกมี priority 2 และ _dd.limit_psr=1; ที่เหลือมี priority -1 และ _dd.limit_psr = 0.833, 0.714, ... _dd.limit_psr คืออะไร?
คุณ POST priority-1 720 trace ที่ 60 ต่อวินาทีไป Agent จริงที่ target_traces_per_second เป็น default 10 กี่ trace ไปถึง intake (fake)?
ข้อใดเกี่ยวกับ DD_TRACE_SAMPLING_RULES ที่มี "max_per_second" ถูกพิสูจน์ในบทนี้?
เมื่อตั้ง DD_APM_PROBABILISTIC_SAMPLER_ENABLED=true และ 20% trace priority-1 จำนวน 1,000 ตัวให้ chunk ที่ forward 210 ตัวพร้อม _dd.p.dm=-9 อีกข้อสังเกตอะไรมาพร้อมกัน?
ข้อใดไม่มีหลักฐานบน span หรือใน payload จึงพิสูจน์ในบทนี้ได้ด้วยการอ้าง docs เท่านั้น?