线上出了个PGVector的Bug,我排查了一夜。

本文是我的排查记录,包括问题现象、排查过程、根本原因、解决方法,以及经验教训。

一、问题出现

1. 线上报警

那天晚上,线上报警了。

  • 向量搜索服务异常
  • 查询超时
  • 错误率飙升
  • 用户投诉
  • 我被电话叫醒了

线上问题,总是在半夜来。

2. 初步判断

初步判断:

  • 是PGVector的问题
  • 不是应用层的问题
  • 不是网络的问题
  • 是数据库层面的问题
  • 需要尽快排查

PGVector,是核心。

3. 开始排查

我开始排查。

  • 登录数据库
  • 看日志
  • 看监控
  • 看慢查询
  • 一步步来

排查,需要冷静。

二、排查过程

1. 第一步:看慢查询

第一步:看慢查询。

-- 查看慢查询
SELECT * FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;
  • 发现向量查询很慢
  • 平时几十毫秒
  • 现在几秒
  • 找到了线索

慢查询,是第一线索。

2. 第二步:看索引

第二步:看索引。

-- 查看索引
SELECT * FROM pg_indexes WHERE tablename = 'documents';
  • 索引存在
  • 但没生效
  • 为什么?
  • 继续排查

索引,是关键。

3. 第三步:看执行计划

第三步:看执行计划。

-- 查看执行计划
EXPLAIN ANALYZE
SELECT * FROM documents
ORDER BY embedding <=> '[1,2,3]'
LIMIT 10;
  • 发现没有用索引
  • 全表扫描
  • 为什么索引失效?
  • 继续排查

执行计划,是真相。

4. 第四步:看数据量

第四步:看数据量。

  • 数据量增长很快
  • 从几十万到几百万
  • 索引可能有问题
  • 或者参数不对
  • 继续排查

数据量,是诱因。

5. 第五步:复现问题

第五步:复现问题。

  • 测试环境复现
  • 大数据量下
  • 确实查询慢
  • 索引失效
  • 确认了问题

复现,是修复的前提。

三、根本原因

1. 原因一:索引参数不对

第一个原因:索引参数不对。

  • ivfflat索引
  • lists参数设置不合理
  • 数据量增长后
  • 索引效率下降
  • 查询变慢

索引参数,是核心问题。

2. 原因二:没有重建索引

第二个原因:没有重建索引。

  • 数据量增长后
  • 索引需要重建
  • 但没有重建
  • 索引效率下降
  • 查询变慢

重建索引,是必要的。

3. 原因三:work_mem太小

第三个原因:work_mem太小。

  • 向量查询需要内存
  • work_mem设置太小
  • 导致排序慢
  • 查询超时
  • 是参数问题

work_mem,是参数问题。

4. 原因四:没有分区

第四个原因:没有分区。

  • 单表数据量太大
  • 没有分区
  • 查询慢
  • 维护难
  • 是架构问题

分区,是架构问题。

5. 原因五:监控不完善

第五个原因:监控不完善。

  • 没有向量查询监控
  • 没有索引监控
  • 没有数据量监控
  • 问题发现晚
  • 被动应对

监控,是眼睛。

四、解决方法

1. 临时方案:优化参数

临时方案:优化参数。

-- 调整work_mem
SET work_mem = '256MB';
-- 调整维护内存
SET maintenance_work_mem = '1GB';
  • 先调参数
  • 缓解问题
  • 恢复服务
  • 再慢慢优化
  • 先止血

止血,是第一步。

2. 重建索引

重建索引:

-- 重建索引
REINDEX INDEX documents_embedding_idx;
-- 或者重建ivfflat索引
DROP INDEX documents_embedding_idx;
CREATE INDEX documents_embedding_idx ON documents USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);
  • 重建索引
  • 调整lists参数
  • 根据数据量调整
  • 提升查询效率

重建索引,是核心修复。

3. 优化查询

优化查询:

-- 用索引扫描
SET enable_seqscan = off;
-- 调整probes
SET ivfflat.probes = 10;
  • 优化查询参数
  • 强制用索引
  • 调整probes
  • 平衡精度和速度

查询优化,是辅助。

4. 分区表

分区表:

-- 创建分区表
CREATE TABLE documents (
    id bigserial,
    embedding vector(1536)
) PARTITION BY RANGE (id);

-- 创建分区
CREATE TABLE documents_0 PARTITION OF documents FOR VALUES FROM (0) TO (1000000);
CREATE TABLE documents_1 PARTITION OF documents FOR VALUES FROM (1000000) TO (2000000);
  • 大表分区
  • 提升查询性能
  • 便于维护
  • 是长期方案

分区,是长期方案。

5. 完善监控

完善监控:

  • 向量查询耗时监控
  • 索引使用率监控
  • 数据量监控
  • 慢查询告警
  • 及时发现问题

监控,是保障。

五、验证效果

1. 测试

测试:

  • 测试环境测试
  • 大数据量下
  • 查询从几秒降到几十毫秒
  • 索引用上了
  • 性能提升明显

测试,验证修复。

2. 上线

上线:

  • 灰度发布
  • 观察监控
  • 没有问题
  • 全量发布
  • 服务恢复

上线,完成修复。

3. 观察

观察:

  • 观察几天
  • 查询稳定
  • 没有超时
  • 错误率正常
  • 问题解决

观察,确保稳定。

六、经验教训

1. 教训一:向量数据库要调优

第一个教训:向量数据库要调优。

  • PGVector不是装了就好
  • 需要调优
  • 需要根据数据量调整参数
  • 需要重建索引
  • 不能忽视

调优,是必要的。

2. 教训二:索引参数很重要

第二个教训:索引参数很重要。

  • ivfflat的lists参数
  • 要根据数据量调整
  • 不是一成不变
  • 数据量增长后要调整
  • 很关键

索引参数,是关键。

3. 教训三:大数据量要分区

第三个教训:大数据量要分区。

  • 单表不要太大
  • 要分区
  • 提升性能
  • 便于维护
  • 是架构设计

分区,是架构。

4. 教训四:监控要完善

第四个教训:监控要完善。

  • 向量查询要监控
  • 索引要监控
  • 数据量要监控
  • 早发现早处理
  • 不要等用户投诉

监控,是眼睛。

5. 教训五:要有应急预案

第五个教训:要有应急预案。

  • 出问题知道怎么办
  • 先止血再修复
  • 快速恢复
  • 减少影响
  • 是保障

预案,是保障。

七、PGVector最佳实践

1. 索引选择

最佳实践一:索引选择。

  • ivfflat:适合大数据量,查询快
  • hnsw:适合高精度,更新快
  • 根据场景选择
  • 没有最好只有最合适

索引选择,是基础。

2. 参数调优

最佳实践二:参数调优。

  • lists:根据数据量,一般数据量/1000
  • probes:查询时设置,平衡精度和速度
  • work_mem:足够大,避免磁盘排序
  • 定期调整

参数调优,是核心。

3. 数据维护

最佳实践三:数据维护。

  • 定期重建索引
  • 定期VACUUM
  • 监控数据量
  • 及时分区
  • 保持健康

数据维护,是长期工作。

4. 查询优化

最佳实践四:查询优化。

  • 用LIMIT
  • 用索引
  • 调整probes
  • 避免全表扫描
  • 持续优化

查询优化,是持续的。

5. 监控告警

最佳实践五:监控告警。

  • 查询耗时监控
  • 索引使用率监控
  • 数据量监控
  • 慢查询告警
  • 及时处理

监控告警,是保障。

八、写在最后

线上出了个PGVector的Bug,我排查了一夜。

问题是索引参数不对、没有重建索引、work_mem太小、没有分区、监控不完善。排查过程:看慢查询、看索引、看执行计划、看数据量、复现问题。解决方法:优化参数、重建索引、优化查询、分区表、完善监控。

2023年了,向量数据库越来越流行,PGVector是其中的代表。但不是装了就好,需要调优,需要维护,需要监控。出问题不可怕,可怕的是不从问题中学习。

最后,用一句话总结:"PGVector,不是装了就好,需要调优,需要维护,需要监控。出问题不可怕,可怕的是不总结。每次排查都是一次成长。"

愿你的线上服务,永远稳定,永远不半夜报警。