AI 对话慢到怀疑人生?可能不是代码的问题

5 阅读6分钟

这是我在开发 My-Notion 项目时踩的另一个坑——迁移到Qdrant向量数据库后,本地 AI 对话慢得离谱,我花了好久优化代码,结果毫无效果。最后发现根本不是代码的问题,而是网络的问题。

问题现象

项目里有个 RAG 对话功能:用户提问 → 向量检索文档 → LLM 生成回答。本地开发时,每次对话要等 5-8 秒才开始出字,体验非常差。

我的第一反应是:代码写得有问题,性能需要优化。

错误的排查方向

于是我开始了一系列"优化":

1. 怀疑 RAG 检索太慢

以为是 Qdrant 向量检索耗时,于是加了日志:

const start = Date.now();
const results = await vectorStore.similaritySearch(query, k, minScore);
console.log(`[RAG] 检索耗时: ${Date.now() - start}ms`);

结果检索本身只要 200-400ms,不是瓶颈。

2. 怀疑 Embeddings 计算太慢

以为是文本向量化耗时,又加了日志:

const embStart = Date.now();
const embedding = await openai.embeddings.create({
  model: EMB_MODEL,
  input: query,
});
console.log(`[Embeddings] 耗时: ${Date.now() - embStart}ms`);

结果 Embeddings 要 1-2 秒,有点慢但也不是 5-8 秒的量级。

3. 怀疑 LLM 流式响应有延迟

以为是流式输出卡顿,检查了 SSE/NDJSON 的解析逻辑,甚至重写了 ReadableStream 的处理方式,还是没改善。

4. 怀疑 Convex 查询慢

以为是数据库查询拖后腿,检查了 Convex 的函数耗时面板,全都在 100ms 以内。

折腾了好几天,每个环节单独看都不慢,但整体就是慢。

真正的原因

直到有一天,我把项目部署到 Vercel 上测试,发现线上对话飞快——1-2 秒就开始出字,体验丝滑。

同样的代码,本地慢、线上快,问题只能出在网络上。

画一下请求链路就清楚了:

本地开发环境(中国)

  用户提问
    → 本地 Next.js (localhost:3000)
      → 通义千问 API (中国,快 ✅)
      → Qdrant Cloud (美国,慢 ❌)    ← 跨太平洋往返 ~300-500ms/次
      → Convex Cloud (美国,慢 ❌)    ← 跨太平洋往返 ~200-300ms/次
线上环境 (Vercel,美国)

  用户提问
    → Vercel Serverless (美国)
      → 通义千问 API (中国,有延迟但只一次)
      → Qdrant Cloud (美国,快 ✅)    ← 同机房级别,~10-30ms
      → Convex Cloud (美国,快 ✅)    ← 同区域,~20-50ms

一次 RAG 对话的完整链路中,至少有 3-4 次跨太平洋的网络请求:

  1. Embeddings 计算:本地 → 通义千问 API(国内,快)
  2. 向量检索:本地 → Qdrant Cloud(美国,慢)
  3. 文档内容获取:本地 → Convex Cloud(美国,慢)
  4. LLM 生成:本地 → 通义千问 API(国内,快)

其中第 2、3 步每次往返都要 300-500ms,3-4 次加起来就是 1-2 秒的纯网络延迟。再加上 LLM 本身的生成时间,体感就是 5-8 秒。

而线上环境,Vercel、Qdrant、Convex 都在美国,服务间通信延迟只有几十毫秒,所以飞快。

为什么会这样

原因很简单——我偷懒了。

Qdrant 官方提供了 Docker 本地部署方案,一条命令就能跑起来:

docker run -p 6333:6333 qdrant/qdrant

但为了省事,我直接用了 Qdrant Cloud 的免费集群,没有在本地搭 Qdrant。Convex 也是同理,直接用的云端实例。

本地开发时,所有请求都打到了美国的服务器,跨太平洋的物理距离(约 12000 公里)决定了光速延迟就不可能快:

理论最低延迟 = 2 × 距离 / 光速
             = 2 × 12000km / 300000km/s
             ≈ 80ms(单次往返,理想情况)

实际延迟 = 200-500ms(经过多层路由和 CDN 节点)

一次 RAG 对话涉及 3-4 次这样的往返,纯网络开销就 1-2 秒。

怎么解决

方案 1:本地部署 Qdrant(推荐)

最直接的方案——把 Qdrant 跑在本地,消除跨洋延迟:

# Docker 一键启动
docker run -p 6333:6333 \
  -v ./qdrant_storage:/qdrant/storage \
  qdrant/qdrant

然后在 .env.local 中切换到本地地址:

- NEXT_PUBLIC_QDRANT_URL=https://your-cluster.us-east4.gcp.cloud.qdrant.io
+ NEXT_PUBLIC_QDRANT_URL=http://localhost:6333

本地 Qdrant 的向量检索延迟从 300-500ms 降到 5-15ms,体感提升非常明显。

方案 2:使用环境变量区分本地和线上

在代码中根据环境自动切换:

const QDRANT_URL = process.env.NODE_ENV === "development"
  ? "http://localhost:6333"       // 本地开发用 Docker Qdrant
  : process.env.NEXT_PUBLIC_QDRANT_URL;  // 线上用 Qdrant Cloud

方案 3:给本地开发加网络延迟感知

如果暂时不想搭本地 Qdrant,至少要在开发时意识到网络延迟的存在,避免像我一样把时间浪费在"优化代码"上。

一个简单的办法是给关键请求加耗时日志,区分"网络耗时"和"处理耗时":

async function fetchWithTiming(url: string, options?: RequestInit) {
  const start = Date.now();
  const response = await fetch(url, options);
  const elapsed = Date.now() - start;
  console.log(`[Network] ${url} - ${elapsed}ms`);
  return response;
}

看到网络耗时占了大头,就不会盲目优化代码逻辑了。

排查网络问题的方法论

这次踩坑让我总结了一套排查"慢"问题的思路:

1. 先区分是"网络慢"还是"计算慢"

加耗时日志,把每个环节的耗时拆开看:

[RAG] Embeddings: 1200ms    ← 网络 + 计算
[RAG] 向量检索: 450ms       ← 网络 + 查询
[RAG] 文档获取: 280ms       ← 网络 + 查询
[RAG] LLM 生成: 3000ms      ← 网络 + 生成
[RAG] 总耗时: 4930ms

如果某个环节的耗时远超预期,大概率是网络问题而不是代码问题。

2. 对比本地和线上的表现

同样的代码,本地慢、线上快 → 网络问题 本地慢、线上也慢 → 代码问题

这是最简单有效的判断方式。

3. 检查服务器的物理位置

你的位置服务位置预期延迟
中国中国10-50ms
中国美西150-300ms
中国美东250-500ms
中国欧洲200-400ms
美国美国10-50ms

如果你的服务部署在不同区域,延迟就会叠加。

4. 用 ping 和 traceroute 验证

# 测试到 Qdrant 服务器的延迟
ping your-cluster.qdrant.io

# 查看网络路径
traceroute your-cluster.qdrant.io

如果 ping 延迟在 200ms+,基本就是跨洋链路了。

总结

这次踩坑最大的教训是:遇到性能问题,先排查网络,再优化代码。

我花了很久时间优化代码逻辑,结果毫无效果。而部署到线上后,同样的代码跑得飞快——因为线上环境的网络延迟只有本地的十分之一。

对于使用海外云服务的开发者(在国内开发,服务部署在 AWS/GCP 等海外区域),这个问题尤其常见。建议:

  1. 本地开发尽量用本地服务 — Qdrant、Redis、PostgreSQL 都有 Docker 一键部署方案
  2. 加耗时日志区分网络和计算 — 不要盲目优化代码
  3. 本地慢但线上快 → 优先怀疑网络 — 别像我一样走弯路
  4. CI/CD 环境和线上保持一致 — Vercel + Qdrant Cloud 都在美国,CI 构建时的网络延迟和线上一致,不会出现"本地没问题、线上有问题"的反转

本文基于 My-Notion 项目的真实踩坑经历撰写,项目是一个 AI 原生的个人版 Notion,欢迎 Star ⭐