乌克兰最大的法院数据库是如何被向量化的:3370万份判决,一个向量数据库

乌克兰最大的法院数据库是如何被向量化的:3370万份判决,一个向量数据库

通过语义搜索让公开的司法记录可被检索

想象一下,如果律师只需输入一段通俗易懂的自然语言提问,就能立即获取 5 份最相关且附带关键段落的法院裁决——而不是在成千上万个关键词匹配结果中大海捞针。这正是语义搜索所能实现的愿景。然而,要在 3370 万份法院裁决中落地这一功能,则完全是另一场严峻的挑战。

EDRSR(国家法院判决统一登记处)已将乌克兰的整个司法记录向公众开放。现在,一个团队正在努力使所有这些文本真正具备可搜索性。

为什么这在当下至关重要

到 2026 年,法院判决在乌克兰属于公开数据,但仍难以进行有效检索。查找特定问题先例的律师不得不使用笨拙的关键词搜索,这会返回成千上万条无关的结果。系统并不理解 含义 ——它只是查找包含你输入的文字的文档。而通过基于向量嵌入(vector embeddings)的语义搜索,律师可以询问他们真正想了解的内容:“是否有关于追讨银行提前还款手续费的判例法?”系统会找到 5 份最相关的裁决,提取出关键段落,并展示法院的推理过程。

但在实现语义搜索之前,你必须先将文本向量化。而乌克兰法院系统的数据规模绝非小问题。

规模

该登记处保存着追溯至 2006 年的判决。具体分类如下:

  • 民事案件(CPC):3370 万份文档——最大的类别
  • 刑事案件(CrPC):1200 万+
  • 行政案件(CAS):1400 万+
  • 商事案件(CC):600 万+
  • 轻罪/行政违法案件(CUaP):600 万+

截至目前,Qdrant 向量数据库已容纳 4400 万+ 个向量。民事案件的处理已完成 42%(3370 万份中已处理 1430 万份)。民事部分处理完成后,集合将容纳约 6300 万+ 个向量——比典型的 RAG 项目(可能拥有 10 万到 100 万个向量)高出两个数量级。

处理如此庞大的文档数量,意味着需要构建一个在半途不会崩溃的管线(pipeline)。

技术栈

团队选择了经过验证且务实的技术方案:

嵌入模型: Voyage AI 的 voyage-3.5,输出 1024 维度的向量。他们测试了 Voyage 3 Large 和 OpenAI 的 text-embedding-3-large,但发现其在法律文本上的质量提升不足以弥补成本差异。Voyage 3 Large 的价格要高出三倍。

向量数据库: Qdrant v1.17,部署在专用 Amazon EC2 实例(r6a.xlarge:4 CPU,32 GB 内存,2 TB gp3 存储)上的 Docker 中。他们为其分配了独立实例,因为带 HNSW 索引的 4400 万+ 数据点导致生产数据库内存不足(OOM),并彻底阻塞了对话服务。

单一信任源(Source of truth): PostgreSQL 15,表结构按裁决日期进行分区。完整的法院文本保存在一个表中,元数据保存在另一个表中。跨所有分区的 JOIN 会触及 3000 万+ 行数据,因此该管线每次处理一年的数据。

管线运行时: Python 3.11、asyncio、aiohttp。没有重型框架——仅直接发起针对 Voyage 和 Qdrant 的 HTTP 调用。整个代码文件仅 440 行。

他们如何拆分任务

法院判决书非常 。一份普通民事裁决通常长达 8,000 至 12,000 个字符。有些甚至达到 200,000 个字符。Voyage 每个输入最多接受 32,000 个 token,但在长上下文下质量会下降,而且单一的长向量对于检索毫无用处——语言模型无法精确定位哪个段落是相关的。

因此团队进行了文本分块(chunking):

  • 每个分块最多 2,048 个字符
  • 相邻分块之间有 50 个单词的重叠(以保留边界处的上下文)
  • 按段落边界拆分,以保持语义连贯性

平均而言,一份判决能生成 2.7 个分块。每个分块在 Qdrant 中获得一个组合 ID(doc_id × 1000 + chunk_index),这使得单个 payload 过滤器能够提取出一份判决的所有分块。

速度与并发

Voyage 有速率限制:每个 API key 每分钟 2,000 次请求(RPM)。团队使用了两个密钥并进行轮询(round-robin),达到了理论上 4,000 RPM 的上限。

他们将并发量维持在 50 个并发请求,稳定实现每秒处理 63 份文档。这相当于每个密钥每分钟约 170 次请求——远低于限制。他们曾尝试 70 的并发量,但遭遇了 Python GIL(全局解释器锁)瓶颈:进程卡在 13% 的 CPU 利用率,没有任何进展,也没有抛出任何错误。就那样挂起了。降回 50 后运行流畅。

每处理 100 份文档,他们会将 500 个分块打包批量发送给 Voyage,收集嵌入向量,构建 Qdrant 数据点并进行 upsert 操作。遇到错误(429 速率限制、网络超时)时,他们使用带抖动(jitter)的指数退避策略,最多重试 5 次。

节省了数周时间的检查点(Checkpoint)机制

在 3370 万份文档的规模下,任何失败都意味着数小时或数天的工作白费。团队构建了一个检查点系统:每处理 1,000 份文档,管线就会写入一个包含最后文档 ID、数量、所用 token 数和时间戳的 JSON 快照。

重启时,它会读取该检查点并从 WHERE doc_id > last_doc_id 恢复执行。无需重复劳动,无需从零开始。

这已经两次救了他们。一次是在 PostgreSQL 内存耗尽时(详情见下文)。另一次是在 Qdrant 重启并丢失环境变量中的 API key 时。

一起生产事故:Postgres 内存溢出(Out of Memory)

在处理到 286 万份文档时,PostgreSQL 陷入了恢复模式。根本原因:配置不匹配。

数据库设置为 shared_buffers=16GB,但容器的内存限制为 12GB。PostgreSQL 试图分配超过其拥有的内存;操作系统杀死了该进程。

修复方案(PR #1453)将容器限制提升至 24GB,并将 shm_size 提升至 16GB。重启后,PostgreSQL 在 4 秒内恢复上线并保持稳定。

经验教训: PostgreSQL 配置参数必须与容器内存限制相匹配。 在首次负载突增之前,系统一直运行良好,随后就会非优雅地崩溃。

他们还将开发机上的 swap 空间从 8GB 提升到了 24GB,因为密集的 Voyage API 流量会在 Python 进程中产生大量临时对象。

截至目前的账单

一份民事文档平均包含 2.7 个分块 × 850 个 token = 2,300 个 token。按照 Voyage 每百万个 token 6 美分的定价,每份文档只需 0.014 美分——约合 138 微美元(microdollars)。

截至目前(已完成 42%):

  • 已处理 1,430 万份文档
  • Voyage API 支出约 1,980 美元
  • 管线运行时间约 63 小时

剩余部分(还剩 58%):

  • 1,940 万份文档
  • 预估 Voyage 成本约 2,680 美元
  • 预估运行时间 85 小时(约连续运行 3.5 天)

整个民事类目的总成本: API 费用大约为 4,660 美元。

按需计费的专用 EC2 实例每小时约 0.20 美元——每月约 145 美元。这比在生产环境中从 OOM 事故中恢复要便宜得多。

作为对比:在 OpenAI 的 text-embedding-3-large 上使用相同的预算只能向量化四分之一的数据量。在这种规模下,选用 Voyage 在经济上更为合理。

它所带来的可能性

一旦管线完成,该集合将包含跨所有民事案件的 6300 万+ 个向量。律师输入自然语言查询——“关于因卖方缺乏行为能力而撤销买卖合同的判例法”——系统就会呈出来自相应管辖区域的最相关判决,并附带关键段落摘录以及指向 EDRSR 的链接。

这就是面向整个乌克兰民事法院系统的语义搜索。

结语

对 3370 万份法院判决进行向量化绝非小型的工程问题。它需要在成本与质量的权衡中选择合适的嵌入模型,隔离向量数据库以避免挤占生产资源,仔细管理并发以避免 GIL 死锁,并在长达 100+ 小时的管线中构建容错机制。乌克兰的司法记录现在正被转型为一个可搜索的知识库。

优点

  • 基于全文的语义搜索。 律师获得的是答案,而非简单的关键词匹配。
  • 大规模下的高成本效益。 在处理这一数据量时,Voyage AI 比替代方案便宜 4 倍。
  • 容错设计。 检查点机制使管线能够抵御失败而无需重复执行。
  • 实用基础设施。 在独立容器中运行 Qdrant,配备正确内存边界的 PostgreSQL——这些设计决策保护了生产环境。
  • 记录详尽的事故。 Postgres OOM 失败成为了关于配置匹配的清晰教训。

缺点

  • 管线运行时间长。 还剩 85+ 小时意味着需要连续运行数周;尽管有检查点,基础设施仍可能在运行中途发生故障。
  • 规模比典型项目高出两个数量级。 6300万+ 向量属于未经证实的领域;依然存在扩展风险。
  • 专用硬件成本。 r6a.xlarge 实例是必要的,但增加了持续的运营开销。
  • 对语言模型的依赖。 质量取决于嵌入模型;如果 Voyage 调整价格或服务,计算模型就会随之改变。
  • 未提及查询延迟。 文章未讨论在 6300万 个向量上进行语义搜索实际执行的速度有多快。

注意事项

本文仅供参考教育用途,描述了源材料中所报道的真实项目。任何实际实施都应根据当前的 Voyage 定价核实成本数据,在您自身的环境中结合自身数据测试 PostgreSQL 配置参数,并验证并发设置(此处适用的 50 个并发请求可能并不适合所有系统)。Postgres 的 shared_buffers 值必须与您实际的容器内存相匹配。在依赖任何具体数字或方法之前,请先查阅原始出处和您自身的基础设施。

常见问题解答

  • 什么是 EDRSR?为什么乌克兰要向公众公开所有法院判决?
  • 向量嵌入是如何工作的?为什么它们在法律文档处理上优于关键词搜索?
  • 为什么团队使用 Qdrant 而非 Pinecone 或 Milvus 等其他向量数据库?
  • 什么是 GIL 死锁?为什么 70 的并发量会导致管线卡死挂起?
  • 基于检查点的恢复机制是如何工作的?它会增加多少重启开销?
  • 为什么 PostgreSQL 配置与容器内存限制的匹配如此关键?
  • 在 3370 万份文档的规模下,Voyage AI 与 OpenAI 嵌入之间的成本差异是多少?
  • 在 6300万+ 向量集合上,执行一次语义搜索查询需要多长时间?

标签

#vectorsearch #qdrant #voyageai #legaltech #ukraine #semanticsearch #ragapplications #scalinginfrastructure

Free field guide

Docker Security Checklist

Lock down your containers from build to runtime — 29 practical controls covering images, runtime flags, secrets, and the daemon. Enter your email — you'll get the PDF instantly, plus new posts on Docker, Linux & security.