基于DigitalOcean数据与学习层构建AI应用:PostgreSQL+pgvector实战指南
1. 项目概述为什么“拼凑”是AI应用开发的效率黑洞如果你正在或者尝试过开发一个AI应用比如一个智能客服、一个文档问答系统或者一个个性化推荐引擎那么下面这个场景你一定不陌生你首先需要一个关系型数据库比如PostgreSQL或者MySQL来存储用户信息、订单记录这些结构化数据。然后为了处理用户用自然语言提出的问题你需要一个向量数据库比如Pinecone、Weaviate或者Milvus来存储和检索文本、图片的向量嵌入。接着你需要一个缓存层比如Redis来提升热点数据的访问速度。最后你可能还需要一个消息队列、一个对象存储服务……光是让这些组件彼此通信、保持数据一致性就足以消耗掉你大半的开发精力。这还不是最头疼的当应用流量上来你需要考虑分库分表、向量索引的优化、缓存的穿透击穿雪崩运维的复杂度呈指数级上升。这就是典型的“拼凑式”架构。每一个组件都是领域的专家但把它们组合成一个稳定、高效、易维护的整体需要开发者具备全栈的架构和运维能力。DigitalOcean提出的“数据与学习层”概念正是瞄准了这个痛点。它不是一个全新的、颠覆性的技术而是一种产品理念的整合将AI应用开发中最核心的数据存储、向量检索乃至模型推理所需的基础设施打包成一个内聚的、无缝协作的服务层。其核心载体便是对PostgreSQL的深度增强。简单来说它想让开发者像使用一个“超级数据库”一样来构建AI应用所有的数据——无论是结构化的用户ID还是非结构化的文本向量——都存放在同一个地方使用同一种方式管理和查询。这听起来像是把向量搜索功能“插件化”到传统数据库中但DigitalOcean的尝试更进一步它试图从云服务的层面提供开箱即用的集成体验和自动化的运维管理。对于中小型团队和个人开发者而言这意味着你可以将宝贵的开发资源从复杂的基础设施编排中解放出来更聚焦于业务逻辑和AI模型本身。2. 核心需求解析AI应用需要什么样的数据层要理解“数据与学习层”的价值我们必须先拆解一个现代AI应用特别是基于大语言模型的RAG应用对数据基础设施的核心需求。这些需求远不止“存”和“取”那么简单。2.1 多模态数据的一体化存储与关联一个智能应用的数据是立体的。以电商智能导购为例结构化数据商品SKU、价格、库存、用户订单、收货地址。这些数据传统上存放在PostgreSQL的表中通过SQL进行精准的联表查询。非结构化数据商品描述文案、用户评论、客服对话记录、商品图片。这些数据经过嵌入模型处理后会变成高维向量用于语义搜索。元数据向量的来源、生成时间、所属类别。这些数据用于对向量检索结果进行高效的过滤。在“拼凑”架构中商品详情结构化在PostgreSQL商品描述向量在专门的向量数据库两者通过一个外键如商品ID进行逻辑关联。每次查询应用层需要先到向量库做语义搜索拿到一批商品ID再回传到关系库去补全商品详情涉及多次网络往返和事务管理。而在理想的数据与学习层中商品详情表和它的描述向量可以存放在同一个数据库实例甚至同一张表的扩展字段中。一次查询就能同时利用索引完成向量相似度计算和结构化字段的过滤效率和数据一致性得到根本性提升。2.2 近实时、高并发的向量检索向量搜索不是批处理它要求在线、低延迟。当用户输入“适合夏天穿的、透气轻便的男士衬衫”时应用需要在毫秒到百毫秒内从上百万个商品向量中找到最相关的几十个。这要求底层向量索引如HNSW、IVF必须高效并且能够支持高并发查询。传统数据库并非为此设计而许多专业的向量数据库在应对复杂过滤如“价格在100-300元之间且评分大于4.5”时又可能表现不佳。数据与学习层需要融合两者的优势既要有专业向量库的检索性能又要具备关系数据库强大的过滤和事务能力。2.3 简化的运维与无缝的扩展这是云服务的核心价值所在。开发者不想关心向量索引的调参HNSW的ef_construction、M参数怎么设置IVF的聚类数选多少这些参数直接影响搜索精度和性能。集群的伸缩数据量增长了如何给向量索引部分扩容如何实现读写分离备份与容灾向量数据和关系数据如何保持一致性的备份监控与告警如何监控向量检索的延迟和召回率一个成熟的数据与学习层服务应该像使用托管数据库一样提供一键部署、自动备份、监控仪表盘和弹性伸缩策略将上述运维负担全部接管。2.4 与AI工作流的原生集成数据层不应该只是一个被动的存储系统。它需要更好地与AI工作流对接。例如嵌入模型的集成能否在数据入库时自动调用配置好的嵌入模型如OpenAI的text-embedding-3-small或开源的BGE模型为文本字段生成向量而无需开发者手动编写ETL脚本推理端点的就近访问当检索出相关上下文后能否在云网络内部高速、安全地调用托管的LLM如DigitalOcean的AI Inference服务进行生成避免公网传输延迟和风险数据变更的自动捕获当源数据更新时能否自动触发相关向量的重新生成和索引更新DigitalOcean的“数据与学习层”愿景正是试图通过整合其托管PostgreSQL、Spaces对象存储、AI Inference等服务并在PostgreSQL中深度集成pgvector等扩展来系统性满足上述所有需求提供一个“一步到位”的解决方案。3. 技术实现剖析PostgreSQL pgvector 如何扛起大旗DigitalOcean实现其“数据与学习层”战略的核心技术基石是托管PostgreSQL数据库和对pgvector扩展的支持。这并不是简单的功能堆砌而是一种深思熟虑的架构选择。我们来深入看看这套组合拳是如何工作的。3.1 pgvector让PostgreSQL学会“理解”语义pgvector是一个开源PostgreSQL扩展它增加了对向量数据类型的原生支持并提供了用于相似性搜索的运算符和索引。正是它将PostgreSQL从一个纯粹的关系型数据库转变为一个混合事务/分析处理并具备向量检索能力的“多模”数据库。核心数据类型与操作vector类型用于存储浮点数向量例如vector(1536)可以存储一个1536维的向量对应OpenAI text-embedding-3-small的维度。相似度运算符最常用的是-欧几里得距离和余弦距离。余弦距离更常用于文本语义相似度计算值越小表示越相似。-- 查找与给定向量最相似的10条记录使用余弦距离 SELECT id, content, embedding [0.1, 0.2, ...] AS distance FROM documents ORDER BY embedding [0.1, 0.2, ...] LIMIT 10;索引机制性能的关键没有索引向量搜索就是全表扫描的噩梦。pgvector支持两种主流索引IVFFlat倒排文件索引类似于传统搜索引擎的原理。它首先对数据集中的向量进行聚类比如分成1024个簇并记录每个簇的中心点。搜索时先找到距离查询向量最近的N个簇然后只在这些簇内的向量中进行精确计算。它构建速度快占用空间小但召回率找到真正最相似向量的能力在参数设置不当时可能受影响。CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists 1000); -- lists参数大致等于聚类数量注意IVFFlat索引在数据大量新增或删除后索引效果会下降需要定期使用REINDEX命令重建索引或在新数据积累到一定比例如20%后重建。HNSW分层可导航小世界图目前多数专业向量数据库的首选算法。它构建一个多层图结构上层是“高速公路”用于快速逼近目标区域下层是“精细道路”用于在局部区域找到最近邻。HNSW的查询速度通常极快召回率高但索引构建时间长内存占用大。CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 64);m图中每个节点最大连接数影响索引结构和搜索精度/速度典型值16-48。ef_construction构建索引时动态候选集大小影响索引质量典型值64-200。选择建议数据量小100万或频繁更新可以不建索引或使用IVFFlat。数据量大100万追求极致查询性能和高召回率且数据相对静态优先选择HNSW。内存有限IVFFlat更节省内存。DigitalOcean托管环境由于底层硬件和PostgreSQL版本已优化可以更放心地使用HNSW索引。关键在于根据你的数据集大小和查询模式在控制台或通过SQL调整m和ef_construction参数。3.2 混合查询向量检索与结构化过滤的化学反应这是pgvector在PostgreSQL中最大的威力所在。你可以将向量相似度搜索和复杂的SQL过滤、排序、连接无缝结合。-- 一个复杂的混合查询示例在技术文档中搜索与“如何优化数据库连接池”语义相近 -- 且标签包含“PostgreSQL”、发布时间在一年内、点赞数超过10的文档并按相似度和点赞数综合排序。 SELECT doc.id, doc.title, doc.content, doc.embedding query_vec AS semantic_distance, doc.upvotes FROM documents doc JOIN document_tags dt ON doc.id dt.document_id JOIN tags t ON dt.tag_id t.id WHERE t.name PostgreSQL AND doc.published_at NOW() - INTERVAL 1 year AND doc.upvotes 10 AND doc.embedding query_vec 0.2 -- 设定一个相似度阈值 ORDER BY (doc.embedding query_vec) * 0.7 (1.0 / (doc.upvotes 1)) * 0.3 ASC -- 综合排序 LIMIT 5;这个查询一次性完成了语义匹配、多表关联、范围过滤和自定义加权排序。在“拼凑”架构中这需要应用层进行多次查询和复杂的逻辑合并而在集成的数据层中这只是一个查询计划优化问题。PostgreSQL的查询优化器会尝试最有效的方式例如先利用B-tree索引过滤标签和时间再对符合条件的子集进行向量搜索来执行。3.3 DigitalOcean的托管增强DigitalOcean的托管PostgreSQL服务在此基础上提供了关键的企业级能力使其真正成为可靠的“数据与学习层”一键启用与自动管理在控制台点击即可为PostgreSQL集群启用pgvector扩展无需手动编译安装。DigitalOcean负责底层扩展的版本兼容性和安全更新。高性能硬件基础提供配备高性能NVMe SSD的机型这对于需要大量随机读写的向量索引尤其是HNSW至关重要能显著降低查询延迟。可扩展性与高可用支持从单节点轻松扩展到多节点只读副本。你可以将读密集型的大量向量搜索请求分流到只读副本上而主节点专注于处理写事务和复杂混合查询。集成生态数据可以方便地与DigitalOcean Spaces对象存储存放原始文件、App Platform应用托管和AI Inference服务模型推理在同一私有网络内高速通信构成了一个完整的AI应用开发生态闭环。4. 从零到一基于DigitalOcean构建一个AI知识库应用理论说得再多不如动手实践。让我们以一个“智能产品文档知识库”为例完整走一遍在DigitalOcean上使用其“数据与学习层”构建AI应用的流程。这个应用允许用户用自然语言提问比如“如何设置双因素认证”然后从产品手册中找出最相关的段落并生成答案。4.1 第一步基础设施搭建创建托管PostgreSQL集群登录DigitalOcean控制台进入“Databases”。点击“Create Database Cluster”选择PostgreSQL版本建议15及以上对pgvector支持更好。选择机型。对于初期原型选择“Basic”或“General Purpose”系列中配备NVMe SSD的规格即可如“Basic-2 vCPU / 4GB RAM”。选择区域最好靠近你的主要用户或后续要集成的其他服务如AI Inference。启用连接池如PgBouncer这对处理大量并发的向量搜索短连接非常有帮助。创建集群记下连接信息主机、端口、数据库名、用户名、密码。启用pgvector扩展集群创建完成后进入其概览页面找到“Extensions”选项卡。在列表中找到pgvector点击“Enable”。DigitalOcean会自动完成安装。准备AI推理服务可选但推荐在“AI/ML”服务中创建或部署一个AI Inference服务。你可以选择预置的模型如Llama 3.1 8B Instruct或上传自己的自定义模型。这将为我们后续的RAG生成步骤提供便利的API端点。4.2 第二步数据库Schema设计与数据灌入连接到你的PostgreSQL数据库执行以下SQL-- 1. 创建扩展如果控制台启用后自动创建此步可省略但执行无害 CREATE EXTENSION IF NOT EXISTS vector; -- 2. 创建存储文档片段和向量的核心表 CREATE TABLE document_chunks ( id BIGSERIAL PRIMARY KEY, document_id VARCHAR(255) NOT NULL, -- 原始文档ID chunk_text TEXT NOT NULL, -- 文本片段内容 embedding vector(1536), -- 假设使用OpenAI text-embedding-3-small模型 metadata JSONB DEFAULT {}, -- 存储来源、页码、标题等元数据 created_at TIMESTAMPTZ DEFAULT NOW() ); -- 3. 创建HNSW索引以加速向量搜索 CREATE INDEX ON document_chunks USING hnsw (embedding vector_cosine_ops); -- 4. 创建用于过滤的B-tree索引 CREATE INDEX ON document_chunks USING gin (metadata); -- 支持JSONB字段的快速查询 CREATE INDEX ON document_chunks (document_id);接下来是数据灌入。你需要一个流程将你的产品手册PDF、Markdown等进行文本分割、向量化并存入数据库。# 这是一个使用Python的示例脚本 import psycopg2 from langchain.text_splitter import RecursiveCharacterTextSplitter from openai import OpenAI import PyPDF2 # 假设处理PDF # 配置 DO_DB_CONN_STR postgresql://user:passwordhost:port/dbname OPENAI_API_KEY your-key EMBEDDING_MODEL text-embedding-3-small # 初始化 client OpenAI(api_keyOPENAI_API_KEY) conn psycopg2.connect(DO_DB_CONN_STR) cur conn.cursor() text_splitter RecursiveCharacterTextSplitter(chunk_size1000, chunk_overlap200) def extract_text_from_pdf(pdf_path): # ... 使用PyPDF2提取文本 ... return full_text def get_embedding(text): response client.embeddings.create(modelEMBEDDING_MODEL, inputtext) return response.data[0].embedding def process_document(pdf_path, doc_id): full_text extract_text_from_pdf(pdf_path) chunks text_splitter.split_text(full_text) for i, chunk in enumerate(chunks): embedding get_embedding(chunk) metadata {doc_id: doc_id, chunk_index: i, source: pdf_path} # 使用pgvector的扩展类型psycopg2会自动适配 cur.execute( INSERT INTO document_chunks (document_id, chunk_text, embedding, metadata) VALUES (%s, %s, %s, %s), (doc_id, chunk, embedding, metadata) ) conn.commit() # 处理文档 process_document(user_manual.pdf, manual_v2.0) cur.close() conn.close()实操心得在灌入大量数据时不要逐条提交。可以每处理100或500个chunk再commit一次或者使用cursor.executemany进行批量插入能极大提升效率。另外在构建HNSW索引前灌入数据比先建索引再插入要快得多。建议灌完所有数据后再执行CREATE INDEX语句。4.3 第三步实现RAG检索与生成核心逻辑应用后端比如一个FastAPI服务的核心函数如下from fastapi import FastAPI from pydantic import BaseModel import psycopg2 from psycopg2.extras import RealDictCursor import openai import os app FastAPI() # 配置 DO_DB_CONN_STR os.getenv(DO_DB_CONN_STR) OPENAI_API_KEY os.getenv(OPENAI_API_KEY) DO_AI_INFERENCE_URL os.getenv(DO_AI_INFERENCE_URL) # 如果使用DO的推理服务 client openai.OpenAI(api_keyOPENAI_API_KEY) class QueryRequest(BaseModel): question: str top_k: int 5 filter_doc_id: str None def get_query_embedding(question: str) - list: response client.embeddings.create(modeltext-embedding-3-small, inputquestion) return response.data[0].embedding app.post(/ask) async def ask_question(req: QueryRequest): # 1. 将用户问题转换为向量 query_vec get_query_embedding(req.question) # 2. 在数据库中执行混合检索 conn psycopg2.connect(DO_DB_CONN_STR, cursor_factoryRealDictCursor) cur conn.cursor() sql SELECT chunk_text, metadata, embedding %s AS similarity FROM document_chunks WHERE 11 params [query_vec] if req.filter_doc_id: sql AND metadata-doc_id %s params.append(req.filter_doc_id) sql ORDER BY similarity ASC LIMIT %s; params.append(req.top_k) cur.execute(sql, params) results cur.fetchall() cur.close() conn.close() # 3. 构建上下文 context \n\n---\n\n.join([f[来源{r[metadata].get(source, N/A)}]\n{r[chunk_text]} for r in results]) # 4. 调用LLM生成答案这里以OpenAI为例也可替换为DO AI Inference端点 prompt f基于以下上下文信息回答用户的问题。如果上下文没有提供足够信息请直接说“根据现有资料无法回答”。 上下文 {context} 用户问题{req.question} 请用中文给出清晰、准确的回答 completion client.chat.completions.create( modelgpt-4o-mini, # 或使用部署在DO上的模型 messages[{role: user, content: prompt}], temperature0.2 ) answer completion.choices[0].message.content return { answer: answer, relevant_chunks: results, context_used: context }这个简单的API端点就完成了从问题向量化、数据库混合检索、上下文构建到最终答案生成的完整RAG链条。部署这个应用到DigitalOcean的App Platform并与你的数据库、AI推理服务配置在同一VPC内就能获得最佳的网络性能和安全性。5. 性能调优与生产环境考量将原型投入生产我们需要关注性能、稳定性和成本。以下是基于DigitalOcean环境的关键调优点。5.1 向量索引参数调优索引参数没有银弹需要基于你的数据集进行测试。HNSW参数 (m,ef_construction,ef_search)m增加m会提高召回率和索引大小降低构建速度。对于100万左右的数据集从16开始测试。数据量更大或对精度要求极高可以尝试24或32。ef_construction增加此值会提高索引质量召回率但也会显著增加构建时间和内存。通常设置为m的2-4倍例如m16时ef_construction64是一个不错的起点。ef_search这是在查询时使用的参数不是在CREATE INDEX中设置而是在查询时通过SET或在索引中指定。它控制搜索时考察的候选节点数量。值越大召回率越高速度越慢。可以在查询时动态调整SET hnsw.ef_search 100; -- 在会话中设置 SELECT * FROM items ORDER BY embedding [0.1, ...] LIMIT 10;生产环境中可以根据查询的实时性要求在应用代码中为不同类型的查询设置不同的ef_search值。IVFFlat参数 (lists)lists聚类数量。一个经验法则是设置为sqrt(行数)。对于100万行数据可以设置为1000。lists值越大查询精度越高但速度会变慢。重建索引记住IVFFlat索引在数据更新后需要重建以保持效率。可以设置一个定时任务在业务低峰期定期执行REINDEX INDEX index_name;。如何测试建立一个包含代表性查询的测试集然后编写脚本遍历不同的参数组合评估查询延迟和召回率通过人工或与暴力扫描结果对比找到满足你业务需求的最佳平衡点。5.2 查询性能优化使用连接池务必启用DigitalOcean PostgreSQL提供的连接池如PgBouncer。向量搜索查询通常是短平快的连接池可以避免频繁建立和销毁TCP/数据库连接的开销极大提升并发能力。善用过滤条件在向量相似度搜索前尽可能使用高效的过滤条件如WHERE metadata-doc_id xxx缩小搜索范围。PostgreSQL可能会先利用B-tree或GIN索引快速过滤出子集再在这个小得多的子集上进行向量比较性能提升巨大。避免SELECT *只查询你需要的列特别是不要轻易在查询中包含巨大的embedding向量列除非必要。传输大量向量数据会消耗网络带宽和内存。分页优化对于深度分页LIMIT 10 OFFSET 10000传统数据库性能会下降。对于向量搜索更常见的模式是“游标分页”或“基于相似度阈值分页”。例如记录上一次查询最后一个结果的相似度得分下一次查询时加上WHERE similarity :last_score条件。5.3 成本控制与架构设计在云上成本意识很重要。选择合适的机型从“Basic”系列起步监控CPU、内存、磁盘IO和连接数。如果CPU持续高负载说明向量计算压力大考虑升级CPU。如果内存使用率持续高位可能是HNSW索引或连接缓存占用过多需要升级内存或优化索引参数。读写分离利用DigitalOcean PostgreSQL的只读副本。将大量的向量搜索只读请求路由到只读副本主库只负责处理写操作数据插入、更新和复杂的混合事务。这不仅能提升性能也是一种高可用策略。冷热数据分层对于历史数据或访问频率极低的数据可以考虑将其向量从昂贵的、索引优化的主表中迁移到另一个未建索引或使用IVFFlat索引的归档表中。查询时优先查询热表必要时再联合查询冷表。监控与告警充分利用DigitalOcean提供的数据库监控仪表盘。重点关注查询延迟Query latency特别是向量搜索查询的P95、P99延迟。连接数Connections确保没有连接泄漏且连接池配置合理。磁盘IOPS向量索引的随机读取对IOPS敏感确保磁盘性能足够。设置告警当延迟超过阈值或连接数爆满时及时通知。6. 常见陷阱与进阶技巧在实际开发中你会遇到一些教科书上不会提的问题。这里分享一些踩坑后总结的经验。6.1 向量维度对齐与模型升级这是一个极易忽略的致命问题。假设你最初使用text-embedding-ada-0021536维所有数据都用这个模型生成了向量。后来你升级到了性能更好的text-embedding-3-small也是1536维但向量空间不同。如果你直接用新模型为新的查询生成向量去检索用旧模型生成的数据向量效果会非常差因为它们的向量空间没有对齐。解决方案一次性全量迁移最彻底的方法。用新模型为所有历史数据重新生成向量替换掉旧的。这需要停机窗口或双写双读的复杂迁移方案。使用跨编码器进行重排Rerank这是一个更实用的渐进式方案。继续用旧模型进行初步的向量检索比如召回100个结果然后用一个强大的跨编码器模型如BGE Reranker对这100个结果进行精排。跨编码器直接计算查询和文档之间的相关性分数不依赖于向量空间的一致性可以作为不同嵌入模型之间的“桥梁”。你可以在DigitalOcean AI Inference上部署一个这样的重排模型。6.2 文本分块的艺术分块Chunking策略直接决定检索质量。简单的按固定字符数分割会切断完整的句子或概念。递归字符分割LangChain的RecursiveCharacterTextSplitter是个不错的起点它会优先按段落、句子、单词等自然分隔符来分。基于语义的分割使用嵌入模型本身或小型句子模型计算句子间的相似度在语义变化处进行分割。这更复杂但效果更好。重叠Overlap设置重叠区如200个字符至关重要它能防止一个概念恰好被切成两半而丢失关键信息。小技巧对于代码文档可以按函数/类定义分块对于手册可以按章节或子标题分块。在metadata中记录分块策略和来源位置便于后续调试和优化。6.3 处理“未命中”与幻觉RAG不是万能的当知识库中没有相关信息时LLM可能会“胡编乱造”幻觉。设置相似度阈值在查询时添加WHERE embedding query_vec threshold。只返回相似度高于一定阈值即距离小于阈值的结果。这个阈值需要通过实验确定。在Prompt中明确指令就像我们示例中做的在Prompt里明确要求“如果上下文没有提供足够信息请直接说‘根据现有资料无法回答’”。引用溯源在返回答案的同时返回引用的原文片段及其元数据如来源、页码。这不仅能增加可信度也方便用户追溯和验证。6.4 超越简单检索高级RAG模式当基本RAG遇到复杂问题时可以考虑以下进阶模式多跳检索Multi-hop Retrieval对于需要串联多个知识点才能回答的问题如“A产品相比B产品在安全特性上有何优势”可以先检索关于A产品安全特性的文档再从这些文档中提取关键实体如“加密算法X”再用这些实体作为新的查询去检索B产品的相关文档。这需要更复杂的查询规划和迭代。查询扩展Query Expansion使用LLM对原始用户问题进行重写或生成多个相关问题。例如将“怎么退款”扩展为“退款流程是什么”、“退款需要多久”、“退款政策有哪些”。然后用这组问题并行检索合并结果能显著提高召回率。混合检索Hybrid Search结合稀疏检索如BM25关键词匹配和密集检索向量搜索。BM25擅长精确匹配关键词向量搜索擅长语义匹配。将两者的得分进行加权融合可以兼顾查全率和查准率。虽然pgvector本身不直接提供BM25但PostgreSQL的全文搜索功能可以作为一个轻量级的替代方案。在DigitalOcean的生态内你可以将上述复杂逻辑实现在你的应用后端部署在App Platform或Kubernetes上数据库负责高效的数据存储和混合检索AI Inference服务负责嵌入生成、重排和最终答案生成形成一个强大且灵活的AI应用后端。这正体现了“数据与学习层”整合带来的敏捷性——你无需在多个异构系统间挣扎可以更专注于业务逻辑本身的创新。