博客

用 SQL 排查支付故障,OpenTelemetry 接入 Databend Cloud 实战

avatarJeremy11月 4, 2026查看 Markdown
用 SQL 排查支付故障,OpenTelemetry 接入 Databend Cloud 实战

排查一次支付故障,找到报错日志只是开始。还要沿着调用链看时间花在了哪里,再确认失败的请求涉及哪些订单。如果日志存在 Elasticsearch 或 Loki,链路存在 Jaeger 或 Tempo,指标由 Prometheus 管理,这些信息就需要在不同系统之间核对。进一步分析业务影响时,数据工程师还得把可观测数据与订单数据关联起来,增加了数据导出、字段整理和拼表的工作。

OpenTelemetry(下文简称 OTel)为日志、链路和指标提供了统一的采集与传输规范。应用通过 SDK 或自动埋点产生数据,经由 OTLP 协议发送给 Collector,再由 Collector 处理和导出。数据最终存在哪里、怎样跨信号查询,以及如何关联业务表,仍需要后端来解决。

湖仓和分析数据库也在沿着这个方向接入可观测数据。对已经使用 Databend 做数据分析的团队来说,把 OTel 数据接入现有湖仓,就能沿用熟悉的 SQL 查询方式,并在业务表已入仓的前提下,直接分析故障与订单之间的关系。

Databend 提供的 OpenTelemetry Collector exporter 负责这一步接入,将 logs、traces、metrics 写成三张普通表。下面用 Databend Cloud 上的一次实际演示,看看这条路径如何跑通。本文截图、动图和视频均来自这次演示。应用与订单数据由模拟程序生成,文中的查询结果来自实际运行。


接入原理与数据结构

数据从应用到查询,经过以下三个步骤:

  1. 应用使用 OTel SDK 或自动埋点,通过 OTLP 发给 Collector;

  2. Collector 里的

    databend
    exporter 把每批数据编码成 NDJSON,通过 Databend 原生的 HTTP Streaming Load 接口写入;

  3. 数据落在

    otel_logs
    、
    otel_traces
    、
    otel_metrics
    三张表里,在 Worksheet、BendSQL、BI 工具或 Grafana 里用 SQL 查询。

exporter 在写入方式和数据保留上做了以下设计:

**通过 Databend 原生接口写入。**exporter 直接调用 Databend

/v1/streaming_load
,每批数据上传一次,无需拼接 SQL 字面量或引入额外驱动。

**支持三类信号和五种指标类型。**gauge、sum、histogram、exponential histogram、summary 均可写入,各类型中的 exemplar、bucket 边界、min/max 和分位数等信息也会保留。

数据原样保留,不做有损压平。 每行是一条日志、一个 span 或一个指标数据点。常用的过滤字段(时间、服务名、trace_id、span_id、名称、指标类型)单独成列,完整的 OTLP 对象放在

VARIANT
类型的
record
列里。属性类型、span 的 events 和 links、状态以及 dropped 计数都会保留。

时间戳精确到纳秒。 Databend 的 TIMESTAMP 使用微秒精度,因此这里用

UInt64
保存原始纳秒值,64 位整数也不会因经过 float64 转换而丢失精度。

用 Collector 标准能力做可靠性。 发送队列(可配持久化存储)、重试退避、超时、TLS、认证扩展都直接复用 Collector 的 exporterhelper 和 confighttp,配置方式和其他 exporter 一样。

支持自动建表,也可按最小权限配置。 默认

create_schema: true
,启动时自动建库建表;生产环境可以让管理员用仓库里的
schema.sql
预先建表,exporter 账号只给 INSERT 权限,设置
create_schema: false
。


跑通 Demo,将三类数据写入 Databend Cloud

演示代码、完整配置和复现步骤见 Demo 说明。下面按连接、编译、启动和发送数据的顺序操作,也可以先看这段约 50 秒的终端演示:

第 1 步:获取连接信息

登录 app.databend.com,点首页右上角的 Connect。exporter 需要的是 Host(形如

<tenant>.gw.<region>.default.databend.com
)和 Warehouse。

截图中的租户信息已打码。

建议给 exporter 单独建一个用户,不要用管理员账号。在 Worksheet 中执行以下 SQL:

CREATE ROLE otel_exporter_demo_role;
-- 只有 create_schema: true 时需要
GRANT CREATE DATABASE ON *.* TO ROLE otel_exporter_demo_role;
CREATE USER otel_exporter_demo IDENTIFIED BY '<强密码>'
WITH DEFAULT_ROLE = 'otel_exporter_demo_role';
GRANT ROLE otel_exporter_demo_role TO otel_exporter_demo;

第 2 步:编译包含 Databend exporter 的 Collector

本次演示使用的 exporter 尚未纳入 OpenTelemetry Collector Contrib 官方发行版,需要通过 OpenTelemetry Collector Builder(ocb)构建自定义 Collector。仓库已提供 builder 配置:

git clone https://github.com/databendlabs/opentelemetry-exporter-databend
cd opentelemetry-exporter-databend
go run go.opentelemetry.io/collector/cmd/builder@v0.164.0 \
--config examples/builder-config.yaml
## 产物:./_build/otelcol-databend

需要 Go 1.26 或更高版本。如果你已经有自己的 Collector 发行版,把 exporter 加到 builder 配置的

exporters
里即可。

第 3 步:配置并启动 Collector

以下列出 exporter 和数据管道的主要配置,完整配置见文档 https://github.com/databendlabs/opentelemetry-exporter-databend/tree/main/examples:

exporters:
databend:
endpoint: ${env:DATABEND_ENDPOINT} # https://<Host>
username: ${env:DATABEND_USERNAME}
password: ${env:DATABEND_PASSWORD}
headers:
X-Databend-Warehouse: ${env:DATABEND_WAREHOUSE}
database: otel_demo
create_schema: true
sending_queue:
enabled: true
retry_on_failure:
enabled: true

service:
pipelines:
logs: { receivers: [otlp], processors: [memory_limiter, batch], exporters: [databend] }
traces: { receivers: [otlp], processors: [memory_limiter, batch], exporters: [databend] }
metrics: { receivers: [otlp], processors: [memory_limiter, batch], exporters: [databend] }

凭据通过环境变量注入,无需写入配置文件:

export DATABEND_ENDPOINT=https://<tenant>.gw.aws-us-east-2.default.databend.com
export DATABEND_USERNAME=otel_exporter_demo
export DATABEND_PASSWORD='<强密码>'
export DATABEND_WAREHOUSE=default
./_build/otelcol-databend --config collector-cloud.yaml

本次演示中,出现

Everything is ready. Begin running and processing data.
后,Collector 已启动,exporter 已连接 Databend Cloud,并完成三张表的创建。

第 4 步:发送数据并确认入库

可以使用支持 OTLP 的程序发送数据。本地接收地址为

127.0.0.1:4317
(gRPC)或
127.0.0.1:4318
(HTTP)。已接入 OTel 的服务可将
OTEL_EXPORTER_OTLP_ENDPOINT
配置为对应协议的 Collector 地址。

演示使用一个模拟电商应用,一次结账请求依次经过 frontend、checkout、inventory、payment 四个服务。大约 8% 的请求会在支付环节失败(

card declined
),大约 10% 的支付耗时会超过 400 ms。它用官方 OTel Go SDK 同时上报 traces、logs、metrics。

回到 Worksheet,可以看到三张表中已经写入数据:

这次一共模拟了 2,100 次结账,写入 6,300 条日志、13,628 个 span、693 个指标数据点。本次运行中,Collector 未出现 error 或 warn 日志。


用 SQL 排查支付故障,分析订单影响

数据写入后,就可以在 Worksheet 中查询。先从报错日志入手,再通过

trace_id
还原调用链,下面的动图展示了这个过程:

从日志中定位支付错误

SELECT to_timestamp((time_unix_nano // 1000)::Int64) AS ts, service_name,
record:body:stringValue::STRING AS message,
record:attributes[0]:value:stringValue::STRING AS error,
trace_id
FROM otel_demo.otel_logs
WHERE record:severityText::STRING = 'ERROR'
ORDER BY time_unix_nano DESC LIMIT 20;

报错集中在 payment 服务,原因是

card declined: insufficient funds
。本例中,OTel SDK 将当前 span 的上下文写入日志,因此可以通过日志中的
trace_id
继续查找对应链路。

用 trace_id 还原请求调用链

从日志中选取一个

trace_id
,查询该请求的所有 span,再用
repeat()
绘制文字版瀑布图:

SELECT service_name, name AS span_name, offset_ms, duration_ms,
repeat('·', floor(offset_ms / 40)::INT)
|| repeat('█', greatest(1, ceil(duration_ms / 40))::INT) AS timeline,
status
FROM (SELECT service_name, name, time_unix_nano,
record:status:message::STRING AS status,
round((time_unix_nano - min(time_unix_nano) OVER ()) / 1e6, 1) AS offset_ms,
round((record:endTimeUnixNano::UInt64
- record:startTimeUnixNano::UInt64) / 1e6, 1) AS duration_ms
FROM otel_demo.otel_traces WHERE trace_id = '<trace_id>')
ORDER BY time_unix_nano;

这次请求总耗时 482 ms,库存检查用了 20 ms,

PaymentService/Charge
用了 462 ms,并最终返回失败。耗时主要集中在支付环节。

统计各服务的 P95 延迟与错误数

可以直接根据 span 计算延迟和错误数,无需为这次查询预先定义指标:

SELECT service_name, name AS span_name, count(*) AS spans,
round(avg(dur_ms), 1) AS avg_ms,
round(quantile_cont(0.95)(dur_ms), 1) AS p95_ms,
sum(if(record:status:code::INT = 2, 1, 0)) AS errors
FROM (SELECT *, (record:endTimeUnixNano::UInt64
- record:startTimeUnixNano::UInt64) / 1e6 AS dur_ms
FROM otel_demo.otel_traces)
GROUP BY service_name, span_name
ORDER BY p95_ms DESC;

结账接口的 P95 延迟为 450.9 ms,支付环节的 P95 为 430.2 ms,inventory 的 P95 仅为 37.4 ms。

关联日志与链路,统计失败订单金额

接下来可以把技术故障与订单影响放在一起分析。本例的日志记录了订单金额,span 记录了处理耗时,通过

trace_id
和
span_id
即可关联:

SELECT l.service_name,
l.record:attributes[0]:value:stringValue::STRING AS error,
count(*) AS failed_orders,
round(avg((t.record:endTimeUnixNano::UInt64
- t.record:startTimeUnixNano::UInt64) / 1e6), 1) AS avg_charge_ms,
round(sum(l.record:attributes[1]:value:doubleValue::DOUBLE), 2) AS lost_revenue_usd
FROM otel_demo.otel_logs l
JOIN otel_demo.otel_traces t ON l.trace_id = t.trace_id AND l.span_id = t.span_id
WHERE l.record:severityText::STRING = 'ERROR'
GROUP BY ALL;

本次模拟中,177 笔订单因扣款失败未成交,涉及金额 36,058.96 美元。日志与链路存入 Databend Cloud 后,一次 JOIN 就能将失败原因、订单金额和支付耗时放在同一份结果中。如果订单表也在 Databend Cloud 中,还可以继续按用户、渠道或商品分析影响。

查看直方图指标与关联链路

指标表的每一行对应一个数据点,保留了直方图的 bucket 计数、边界和 min/max。exemplar 中的

trace_id
还可以用于查询对应的请求链路,进一步查看该样本的执行过程。

将查询结果转为请求量趋势图

Worksheet 支持将查询结果切换为图表视图。下图展示了结账接口按 10 秒窗口统计的请求量:


把可观测数据纳入湖仓,能带来什么?

Databend 是云原生湖仓,基于对象存储保存数据,将计算与存储分离,并提供 SQL 分析能力。结构化的业务表和半结构化的日志、链路可以在同一平台中查询。Databend Cloud 提供托管服务,让团队按工作负载配置计算资源。这样的架构为可观测数据的长期保留和关联分析提供了基础。

结合上面的 Demo,用户往往更看重的是数据写入之后能做什么。排查支付故障时,我们已经能在同一条查询中看到错误原因、处理耗时和失败订单金额。如果业务数据也在 Databend Cloud 中,后续就可以继续分析故障影响了哪些用户、哪些渠道,而无需先把日志导出到另一个分析系统。

对数据工程师来说,可观测数据可以和业务数据一起纳入日常分析。要让这样的分析方便开展,既需要统一的查询方式,也需要考虑数据保留、字段变化和现有工具的接入。Databend Cloud 在这些方面提供了相应支持:

三类信号使用同一套 SQL。 日志、链路和指标写入同一引擎后,可以通过

trace_id
、
span_id
或服务名等字段查询和关联,减少跨系统核对信息的工作。上面的日志与链路 JOIN 就是一个直接例子。

与已有业务表直接关联。 当订单、用户和账单数据已经在 Databend Cloud 中,并具备可用的关联字段时,可以直接用 SQL 分析业务影响,无需为一次排查额外导出和搬运数据。分析也可以从单次故障延伸到一段时间内的失败分布。

按保留周期和查询负载分别配置资源。 对象存储承载历史数据,计算资源按写入和查询需求配置。团队可以根据复盘、容量规划或审计的需要决定日志与链路的保留时长,避免仅因历史数据增加就同步扩大计算规模。实际成本仍取决于数据量、保留周期和读写负载。

保留动态属性,按分析需要提取字段。 OTel 属性会随服务和版本变化,

VARIANT
列可以保存这些半结构化内容。查询时通过
record:attributes
、
record:status:code
等路径取值,新增属性通常无需修改表结构,也为后续分析保留了原始信息。

沿用已有的 OTel 采集方式。 对已经通过 OTLP 向 Collector 发送数据的应用,接入 Databend Cloud 主要是在 Collector 中增加 exporter 和管道配置。需要同时写入其他后端时,也可以配置多个 exporter,保留已有的监控流程。

继续使用熟悉的分析工具。 数据以普通表的形式存储,可以通过支持 Databend 的 BI 工具查询。Databend 的 Grafana 数据源插件也支持用 SQL 配置看板和告警。需要由 AI Agent 辅助查询时,同样可以复用这些表和 SQL 接口。


组件状态与使用边界

本文演示使用的 exporter 是开发阶段的独立组件,尚未进入 OpenTelemetry Collector Contrib 官方发行版。团队计划在收集社区反馈后推动捐赠,使用时需了解以下边界:

  • 投递语义是 at-least-once:如果写入已经提交但连接在返回前断了,重试可能产生重复行,目前没有去重;

  • 队列溢出、重试耗尽或进程异常退出(未开持久化队列时)可能丢数据,生产环境建议开启持久化队列,并配置 batch 和 memory_limiter;

  • 表结构变更由运维负责,

    CREATE TABLE IF NOT EXISTS
    不会迁移已有表;没有自动 TTL;

  • 暂不支持 Profiles 信号;

本次演示对应版本的单元测试包含 race 检测,覆盖率为 90.4%;CI 会启动真实的 Databend 实例运行端到端测试,校验五种指标类型以及纳秒时间戳和整数精度。本文 Demo 已在 Databend Cloud(v1.2.952)上完整运行。

获取代码与演示资料

  • 代码和文档:github.com/databendlabs/opentelemetry-exporter-databend

  • 本文的演示代码、全部 SQL 和 Collector 配置位于仓库的

    demo/
    目录,欢迎试用,并通过 Issue 或 PR 反馈问题。如果已经在生产环境接入,也欢迎分享数据规模和使用场景,帮助我们确定后续的优化方向。

分享本篇文章

订阅我们的新闻简报

及时了解功能发布、产品规划、支持服务和云服务的最新信息!