How to trace GreptimeDB

GreptimeDB 使用 Rust 的 tracing 框架进行代码埋点,tracing 的具体原理和使用方法参见 tracing 的官方文档。

通过将 trace_id 等信息在整个分布式数据链路上透传,使得我们能够记录整个分布式链路的函数调用链,知道每个被追踪函数的调用时间等相关信息,从而对整个系统进行诊断。

在 RPC 中定义 tracing 上下文

因为 tracing 框架并没有原生支持分布式追踪,我们需要手动将 trace_id 等信息在 RPC 消息中传递,从而正确的识别函数的调用关系。我们使用基于 w3c 的标准 将相关信息编码为 tracing_context ,将消息附在 RPC 的 header 中。主要定义在:

  • frontenddatanode 交互:tracing_context 定义在 RegionRequestHeader
  • frontendmetasrv 交互:tracing_context 定义在 RequestHeader
  • Client 与 frontend 交互:tracing_context 定义在 RequestHeader

在 RPC 调用中传递 tracing 上下文

我们构建了一个 TracingContext 结构体,封装了与 tracing 上下文有关的操作。相关代码

GreptimeDB 在使用 TracingContext::from_current_span() 获取当前 tracing 上下文,使用 to_w3c() 方法将 tracing 上下文编码为符合 w3c 的格式,并将其附在 RPC 消息中,从而使 tracing 上下文正确的在分布式组件之中传递。

下面的例子说明了如何获取当前 tracing 上下文,并在构造 RPC 消息时正确传递参数,从而使 tracing 上下文正确的在分布式组件之中传递。

rust

  1. let request = RegionRequest {
  2. header: Some(RegionRequestHeader {
  3. tracing_context: TracingContext::from_current_span().to_w3c(),
  4. ..Default::default()
  5. }),
  6. body: Some(region_request::Body::Alter(request)),
  7. };

在 RPC 消息的接收方,需要将 tracing 上下文正确解码,并且使用该上下文构建第一个 span 对函数调用进行追踪。比如下面的代码就将接收到的 RPC 消息中的 tracing_context 使用 TracingContext::from_w3c 方法正确解码。并使用 attach 方法将新建的 info_span!("RegionServer::handle_read") 附上了上下文消息,从而能够跨分布式组件对调用进行追踪。

rust

  1. ...
  2. let tracing_context = request
  3. .header
  4. .as_ref()
  5. .map(|h| TracingContext::from_w3c(&h.tracing_context))
  6. .unwrap_or_default();
  7. let result = self
  8. .handle_read(request)
  9. .trace(tracing_context.attach(info_span!("RegionServer::handle_read")))
  10. .await?;
  11. ...

使用 tracing::instrument 对监测代码进行埋点

我们使用 tracing 提供的 instrument 宏对代码进行埋点,只要将 instrument 宏标记在需要进行埋点的函数即可。 instrument 宏会每次将函数调用的参数以 Debug 的形式打印到 span 中。对于没有实现 Debug trait 的参数,或者结构体过大、参数过多,最后导致 span 过大,希望避免这些情况就需要使用 skip_all,跳过所有的参数打印。

rust

  1. #[tracing::instrument(skip_all)]
  2. async fn instrument_function(....) {
  3. ...
  4. }

跨越 runtime 的代码埋点

Rust 的 tracing 库会自动处理埋点函数间的嵌套关系,但如果某个函数的调用跨越 runtime 的话,tracing 不能自动对这类调用进行追踪,我们需要手动跨越 runtime 去传递上下文。

rust

  1. let tracing_context = TracingContext::from_current_span();
  2. let handle = runtime.spawn(async move {
  3. handler
  4. .handle(query)
  5. .trace(tracing_context.attach(info_span!("xxxxx")))
  6. ...
  7. });

比如上面这段代码需要跨越 runtime 去进行 tracing,我们先通过 TracingContext::from_current_span() 获取当前 tracing 上下文,通过在另外一个 runtime 里新建一个 span,并将 span 附着在当前上下文中,我们就完成了跨越 runtime 的代码埋点,正确追踪到了调用链。