CockroachDB原生向量搜索:强一致分布式AI应用的底层答案

发布时间:2026/7/20 10:59:51
CockroachDB原生向量搜索:强一致分布式AI应用的底层答案 1. 项目概述当向量搜索撞上强一致分布式数据库“Building AI-Powered Applications with CockroachDB Vector Search: From Theory to Practice”——这个标题里藏着一个正在发生的行业拐点。它不是在讲“怎么用PostgreSQL加pgvector搭个检索demo”也不是“用FAISS跑个本地向量库就叫AI应用”。它直指一个更硬核、也更现实的问题当你的AI应用要服务百万级用户、写入TPS超5000、要求跨三地机房数据强一致同时还要支持语义搜索、混合过滤、实时向量更新——你还能只靠单机向量库或松散耦合的向量数据库双写架构吗我在金融风控系统和SaaS客户知识库两个真实项目里踩过坑用Elasticsearch做向量插件结果高并发写入时向量索引与文档元数据不同步查到的“最相似文档”根本不是最新版本用Milvus独立部署运维成本飙升光是向量索引重建就占满3台GPU节点业务方一句“明天上线新模型”就能让整个团队通宵。CockroachDB 23.2正式引入原生向量类型VECTOR(n)和USING HNSW索引不是简单加个功能而是把向量能力直接焊进ACID事务引擎里。这意味着你执行INSERT INTO products (name, desc_vec) VALUES (wireless earbuds, [0.82, -0.14, ...])时向量写入、主键生成、二级索引更新、跨区复制全部在一个事务内原子完成。我实测过在3节点跨AZ集群上插入10万条带128维向量的商品记录耗时17.3秒且任意节点故障后查询结果零丢失、零不一致。这背后是CockroachDB底层Raft共识协议与HNSW图索引结构的深度协同——向量索引分片随数据分片自动迁移无需人工rebalance。对开发者而言它抹平了“AI工程师管向量、DBA管数据库、SRE管一致性”的传统割裂。你不再需要写500行Kafka消费者代码来同步向量变更一条SQL就能搞定从数据摄入、向量计算到语义检索的全链路。适合谁不是给想学AI的大学生看的玩具项目而是给正在重构核心交易系统、构建企业级RAG平台、或需要合规审计能力的AI产品经理、后端架构师、以及被“向量数据库”双栈运维压得喘不过气的工程师。它解决的不是“能不能搜”而是“在银行级SLA下能不能又快、又准、又稳地搜”。2. 核心设计逻辑为什么向量必须长在分布式事务引擎里2.1 传统向量架构的三大反模式与真实代价我们先拆解三个被无数PPT吹捧、却在生产环境反复暴雷的“标准方案”看看它们到底卡在哪反模式一“向量库关系库”双写架构典型如“PostgreSQL pgvector”组合。表面看很美SQL写数据INSERT INTO docs (id, content, embedding) VALUES (...)一行搞定。但问题藏在细节里当业务要求“内容更新后向量必须实时生效”你得确保两件事原子发生——更新content字段和更新embedding字段。PostgreSQL的UPDATE能保证单表原子性可一旦向量由外部AI服务异步生成比如调用OpenAI API你就得自己实现“写消息队列→消费→更新向量”的闭环。我经手的一个合同管理系统就因此出事上游CRM推送新合同文本下游AI服务生成向量耗时波动100ms~2s期间有37次查询命中了旧向量返回的“相似条款”完全错误。修复方案不是加缓存而是引入Saga事务模式代码量从20行SQL膨胀到300行状态机逻辑还增加了6个监控告警项。反模式二“向量专用库”独立部署Milvus、Qdrant这类专为向量优化的数据库性能确实亮眼。但它们天然缺失关系型语义。比如电商场景要查“价格500且与‘降噪耳机’语义相似的商品”Milvus只能做向量近邻搜索价格过滤得靠客户端二次遍历——10万候选结果里逐条比价格延迟从20ms飙到1.2秒。有人会说“加filtering index”但Qdrant的scalar index不支持范围查询Milvus的inverted index对浮点价格字段效果极差。更致命的是运维复杂度Milvus 2.x需要独立维护etcd、pulsar、minio三套组件一次磁盘满导致pulsar broker宕机向量写入直接中断而业务数据库还在正常写入数据彻底失联。我们曾为保障99.95%可用率给Milvus集群配了5人轮值盯屏成本远超数据库License本身。反模式三“向量作为BLOB存关系库”把向量数组序列化成JSON或BYTEA存进MySQL/PostgreSQL。看似省事实则自废武功。首先无法利用向量索引加速——每次查询都要全表扫描CPU计算余弦相似度10万条数据查询耗时8秒。其次丧失类型安全embedding字段可能存错维度本该128维存成768维SQL查询直接报错而错误发现往往在上线后。最后无法做向量运算下推你想查“与A向量相似度0.85且创建时间2024-01-01的记录”数据库只能返回所有记录再由应用层过滤网络传输量爆炸。某客户知识库项目因此单次查询带宽占用达2.3GBCDN费用翻倍。提示这三个反模式的核心病灶是同一问题——向量数据与业务数据在存储、事务、索引、计算四个层面的物理分离。分离带来同步延迟、一致性风险、查询低效、运维碎片化。CockroachDB的破局点就是让向量成为数据库的“一等公民”而非寄居的“外来户”。2.2 CockroachDB向量设计的底层哲学向量即数据索引即协议CockroachDB没有把向量当作特殊数据类型来“兼容”而是从存储引擎层重定义了向量的存在方式。理解这一点才能避开后续所有坑。第一层存储即向量向量即存储CockroachDB的VECTOR(n)类型不是简单的字节数组封装。当你声明embedding VECTOR(768)数据库在底层为该列分配连续内存块并启用SIMD指令集AVX-512进行向量运算加速。更重要的是向量数据与行数据共存于同一Raft Log Entry中。这意味着写入INSERT INTO table (id, name, embedding) VALUES (1, iPhone, [0.1, 0.9, ...])时Raft日志里记录的是“完整行数据”包含id、name、embedding三者。节点故障恢复时回放日志必然还原出完全一致的向量值不存在“只恢复了id和nameembedding丢失”的情况。跨区域复制时向量数据随其他字段一同通过Raft协议强一致同步延迟50ms实测AZ间。对比pgvector其向量存储依赖PostgreSQL的TOAST机制大向量会被切片存到辅助表而TOAST表的复制与主表不同步极端情况下会出现主表有记录、TOAST表无向量的“半残缺”状态。第二层HNSW索引与Raft分片的共生设计HNSWHierarchical Navigable Small World是当前最主流的近似最近邻ANN索引算法但它有个致命弱点图结构动态更新时节点增删会导致大量边重连严重影响写入性能。CockroachDB的解决方案堪称精妙它将HNSW图索引按数据分片Range进行水平切分每个分片只维护自己数据子集的局部HNSW图。当新向量写入仅触发本Range内的图更新无需全局锁或广播。更关键的是HNSW图的元数据如入口点、层级信息与Range元数据一同存储在Raft Group中。Range迁移时如负载均衡HNSW图结构随Range原子迁移避免了传统方案中“索引重建需数小时”的停服窗口。我做过压力测试在3节点集群上持续以2000 QPS写入128维向量HNSW索引构建期间查询P99延迟稳定在42ms±3ms而Milvus同等负载下P99延迟波动达120ms~2.1秒。第三层向量运算下推至分布式执行引擎CockroachDB的SQL执行器Cascades Optimizer能将向量操作下推到存储层。例如这条查询SELECT id, name, embedding - [0.5, 0.3, ...] AS dist FROM products WHERE price 500 AND category electronics ORDER BY dist LIMIT 10;执行计划显示price和category的过滤条件在KV层RocksDB通过索引快速剪枝仅将满足条件的行ID和embedding列送入向量计算层-欧氏距离运算在存储节点本地完成避免海量向量数据跨网络传输。实测100万商品库中该查询平均耗时86ms而同等条件下“PostgreSQLpgvector”需先扫描全表获取100万向量再在应用层计算耗时3.2秒。2.3 选型决策树什么场景必须用CockroachDB向量什么场景反而不该用技术选型不是越新越好而是匹配业务水位线。基于我经手的12个向量项目总结出这张硬核决策树业务特征CockroachDB向量是否推荐关键原因替代方案建议数据强一致性是生命线如金融交易、医疗记录、法律合同✅ 强烈推荐向量更新与业务字段更新在同一个ACID事务中审计日志可追溯每一步变更绝对禁用双写架构Milvus无法满足合规审计要求写入吞吐1000 QPS且需实时检索✅ 推荐HNSW分片Raft协同写入延迟15msP99索引构建不影响查询pgvector在高并发写入下易出现索引锁争用延迟抖动剧烈混合查询频繁向量相似度多字段过滤聚合✅ 推荐SQL原生支持WHEREORDER BY -执行器智能下推避免客户端二次过滤Qdrant虽支持scalar filtering但不支持GROUP BY或复杂JOIN需额外ETL向量维度1024且更新极少⚠️ 谨慎评估高维向量使HNSW图连接度激增内存占用翻倍查询精度下降此时FAISS IVF_PQ量化更优用FAISS离线构建索引CockroachDB仅存元数据通过gRPC桥接纯向量检索无业务字段关联如图像特征库❌ 不推荐过度设计CockroachDB的分布式开销对单点查询是负担直接用Qdrant或Weaviate轻量、启动快、运维简单预算有限团队无分布式数据库经验❌ 暂缓CockroachDB学习曲线陡峭需理解Raft、Range、Locality等概念初期运维成本高先用pgvectorPostgreSQL待业务验证后再迁移一个血泪教训某在线教育平台初期用CockroachDB向量存课程向量但课程元数据讲师、章节、时长存在MongoDB导致“查相似课程”时需双库JOIN性能崩坏。后来我们重构为“所有课程数据向量统一存CockroachDB”JOIN转为单表查询延迟从2.1秒降至89ms但开发周期延长了3周。结论CockroachDB向量的价值只在“向量与业务数据深度耦合”的场景里才真正爆发。3. 实操全流程从零搭建一个抗压的AI应用后端3.1 环境准备与集群部署避开那些没人说的坑别信官方文档里“3分钟启动集群”的宣传。生产级部署的魔鬼在细节里我列出必须亲手验证的5个关键点第一步硬件与OS配置——别让内核拖垮RaftCockroachDB对网络和磁盘延迟极度敏感。我们线上集群强制要求网络节点间RTT 5msAZ内 20ms跨AZ。用ping -c 10 node2实测若抖动3msRaft心跳会频繁超时触发无谓的Leader重选。磁盘必须NVMe SSD禁用机械盘或SATA SSD。RAID配置上我们放弃RAID5写惩罚大采用RAID10noatime,nobarrier挂载参数。实测同一集群SATA SSD下Range复制速度仅12MB/sNVMe可达217MB/s。内核参数vm.swappiness1禁止swap、net.core.somaxconn65535提升连接队列、fs.file-max2097152文件句柄。特别注意net.ipv4.tcp_tw_reuse1否则高并发短连接下TIME_WAIT堆积端口耗尽。注意在AWS EC2上务必选择i3en.2xlarge及以上实例自带NVMe别用m5系列——它的EBS吞吐上限会成为瓶颈。第二步集群初始化——Locality是向量性能的命门CockroachDB的--locality参数不是可选项而是向量索引分片的指挥棒。错误配置会让HNSW图跨地域分散查询时需聚合多地结果延迟翻倍。正确姿势# 三AZ部署每个AZ一个节点 cockroach start \ --storetypessd,path/data1 \ --hostnode1.internal \ --port26257 \ --http-port8080 \ --localityregionus-east,datacenteraz1 \ --joinnode1:26257,node2:26257,node3:26257 \ --cache4GB \ --max-sql-memory4GB关键在--localityregionus-east,datacenteraz1——它告诉CockroachDB“这个节点属于us-east区域的az1机房”。后续创建表时CREATE TABLE语句中的LOCALITY REGIONAL BY TABLE IN PRIMARY REGION会据此将同一张表的Range尽量放在同AZHNSW图自然本地化。我们曾因漏配--locality导致向量查询P99延迟从45ms飙升至320ms排查三天才发现是跨AZ Range请求。第三步数据库与用户权限——最小权限原则不是口号向量操作涉及高计算资源必须隔离权限-- 创建专用向量库 CREATE DATABASE ai_app; -- 创建向量专用用户禁用DDL权限 CREATE USER ai_app_rw WITH PASSWORD strong-pass-2024; -- 授予最小必要权限 GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ai_app.products TO ai_app_rw; GRANT SELECT ON TABLE crdb_internal.kv_store_status TO ai_app_rw; -- 仅用于监控 -- 关键禁用危险权限 REVOKE CREATE ON DATABASE ai_app FROM ai_app_rw; REVOKE DROP ON TABLE ai_app.products FROM ai_app_rw;为什么禁用CREATE因为CREATE INDEX可能被恶意用户滥用创建全表向量索引耗尽内存。我们线上就发生过测试账号误执行CREATE INDEX ON products USING HNSW (embedding), 单节点内存瞬间打满集群假死。第四步客户端驱动选型——Go还是Python实测数据说话CockroachDB官方推荐Go驱动github.com/cockroachdb/cockroach-go/crdb但业务团队多用Python。我们对比了三种方案方案P99延迟128维向量查询内存占用并发稳定性psycopg2pgvector兼容模式112ms42MB/连接高并发下连接池泄漏asyncpg 自定义向量解析89ms28MB/连接稳定但需手动处理VECTOR类型序列化cockroachdb/sqlalchemyORM156ms68MB/连接ORM抽象层开销大复杂查询易N1最终选择asyncpg并封装了向量工具类import asyncpg import numpy as np class VectorDB: def __init__(self, dsn): self.pool await asyncpg.create_pool(dsn) async def search_similar(self, query_vec: np.ndarray, limit10): # 将numpy数组转为CockroachDB接受的格式 vec_str f[{, .join(map(str, query_vec))}] async with self.pool.acquire() as conn: rows await conn.fetch( SELECT id, name, embedding - $1 AS dist FROM products WHERE price $2 ORDER BY dist LIMIT $3, vec_str, 500, limit ) return rows重点vec_str必须是[0.1, 0.2, ...]格式字符串不能是Python list否则驱动报错。3.2 表结构设计与向量索引维度、精度、分片的三角平衡一张表的设计决定了未来半年的性能天花板。我们以电商商品表为例拆解每个字段背后的权衡-- 生产环境推荐表结构 CREATE TABLE products ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), sku STRING UNIQUE NOT NULL, name STRING NOT NULL, description STRING, price DECIMAL(10,2) NOT NULL, category STRING NOT NULL, embedding VECTOR(128) NOT NULL, -- 关键维度选择 created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() ) LOCALITY REGIONAL BY TABLE IN PRIMARY REGION; -- 复合索引支撑混合查询 CREATE INDEX idx_category_price ON products (category, price); -- 向量索引HNSW配置详解 CREATE INDEX idx_products_embedding_hnsw ON products USING HNSW (embedding) WITH ( distance_function vector_l2_ops, -- 欧氏距离非余弦 m 30, -- 每层最大连接数影响图密度 ef_construction 100, -- 构建时搜索邻居数越大越准但越慢 ef_search 50 -- 查询时搜索邻居数越大越准但越慢 );维度选择128维不是玄学是算出来的向量维度直接影响存储、内存、计算三重开销。我们用真实数据测算商品标题经Sentence-BERT编码原始768维。用PCA降维到128维后语义相似度cosine similarity与原始向量的相关系数达0.982Pearson检验p0.001。存储空间768维向量占3.07KB/条128维仅0.51KB/条100万条节省2.5GB。查询延迟128维HNSW索引构建耗时1.2秒768维需8.7秒且P99延迟高37%。结论除非业务明确要求高维特征如医学影像否则128维是性价比最优解。距离函数选择为什么用vector_l2_ops而不是vector_cosine_opsCockroachDB目前仅支持欧氏距离L2和内积IP不支持余弦距离。但别慌——余弦相似度可转化为L2距离cosine_sim(A,B) dot(A,B) / (||A||*||B||) 当A、B已归一化||A||||B||1则 cosine_sim(A,B) dot(A,B) 而 L2^2(A,B) ||A||^2 ||B||^2 - 2*dot(A,B) 2 - 2*dot(A,B) 所以 dot(A,B) 越大 → L2距离越小 → 本质相同因此我们在AI服务端生成向量时强制归一化from sklearn.preprocessing import normalize embedding model.encode(text) embedding normalize(embedding.reshape(1,-1), norml2).flatten() # 归一化为单位向量这样embedding - query_vec的L2距离排序等价于余弦相似度排序。实测准确率无损。HNSW参数调优m、ef_construction、ef_search的黄金比例这些参数不是拍脑袋定的而是基于数据分布和QPS压测得出m30经验值。m决定图的连接度m30在128维数据上图平均度数约28平衡了查询精度高连接度和内存占用低连接度。m50时内存占用暴涨40%P99延迟仅降2ms不值得。ef_construction100构建索引时的“搜索深度”。我们用10万样本数据测试ef_construction100时索引构建耗时1.2秒召回率Recall10达0.992ef_construction200耗时2.8秒召回率仅升至0.995边际效益递减。ef_search50查询时的“搜索广度”。生产环境设为50P99延迟42ms若设为100延迟升至68ms召回率从0.992→0.996但业务方反馈“用户感知不到0.4%的提升”故取平衡点。实操心得ef_search应根据QPS动态调整。我们用Prometheus监控crdb_sql_select_latency_p99指标当该值连续5分钟50ms自动触发SET ef_search 30降低精度保延迟当30ms再升回50。这套自适应逻辑写在应用层比静态配置靠谱得多。3.3 应用集成从Embedding生成到实时检索的端到端链路一个完整的AI应用绝不是“数据库里存个向量”就完事。我们以“客服知识库RAG”为例展示生产级链路Step 1Embedding生成服务——轻量、可靠、可观测不用LangChain的HuggingFaceEmbeddings因其加载模型慢、内存泄漏。我们用FastAPIONNX Runtime自研# embed_service.py from fastapi import FastAPI import onnxruntime as ort import numpy as np app FastAPI() # 预加载ONNX模型避免每次请求加载 session ort.InferenceSession(all-MiniLM-L6-v2.onnx) app.post(/embed) def embed_text(texts: List[str]): # 批处理提升GPU利用率 inputs tokenizer(texts, paddingTrue, truncationTrue, return_tensorsnp) outputs session.run(None, { input_ids: inputs[input_ids], attention_mask: inputs[attention_mask] }) # 取[CLS] token输出归一化 embeddings outputs[0][:, 0, :] # [batch, 384] embeddings embeddings / np.linalg.norm(embeddings, axis1, keepdimsTrue) return {embeddings: embeddings.tolist()}关键设计批处理单次请求最多处理32个文本GPU显存利用率从35%→82%。健康检查/health端点返回{model_loaded: true, gpu_memory_used_gb: 4.2}接入Prometheus。降级策略当GPU OOM自动切换CPU推理ort.InferenceSession(..., providers[CPUExecutionProvider])延迟从80ms→320ms但保证可用。Step 2数据写入管道——事务一致性是底线绝不允许“先写数据库再调用Embedding服务”。必须用事务包裹# app.py from cockroachdb.sqlalchemy import run_transaction def upsert_product(product_data: dict): def callback(session): # 1. 先生成向量同步调用 resp requests.post(http://embed-service/embed, json{texts: [product_data[description]]}) embedding resp.json()[embeddings][0] # 2. 在同一事务中写入 stmt text( INSERT INTO products (sku, name, description, price, category, embedding) VALUES (:sku, :name, :desc, :price, :cat, :emb) ON CONFLICT (sku) DO UPDATE SET name EXCLUDED.name, description EXCLUDED.description, price EXCLUDED.price, category EXCLUDED.category, embedding EXCLUDED.embedding, updated_at now() ) session.execute(stmt, { sku: product_data[sku], name: product_data[name], desc: product_data[description], price: product_data[price], cat: product_data[category], emb: f[{, .join(map(str, embedding))}] }) run_transaction(engine, callback) # CockroachDB专用事务函数run_transaction会自动处理事务重试如遇到SerializationFailure这是CockroachDB分布式事务的必备能力。Step 3实时检索API——混合查询的性能密码最终API需支撑“语义业务规则”双重过滤app.get(/search) def search_products( query: str, min_price: float Query(0), max_price: float Query(1000), category: str None ): # 1. 生成查询向量 emb embed_service.embed_text([query])[embeddings][0] emb_str f[{, .join(map(str, emb))}] # 2. 构建混合查询 where_clauses [price $1 AND price $2] params [min_price, max_price] if category: where_clauses.append(category $3) params.append(category) where_sql AND .join(where_clauses) params.append(emb_str) # 向量参数 params.append(10) # limit # 3. 执行查询关键使用$1,$2占位符防SQL注入 sql f SELECT id, name, description, price, round((embedding - ${len(params)-1})::numeric, 4) AS similarity FROM products WHERE {where_sql} ORDER BY embedding - ${len(params)-1} LIMIT ${len(params)} rows await db.fetch(sql, *params) return {results: [dict(row) for row in rows]}性能密码在于ORDER BY embedding - $NCockroachDB会自动利用HNSW索引而非全表扫描。实测100万商品库该API P95延迟稳定在92ms。3.4 性能压测与调优用真实数据验证SLA所有设计都要经受住压测。我们用k6模拟真实流量// test.js import http from k6/http; import { check, sleep } from k6; export const options { stages: [ { duration: 30s, target: 100 }, // ramp up { duration: 2m, target: 100 }, // sustain { duration: 30s, target: 0 }, // ramp down ], }; export default function () { const query wireless noise cancelling headphones; const res http.get(http://api/search?query${query}min_price0max_price500); check(res, { status was 200: (r) r.status 200, p95 latency 100ms: (r) r.timings.p95 100, }); sleep(1); }压测结果与调优动作指标初始值目标值调优动作结果P95延迟142ms100ms增加--cache6GB--max-sql-memory6GB↓至98msCPU使用率92%75%调整HNSWef_search40原50↓至68%延迟微增至102ms可接受内存RSS12.4GB10GB启用--temp-dir/fast-nvme/tmp避免/tmp占满根分区↓至9.1GBRaft Leader切换次数3次/分钟0修复网络抖动--locality补全0最关键的发现向量查询的瓶颈不在CPU而在网络IO。当ef_search设得过高节点需从其他Range拉取大量候选向量跨AZ网络带宽被打满。解决方案是在应用层做两级缓存——L1Redis缓存query_hash - top10_idsTTL300秒命中率62%L2CockroachDB自身Page Cache--cache参数缓存热点HNSW图节点减少磁盘IO。最终100 QPS下P95延迟稳定在98msCPU均值65%完美达成SLA。4. 故障排查与避坑指南那些文档里不会写的真相4.1 常见问题速查表从报错信息直达根因报错信息根本原因解决方案pq: vector dimension mismatch: expected 128, got 768应用层传入的向量维度与表定义不符检查Embedding服务输出维度用SELECT pg_typeof(embedding) FROM products LIMIT 1确认表定义pq: failed to execute query: context deadline exceededHNSW查询超时通常因ef_search过大或网络延迟降低ef_search值检查crdb_internal.node_status确认节点网络健康pq: invalid input syntax for type vector: [...]向量字符串格式错误含空格或换行用json.dumps(vec.tolist())生成字符串而非str(vec)确保无\n字符pq: insufficient resources: memory usage exceeds limit--max-sql-memory不足HNSW搜索耗尽内存增加--max-sql-memory或降低ef_search监控crdb_sql_mem_usage指标pq: could not serialize access due to concurrent update分布式事务冲突常见于高频更新同一SKU使用run_transaction自动重试或改用UPSERT避免冲突一个血泪案例某次上线后SELECT ... ORDER BY embedding - $1查询大量超时。我们查crdb_internal.node_metrics发现sql.select.latency指标飙升但CPU、内存正常。最终定位到ef_search100导致单次查询需从3个远程Range拉取数据而跨AZ网络带宽被另一批ETL任务占满。解决方案不是调参数而是给向量查询流量打标通过网络QoS限速确保其带宽不低于500Mbps。4.2 独家避坑技巧来自凌晨三点的实战笔记坑一向量索引重建服务中断不可以热替换官方文档说DROP INDEX ...会阻塞写入。但我们发现一个黑科技-- 1. 创建新索引不阻塞 CREATE INDEX idx_products_embedding_hnsw_v2 ON products USING HNSW (embedding) WITH (...); -- 2. 等待新索引构建完成查crdb_internal.index_usage -- 3. 原子切换毫秒级不阻塞 ALTER INDEX idx_products_embedding_hnsw RENAME TO idx_products_embedding_hnsw_old; ALTER INDEX idx_products_embedding_hnsw_v2 RENAME TO idx_products_embedding_hnsw;原理CockroachDB的索引元数据在