PostgreSQL码农集散地

别烧钱了, 大模型语义缓存产品来了

本期播客

别烧钱了, 你和大模型直接需要一层语义缓存, 它来了

别再傻傻匹配字符串了!PostgreSQL语义缓存一招让LLM调用成本狂降70%

我的第一反应是, 信你个鬼, 不过看完下面的介绍, 我知道了, 确实非常适合企业内使用, 当企业内、客服系统冲刺着大量语义相似的提问时. 效果很好. 

说白了就是向量数据库的另一种应用, 在讲上下文发给大模型之前先搜一下有没有语义极度相似的, 也许可以给你省一笔钱. 

本质上和确定性搜索场景的缓存用法类似, 例如redis , ... 

https://www.pgedge.com/blog/semantic-caching-in-postgresql-a-hands-on-guide-to-pg_semantic_cache

你的AI聊天机器人,每天可能被问同样的问题几十遍,只是换了个说法。它却傻乎乎地每次都去调用昂贵的大模型,既烧钱又慢得像蜗牛。这就是传统缓存的死穴——只认死理,非得字一模一样才算命中。今天教你用PostgreSQL官方扩展pg_semantic_cache,让数据库“听懂人话”,轻松实现语义缓存,把LLM调用成本砍掉一大半。

一、传统缓存为啥不顶用?

假如用户问:

  • “我们第四季度收入是多少?”
  • “给我上个季度的销售数字”
  • “2024年Q4营收数据”

在传统缓存眼里,这是三个完全不同的字符串,于是三次都去问大模型。但实际上它们问的是同一件事。研究显示,AI应用里40%到70%的查询都是这种“语义重复”——换汤不换药。结果就是:API调用次数翻倍,延迟翻倍,云账单也翻倍。

二、语义缓存怎么破?

语义缓存不看你用的词,而是看你表达的意思。它会把问题和答案转换成“向量”(可以想象成一组坐标),然后通过计算向量之间的距离来判断两个问题是不是“一伙的”。距离越近,意思越像。超过设定的相似度阈值(比如95%),就直接返回缓存的结果,根本不用再去麻烦大模型。

pg_semantic_cache就是PostgreSQL官方推出的一个扩展,把这种能力直接集成到数据库里。下面我们就动手搭一个环境,亲眼看看它是怎么工作的。

三、动手实践:30分钟跑通语义缓存

1. 用Docker一键拉起环境

创建一个 Dockerfile,内容如下(基于Rocky Linux 9,安装pgEdge Enterprise Postgres 17,自带向量插件pgvector):

FROM rockylinux:9
RUN dnf install -y epel-release && \  
    dnf config-manager --set-enabled crb && \  
    dnf install -y gcc make git redhat-rpm-config  
RUN dnf install -y https://dnf.pgedge.com/reporpm/pgedge-release-latest.noarch.rpm && \  
    dnf install -y pgedge-enterprise-postgres_17 pgedge-pgvector_17 pgedge-postgresql17-devel  
ENV PATH="/usr/pgsql-17/bin:${PATH}"
ENV PG_CONFIG="/usr/pgsql-17/bin/pg_config"
RUN git clone https://github.com/pgedge/pg_semantic_cache.git /tmp/pg_semantic_cache && \  
    cd /tmp/pg_semantic_cache && \  
    make PG_CONFIG=/usr/pgsql-17/bin/pg_config && \  
    make PG_CONFIG=/usr/pgsql-17/bin/pg_config install && \  
    rm -rf /tmp/pg_semantic_cache  
USER postgres  
RUN /usr/pgsql-17/bin/initdb -D /var/lib/pgsql/17/data  
RUNecho"host all all 0.0.0.0/0 trust" >> /var/lib/pgsql/17/data/pg_hba.conf && \  
    echo "listen_addresses = '*'" >> /var/lib/pgsql/17/data/postgresql.conf  
EXPOSE5432
CMD ["/usr/pgsql-17/bin/postgres", "-D", "/var/lib/pgsql/17/data"]  

构建并运行容器:

docker build -t pg-semantic-cache .  
docker run -d --name semcache -p 5432:5432 pg-semantic-cache  

稍等几秒,等数据库启动后,进入容器连接PostgreSQL:

docker exec -it semcache psql -U postgres  

2. 创建数据库并启用扩展

CREATEDATABASE semcache_demo;  
\c semcache_demo  

CREATE EXTENSION IFNOTEXISTS vector;          -- pgvector(自带)  
CREATE EXTENSION IFNOTEXISTS pg_semantic_cache;  -- 语义缓存扩展  

检查一下缓存是否为空:

SELECT * FROM semantic_cache.cache_stats();  

应该看到 total_entries = 0,一切就绪。

3. 配置向量维度(演示用8维,方便理解)

SELECT semantic_cache.set_vector_dimension(8);  
SELECT semantic_cache.rebuild_index();  -- 应用更改,会清空缓存  

4. 模拟存入三条问答(问题 + 向量 + 答案)

假设三个用户分别问了关于PostgreSQL的问题,大模型给出了答案,我们把结果存进缓存:

-- 事务问题  
SELECT semantic_cache.cache_query(  
'How does PostgreSQL handle transactions?',  
'[0.12,0.85,0.44,0.31,0.67,0.22,0.91,0.15]',  
'{"answer": "PostgreSQL implements MVCC for transaction management..."}'::jsonb,  
7200,  
ARRAY['postgres', 'concepts']  
);  

-- 索引问题  
SELECT semantic_cache.cache_query(  
'What types of indexes does PostgreSQL support?',  
'[0.33,0.18,0.72,0.55,0.11,0.88,0.29,0.64]',  
'{"answer": "PostgreSQL supports B-tree, Hash, GiST, GIN, BRIN..."}'::jsonb,  
7200,  
ARRAY['postgres', 'indexing']  
);  

-- 复制问题  
SELECT semantic_cache.cache_query(  
'How do I set up replication in PostgreSQL?',  
'[0.78,0.42,0.15,0.93,0.37,0.56,0.08,0.71]',  
'{"answer": "PostgreSQL supports streaming replication and logical replication..."}'::jsonb,  
7200,  
ARRAY['postgres', 'replication']  
);  

现在缓存里有3条记录了。

5. 模拟一个新用户提问:“Explain ACID and transactions in Postgres”

这个问题跟第一条(事务)语义非常接近,假设嵌入模型给出的向量是 [0.13,0.83,0.46,0.29,0.65,0.24,0.89,0.17](和第一条的向量非常接近)。我们查询缓存:

SELECT * FROM semantic_cache.get_cached_result(  
'[0.13,0.83,0.46,0.29,0.65,0.24,0.89,0.17]',  
0.95-- 相似度阈值95%  
);  

结果:found = t,相似度0.9991,直接返回了第一条的答案!整个过程毫秒级,根本没去调用大模型。

6. 再试一个完全不相关的问题

向量是 [0.95,0.05,0.10,0.02,0.88,0.03,0.50,0.40]:

SELECT * FROM semantic_cache.get_cached_result(  
'[0.95,0.05,0.10,0.02,0.88,0.03,0.50,0.40]',  
0.95
);  

结果:found = f,相似度只有0.6821,没命中。

7. 查看缓存统计

SELECT * FROM semantic_cache.cache_stats();  

你会看到 total_entries 还是3(因为没命中那次虽然没返回结果,但默认不自动插入新条目,实际应用中需要应用自己决定是否插入新结果),hits=1, misses=1,命中率50%。

四、原理一句话:向量相似度计算

  • 每个问题和结果存成一个向量(由嵌入模型生成)。
  • 新查询来了,用它的向量跟缓存里所有向量比一遍,找余弦距离最近的。
  • 如果最近的距离超过阈值(比如0.95,意味着95%相似),就返回对应的缓存结果。
  • 否则就是没命中,应用再去调LLM,得到新结果后再存进缓存。

相似度分数大致可以这样理解:

  • 0.98以上:几乎一样的意思(比如“重置密码”和“密码重置”)
  • 0.93~0.97:同一意图,表达不同(“Q4营收” vs “上个季度销售额”)
  • 0.85~0.92:相关但不完全相同
  • 0.85以下:基本不相关

五、真金白银的效果:命中率从20%飙到80%

传统缓存(精确匹配)在AI应用里命中率通常只有15%~25%。而语义缓存能把命中率提高到60%~80%!这意味着:

  • 65%~75%的LLM调用被省掉了,API费用同比例下降。
  • 缓存命中时,响应时间从大模型的几百毫秒甚至几秒,降到2~3毫秒,用户体验飞升。
  • 对于RAG(检索增强生成)应用,节省的不仅是LLM调用,还跳过了向量检索和生成过程,成本进一步降低。

生成一次嵌入的成本极低(约0.01~0.03。省掉LLM调用,就是省掉了大头。

六、接下来还能玩什么?

缓存不是一存了之。下篇我们会深入聊:

  • 如何用标签(tags)组织缓存条目
  • 缓存失效策略(比如按时间、按标签)
  • 淘汰策略(LRU、TTL等)
  • 成本跟踪和调优
  • 针对百万级缓存使用HNSW索引
  • 完整的Python集成示例

pg_semantic_cache是开源的,遵循PostgreSQL许可证。想自己动手的,直接去GitHub拉代码:
🔗 github.com/pgedge/pg_semantic_cache

一句话总结:让你的数据库学会“意会”,别再让LLM为同义反复买单。