Oracle 23ai架构图:向量内存池与检索链路的排障视图
发布时间:2026/11/2 20:03:57 锦皓数字建站

Oracle 23ai 发布之后我前后画过三版架构图前两版都被自己推翻了。第一版几乎是照着官方文档的示意图改了个标题画完自己都不好意思拿出来——那玩意儿贴到工位上排障的时候一点用没有因为图里没有我实际环境中的容器分布也没有那块新多出来的向量内存。第二版倒是把向量相关的东西堆上去了结果线条乱得像蜘蛛网PDB 级的参数和 CDB 级的参数混在一层越看越糊涂。到第三版我把画图这件事拆成了两个独立视图一张回答实例里的东西摆在哪一张回答一次向量检索的数据怎么流。这两张图现在成了我们团队新人入职第一周的必读材料也是我自己半夜被叫起来排查慢查询时第一眼要看的东西。下面就把这两张图从动机、结构、组件取舍到画完之后的验证过程完整地摊开讲一遍包括我自己在画的过程中被纠正的四个认知偏差。1. 这两张图不是文档插图是排障时随手能翻的地图先说清楚我为什么非要自己画。官方文档里的架构图信息量是够的但它是通用视图——为了覆盖所有部署形态它必须画得抽象。抽象到你在真实环境里看到一条慢 SQL图上找不到任何一个可以直接点击去查的落点。比如文档图里 SGA 就是一个大框里面分了 Buffer Cache、Shared Pool、Large Pool 几块你在图上根本看不出我这个 PDB 到底分到了多少向量内存因为那玩意儿在 23ai 之前压根不存在。架构图对我而言有三个具体用途按重要性排序第一是排障时的索引看到某个现象能立刻定位到这属于哪一层的问题缩小排查范围第二是给团队做知识传递一张画得准的图能省掉大量口头解释第三是自我检查画的过程会强迫你把每个组件之间的依赖关系过一遍很多想当然的地方在落笔时就会露馅。第三个用途是意外收获也是我后来坚持每上一个新版本就重画一版的原因。这两张图的定位也不一样。我把第一张叫 A 图运行时视图静态的回答东西在哪、占多大、谁管着谁第二张叫 B 图数据流视图动态的回答一行文本从进来到变成 Top-K 结果中间经过哪些环节、每个环节的输入输出是什么。两张图放一起看A 图是地形B 图是路线。只看地形不知道路怎么走只看路线不知道哪里有坑。提示如果你也想画自己环境的架构图先别急着开工具。拿一张 A4 纸用铅笔把你最怕出问题的那条链路画出来画不下去的地方就是你需要补知识的地方。这一步比后面调颜色、调对齐重要得多。还有一个现实因素23ai 引入的东西太多太散。向量检索、JSON 关系二元性、库内 JavaScript、True Cache、SQL Domain、Boolean 类型、优先级事务这些东西在文档里是一章一章分开讲的但实际上它们在运行时是叠在同一个实例上的。你如果脑子里没有一个统一的视图很容易出现A 特性开了之后 B 特性的表现变了这种连锁困惑。我画这两张图本质上是在给自己建一个能装下所有新特性的容器。2. A 图把向量内存池塞进 SGA 之后实例视图变了哪里2.1 先画存储与容器层再回头画内存我第一版是从 SGA 开始画的画到一半卡住了因为我不知道向量内存到底该挂在哪个节点下。后来把顺序反过来先从最底下的数据文件画起往上依次是表空间、数据文件所属的 PDB、PDB 所在的 CDB、CDB 对应的实例、实例的内存与进程。这个自下而上的顺序看着反直觉但它有一个好处——每一层都是上一层物理上真实存在的东西你不会画出一个悬空的概念框。画到容器层的时候我特意把 CDB$ROOT 和各个 PDB 用了不同的底色区分并且在 PDB 框的右上角标了这个 PDB 的业务归属和大致数据量。这个细节在正式文档图里肯定不会有但它对我极其有用——半夜看监控告警说某个 PDB 的临时表空间涨了我一眼就能知道这是哪个业务线在跑什么批处理。存储层我画得比较粗只标了数据文件、在线重做日志、归档路径和控制文件四类另外单独拉了一条线指向备份介质。粗是有意的因为 A 图的目的是建立整体位置感不是做存储规划。真要算容量我有另外一套表格不在这张图上。2.2 SGA 新成员向量内存池在图上的位置与边界23ai 里 SGA 的结构变化核心就是多了一块给向量索引和向量运算用的内存区域由VECTOR_MEMORY_SIZE这个参数控制大小。我在 A 图上把它画成和 Buffer Cache、Shared Pool 并列的一块独立区域而不是塞在 Shared Pool 里面。为什么这么画因为它的生命周期和管理方式跟 Shared Pool 完全不同——Shared Pool 里放的是游标、执行计划这些可以随时淘汰的东西而向量池里放的是索引结构淘汰重建的代价非常高。这里有个细节值得单独说向量池的大小是可以按 PDB 分配的。也就是说在 CDB 层面设一个总盘子然后各个 PDB 各自申领一块。我在图上用两种线型表达了这件事——实线表示 CDB 级的参数设置虚线表示 PDB 级的覆盖。这个表达方式是我在图被自己看糊涂两次之后才定下来的前面说了第二版就是因为把这两级混在一层画导致我排查时误判了参数生效位置。参数的实际值我建议你直接查不要记SHOW PARAMETER vector_memory_size SELECT con_id, name, value, ispdb_modifiable FROM v$parameter WHERE name vector_memory_size;ispdb_modifiable这一列很关键它告诉你这个参数能不能在 PDB 级别单独设。不同小版本上这个值可能会有变化所以别背结论现场查。2.3 后台进程那一栏我没有照抄官方图官方图里后台进程画得挺全DBWn、LGWR、CKPT、SMON、PMON、ARCn 一应俱全。我在自己的图上做了删减和增补删掉了几个平时根本不会去关注的增补了几个跟 23ai 新特性相关但我一开始完全没意识到的。其中一个是被我漏掉又补上的向量索引的构建过程会消耗大量内存和临时空间这个构建动作不是一个独立进程完成的它是在服务进程里跑的但它对内存池的申请行为跟普通查询完全不一样。我在图上加了一条从会话服务进程到向量内存池的粗箭头旁边标注了索引构建期峰值占用用来提醒自己建索引的时候别在同一实例上跑大查询。另一个是参数文件的位置。我把 SPFILE 单独画了一个小框用虚线连到实例旁边写了一行备注PDB 级参数修改需要落到 CDB 的 SPFILE。这行备注是血泪——有一次在一个测试 PDB 里改完参数重启之后发现没生效就是因为没搞清楚参数最终落在哪。2.4 CDB 与 PDB 的参数继承用两种线型表达这一小节单拎出来讲因为它是我认为 A 图里最容易画错、也最容易在排查时误导人的地方。Oracle 多租户的参数机制是一部分参数只能在 CDB 根上设一部分可以在 PDB 上覆盖还有一部分是 PDB 设了也不生效的。这三类如果只用一种线画图就废了。我的做法是三条规矩参数类型图上表达典型例子仅 CDB 级可设红色实线连到 CDB 框与实例整体资源相关的参数PDB 可覆盖蓝色虚线连到对应 PDB 框VECTOR_MEMORY_SIZE、各类 PDB 级限制PDB 设置不生效灰色点线旁边打叉部分与 SGA 结构强绑定的参数这张表我直接贴在图的右下角作为图例的一部分。这样任何一个人看图的时候不需要再问我这条虚线什么意思。顺便说一个实操经验改 PDB 级参数的时候我习惯先用ALTER SESSION SET CONTAINER xxx切进去改完之后立刻在当前会话里SHOW PARAMETER验证一次然后再去 CDB 根上看一眼。两次都要看只看一次容易产生已经生效了的错觉实际上可能只是会话级别的临时值。3. B 图一行文本走到 Top-K 的完整链路3.1 嵌入在库外生成还是库内生成图上要标清楚B 图从左边开始画起点是一个文档进来源。这里第一个分叉点就是向量embedding在哪生成。有两条路一条是在应用侧用外部模型算好了再写进数据库另一条是把模型加载进数据库用库内函数直接在 SQL 里算。我在图上把这两条路都画了用不同颜色区分并且标注了各自的代价。库外生成的好处是灵活模型想换就换不占数据库资源坏处是多一次数据搬运而且写入时要注意向量维度和数据类型必须与列定义严格对齐对不齐会直接报错。库内生成的好处是链路短插入的同时就把向量算好了适合那种数据进来马上就要能检索的场景坏处是模型加载和推理会吃数据库的 CPU 和内存得提前评估。-- 库内生成向量的大致形状 INSERT INTO doc_chunks (content, emb) VALUES (:txt, VECTOR_EMBEDDING(my_embed_model USING :txt AS DATA));这段 SQL 里my_embed_model是提前注册好的模型名不同版本的注册方式略有差异具体用法以你手上版本的文档为准。我之所以在图上把库内这条路也画出来是因为它直接决定了 A 图里向量池的压力来源——如果你全部走库内生成那向量池和 CPU 的规划就完全是另一个量级。3.2 VECTOR 列的维度与精度是图上最容易被省略的关键信息很多架构图把表就画成一个矩形写个表名完事我在这张图上特意把关键的向量列单独标了出来写清楚三件事维度、元素类型、距离度量。这三件事为什么重要维度决定了单个向量占多少字节进而决定了索引的内存占用元素类型FLOAT32、FLOAT64、INT8之类同样影响存储和计算精度距离度量余弦、欧氏、点积等必须和你的业务语义匹配最重要的是——查询时用的距离函数必须和建索引时声明的一致不一致会导致索引失效退化成全表扫描。CREATE TABLE doc_chunks ( chunk_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tenant_id NUMBER NOT NULL, content CLOB, emb VECTOR(768, FLOAT32) );我在图的这个节点旁边算了一笔账768 维、FLOAT32单个向量固定占 3KB 左右一百万行就是 3GB 的裸数据加上索引结构通常会翻倍甚至更多。这笔账不算清楚VECTOR_MEMORY_SIZE设多少就完全是拍脑袋。提示维度一定要在项目初期定死。中途换模型导致维度变化意味着整表的向量列要重建、所有索引要重建。我见过因为这事儿返工两周的案例代价极高。3.3 HNSW 与 IVF 在图上长得不一样内存结构与构建期23ai 支持不止一种向量索引结构最常见的是基于图的 HNSW 和基于分区的 IVF。这两种在 B 图上我用了完全不同的画法因为它们的运行时行为差别很大。HNSW 我画成一张网状的图结构节点之间有多层连线旁边标注全内存、构建期内存峰值高、查询快、插入有代价。IVF 我画成一堆被切成扇区的圆盘旁边标注先聚类再分桶、构建期需要采样、内存占用相对可控、查询时要扫多个桶。-- 图结构索引的大致写法 CREATE VECTOR INDEX idx_chunk_emb ON doc_chunks (emb) ORGANIZATION INMEMORY NEIGHBOR GRAPH DISTANCE COSINE WITH TARGET ACCURACY 90; -- 分区式索引的大致写法 CREATE VECTOR INDEX idx_chunk_emb_ivf ON doc_chunks (emb) ORGANIZATION NEIGHBOR PARTITIONS DISTANCE COSINE WITH TARGET ACCURACY 90;WITH TARGET ACCURACY这个子句是 23ai 向量索引里我觉得最值得在图上单独标出来的东西。它表达的是一个权衡目标精度越高索引需要维护的结构越复杂、内存越大、构建越慢。图结构索引在精度上的表现通常更好但构建期的内存峰值也更高——这就是为什么我在 A 图上专门画了那条构建期峰值占用的箭头。3.4 查询侧的过滤条件与混合检索怎么画B 图的右半边是查询侧。这里的核心问题是向量相似度检索经常要和普通的结构化过滤条件组合使用比如在这个租户下、这个时间段内、类型是合同的文档里找最相似的十条。这种组合查询的执行路径跟纯向量检索完全不同图上必须画出来。我画的做法是把查询侧拆成三个并行的输入向量输入、标量过滤条件、返回条数然后汇入一个执行计划选择节点最后分两条路径出去——一条走向量索引再回表过滤一条走标量索引先缩小范围再做向量比较。哪条路径被选中取决于数据分布和优化器的判断。这个分叉在图上是用菱形表达的。实践中最容易出问题的是过滤条件的选择性。如果过滤之后剩下的行数很少优化器很可能干脆放弃向量索引直接全表扫一遍再排序——在小数据量下这反而更快但数据量一大就崩了。我在图上这个菱形旁边写了一行备注关注执行计划里是否真的走了向量索引。4. 画图这半天的工具选择和图层规矩4.1 我为什么放弃自动生成、回到手动矢量绘图一开始我想偷懒试着用各种自动生成工具从配置文件或者字典视图里直接吐出架构图。结论是省下来的时间全部赔在修改上。自动生成的图有两个致命问题——它不知道你想强调什么也不知道哪些细节可以省略它把所有东西画得一样大导致图没有视觉重心。后来我回到手动矢量绘图用的是最普通的那类工具能导出 SVG 就行。手动的代价是要花时间摆位置好处是每一个框的大小、每一条线的走向都是我自己决定的而我决定它们的过程本身就是一次梳理。这里有个取舍手动画的图环境一变就得改。我的应对是把图拆成稳定层和易变层——容器层次、数据流向这些相对稳定画在主干上具体的 IP、端口、数据量、参数值这些易变的全部画成独立的标注框改了直接换标注框就行不用动主干。4.2 三条图层规矩颜色管生命周期、线型管数据流向、命名管可检索这三条规矩是被返工逼出来的。第一版图我用了七八种颜色纯凭感觉配的结果过了一个月自己都看不懂哪种颜色代表什么。现在的规矩是这样的颜色只用来表达生命周期和归属比如蓝色系是常驻内存的、绿色系是持久化存储的、橙色系是一次性的临时对象。线型只用来表达数据流向和依赖关系实线是同步调用、虚线是异步、点线是配置依赖。命名规矩最土但最有用——每个框都有一个唯一的名字这个名字和我在监控系统、在字典视图查询里用的名字保持一致这样从图到命令行是无缝的。图层元素表达含义反例蓝色填充常驻内存结构用蓝色表示重要绿色填充持久化存储用绿色表示正常状态橙色填充临时或过渡对象用橙色表示警告实线箭头同步调用路径用实线表示关系密切虚线箭头异步或后台路径随意切换虚实点线配置继承或依赖用来表示数据流最后一行那个反例是我真犯过的错用点线画数据流结果看图的人以为是配置关系排查方向直接跑偏。4.3 版本落款和变更记录一个几乎没人做但极省事的习惯每张图的右下角我都强制写三行版本号、绘制日期、对应的数据库小版本号。第三行最关键——数据库打个小补丁某些视图或者参数行为就可能变图上标的结论就未必成立了。变更记录我单独维护了一个短表格只记这一版改了什么、为什么改不记细节。比如第三版新增向量内存池节点因为排查时发现参数生效位置判断错误。这个表格的价值在于半年后你看到某个结论觉得奇怪翻一下变更记录就知道当时的判断依据是什么。图 A 变更记录 v1 初版照搬通用结构实用价值低 v2 加入向量相关组件线条混乱参数层级混淆 v3 拆分为运行时视图明确 CDB/PDB 两级参数表达 v4 补充备份路径修正向量池边界描述这四行字比图上任何一个漂亮的圆角矩形都值钱。5. 落笔之后暴露的四个认知偏差5.1 向量内存池够大不代表索引就全在内存里这是我第一个被纠正的想法。我最初以为把VECTOR_MEMORY_SIZE调大索引就会老老实实待在内存里。实际情况是这个池子有它自己的管理策略索引在池子里的驻留与否还跟访问频率、并发情况有关。池子不够大肯定出问题但池子够大不等于万事大吉。排查的时候需要看的视图和普通内存组件不太一样我一般会先把所有跟向量内存相关的固定视图列出来SELECT name FROM v$fixed_table WHERE name LIKE %VECTOR%;这个查询很有用因为不同小版本之间视图名会有增删先列出来再挑着查比死记硬背强。查到之后再去看实际的占用和命中情况。5.2 同一个列上建两个索引账要合起来算测试环境里我为了对比效果在同一个向量列上先后建了两种不同结构的索引。当时觉得反正测试库内存大无所谓。后来把配置往预生产搬的时候才发现两个索引加起来的内存需求远超预期参数按单索引估算的根本不够。这件事在图上的体现是B 图里同一个向量列节点出来了两条线分别指向两个不同的索引结构。第一版图里我只画了一条线因为当时脑子里默认一列一索引。改图的时候我在这个节点旁边加了个小小的乘号标注提醒自己索引数量要乘上去算。实践中我的建议是生产环境同一个向量列上只保留一个索引需要对比效果就在独立环境里做别在同一个实例上堆。5.3 参数生效位置画反了后来排查多花了两小时第二版 A 图里我把VECTOR_MEMORY_SIZE的实线连到了 PDB 框上意思是在 PDB 里设的。实际排查一个内存不足的问题时我一直在 PDB 里查、在 PDB 里改改完没反应折腾了快两个小时才想起来去 CDB 根上看——原来总盘子是在根上设的PDB 只是分配。这个坑的教训被我用最笨的方式固化下来了在 A 图上凡是我曾经判断错过的节点旁边加一个小小的三角形标记。现在图上有五个三角形每一个背后都是一次真实的排查耗时。这个做法看起来很土但它是这张图区别于任何官方示意图的地方——它是被真实问题打磨过的。5.4 备份恢复路径在图上是缺的前三个版本我完全没画备份。理由很自然备份是运维的事跟架构有什么关系。直到有一次要做恢复演练我对着图琢磨这个 PDB 的恢复会影响到哪些东西才发现图上根本没有这条路径只能临时翻文档。第四版我补上了从归档路径到备份介质的一条线从备份介质回到 CDB 根和各个 PDB 的两条线旁边标注了恢复的粒度和大致耗时。补完之后我才意识到这条路径其实反过来约束了 A 图的其他部分——比如 PDB 的分布方式会直接影响单 PDB 恢复的可行性。提示架构图上如果找不到出事了怎么退回去的路径这张图就只完成了一半。备份恢复这条线不难画但特别容易被跳过。6. 用几条 SQL 把图上的每个框点一遍图画完之后我做了一件事把图上每一个能查的节点都用一条 SQL 点一遍确认图上的描述和实际运行状态一致。这个动作我叫它点框,做完之后图的可靠度会完全不一样。6.1 内存与参数自检先确认参数值和图上标的一致SELECT con_id, name, value, isdefault, ispdb_modifiable FROM v$parameter WHERE name IN (vector_memory_size, sga_target, sga_max_size) ORDER BY con_id, name;这条查询返回的con_id列能直接告诉你当前是在哪个容器里看的值比切来切去看SHOW PARAMETER更清楚。我一般在 CDB 根上跑一次然后切到每个 PDB 再跑一次把结果对着图逐行核对。接着看向量内存的实际使用SELECT * FROM v$vector_memory_pool;如果这个视图在你手上版本里查不到别慌先回上一步那个v$fixed_table的查询里找找带 VECTOR 字样的视图名。视图名在小版本之间调整过属于正常现象。6.2 索引与执行计划对照确认索引确实建在你想的那个列上并且结构类型和图上画的一致SELECT index_name, index_type, status, -- 视图列名以实际版本为准 tablespace_name FROM user_indexes WHERE index_type LIKE %VECTOR%;然后是最关键的一步——把一条真实的近似检索查询跑一遍看执行计划EXPLAIN PLAN FOR SELECT chunk_id, VECTOR_DISTANCE(emb, :q, COSINE) AS dist FROM doc_chunks WHERE tenant_id 42 ORDER BY dist FETCH FIRST 10 ROWS ONLY; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);计划里是否真的用上了向量索引是 B 图那个菱形节点的实际答案。如果发现走了全表扫描先别急着调参数先看数据量和过滤条件的选择性——小表全扫往往是最优解强行让它走索引反而更慢。6.3 一次近似查询走全表的定位复现我把一次真实的排查过程完整记下来了因为它很好地验证了 B 图的价值。现象一个原本几百毫秒的查询突然变成七八秒。按 B 图的路径从上往下走第一站看查询语句本身——距离函数和建索引时声明的是不是同一个。这次是同一个排除。第二站看过滤条件——业务侧新加了一个过滤字段选择性很高。第三站看执行计划——果然退化成全表扫描加排序。原因清楚了过滤条件的新增让优化器判断先过滤再算向量距离比走向量索引再回表更划算但实际数据分布下这个判断是错的。应对方式是把过滤条件和向量检索拆成两步或者调整查询写法引导优化器。这个过程如果没有 B 图我大概率会先去查内存参数、查索引状态绕一大圈才回到执行计划上。7. 这两张图接下来我打算怎么长现在这两张图还缺几块我觉得迟早要补的内容。一块是库内 JavaScript 相关的执行环境因为它在运行时也会占用一部分资源跟向量池之间存在资源竞争的可能另一块是 JSON 关系二元性视图的访问路径这块目前在我们的业务里用得还不多但趋势看是要用的。另外一个打算是把 A 图做成两个版本一个逻辑版去掉所有具体数值只保留结构和依赖用来做培训一个实例版把当前环境的真实参数、真实数据量、真实 IP 都标上去用来排障。现在这两个用途挤在一张图上培训的时候要遮遮掩掩排障的时候又嫌不够细。我个人在实际操作中的体会是架构图这件事的价值不在图本身而在画图和点框的过程中被迫做的那些核对。图会过时但每上一个新版本就重画一遍、每画一个框就去查一次真实状态这个习惯不会过时。我见过太多团队把架构图当成一次性的交付物画完往文档库里一扔两年后新人打开一看里面的组件名字都对不上当前环境了那张图就彻底死了。真正活着的架构图是那种边角上有三角形标记、右下角有版本落款、旁边还挂着一张变更记录表的图——它看起来不完美但每一处都对应着一个真实踩过的坑。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。