概念定义
尾部采样是一种分布式链路采样策略:它先缓存一条链路的全部 span,待链路判断为完整后,再依据整体特征——延迟、错误状态等属性——决定是否保留。由于决策看到的是完整链路,它能在仅采样一小部分快速成功请求的同时,保留所有慢请求与失败请求;这与必须在结果未知前就下注的头部采样不同。
为什么重要
失败与长尾延迟相对于快速成功请求的稳定流是稀少的。统一的百分之一采样很难包含正在排查的那起故障。尾部采样把这个比例倒过来:保留全部错误与慢链路,只保留其余的一小部分,从而在有价值处数据密集、在无价值处成本低廉。
工程实践
缓冲时长要足以覆盖最慢的链路,又不能长到内存无界增长,并把一条链路的全部 span 路由到同一个决策点——集中式 collector 或按 trace id 分片。组合策略:保留错误与高延迟、对成功按比例采样、始终保留带特定属性的链路。同时输出采样率计数器,便于后端正确加权统计。
代码示例
from dataclasses import dataclass, field
import random
@dataclass
class Span:
name: str
duration_ms: float
error: bool = False
@dataclass
class Policy:
slow_ms: float = 500.0
keep_errors: bool = True,
fallback_rate: float = 0.01
def decide(spans: list['Span'], policy: Policy, rng: random.Random | None = None) -> tuple[bool, str]:
rng = rng or random.Random()
if not spans:
return False, 'empty'
if policy.keep_errors and any(s.error for s in spans):
return True, 'error'
slowest = max(s.duration_ms for s in spans)
if slowest >= policy.slow_ms:
return True, 'slow'
if rng.random() < policy.fallback_rate:
return True, 'sampled'
return False, 'dropped'
export class LogSampler {
constructor({ rate = 0.01, alwaysKeep = () => false } = {}) {
this.rate = rate;
this.alwaysKeep = alwaysKeep;
this.seen = new Set();
}
shouldEmit(signature) {
if (this.alwaysKeep(signature)) return true;
if (!this.seen.has(signature)) {
this.seen.add(signature);
return true;
}
return Math.random() < this.rate;
}
emit(signature, write) {
if (!this.shouldEmit(signature)) return false;
write({ signature, sampledRate: this.rate });
return true;
}
}