存储策略:Valkey vs JSON vs NumPy
本文解释了为什么我们在 SEO 流水线 和 搜索服务 中针对不同类型的数据使用不同的存储技术。
问题所在:一刀切行不通
不同的数据具有不同的访问模式:
-
嵌入向量 (5K x 384 浮点数):需要快速的向量操作(余弦相似度)
-
短语映射 (2500+ 短语):需要人工编辑、版本控制
-
查询缓存 (实时查询):需要快速的键值查找、TTL 过期
-
产品数据 (64K 产品):需要结构化访问、兼容性规则
对所有数据使用相同的存储方式效率低下。
三种存储技术
1. NumPy 数组:向量操作
使用场景:嵌入向量(产品、查询、短语)
为何选择 NumPy:
-
快速的向量运算:针对矩阵运算优化的 C/Fortran 库
-
内存映射文件:无需复制到 RAM 即可加载大型数组
-
批处理操作:在毫秒级内处理数千个向量
-
标准格式:兼容 ML 库(scikit-learn, TensorFlow)
文件格式:二进制 .npy 文件
加载方式:
import numpy as np
# 内存映射(不会将整个文件加载到 RAM)
embeddings = np.load('embeddings.npy', mmap_mode='r')
# 计算余弦相似度
from sklearn.metrics.pairwise import cosine_similarity
similarities = cosine_similarity(query_embedding, embeddings)
性能:以适合实时推理的速度进行大规模相似性搜索。
2. JSON 文件:可人工编辑的数据
使用场景:配置、映射、元数据
为何选择 JSON:
-
人类可读:易于检查和调试
-
版本控制:Git diff 能精确显示变更内容
-
手动编辑:无需代码即可修复错误
-
通用格式:任何语言都能解析 JSON
我们存储的内容:
-
短语到过滤器的映射:短语 → 过滤规则
-
产品特性:产品 → 特性字典
-
查询元数据:查询 → 点击、展示、来源
-
流水线配置:步骤参数、阈值
文件格式:文本 .json 文件
加载方式:
import json
with open('phrase_mappings.json') as f:
mappings = json.load(f)
# 访问数据
filters = mappings['mini pc'] # ['form_factor:mini', 'category:pc']
3. Valkey (Redis):快速的键值缓存
使用场景:实时搜索、自动补全、热门查询
为何选择 Valkey:
-
内存存储:查找延迟为微秒级
-
RediSearch:带索引的向量相似性搜索
-
TTL 过期:自动缓存失效
-
发布/订阅:跨服务器的实时更新
-
持久化:可选的磁盘快照以确保持久性
我们存储的内容:
-
查询嵌入向量缓存:最近查询 → 嵌入向量
-
热门查询:按流量排名的前 1000 条查询
-
自动补全索引:前缀 → 查询建议
-
过滤器提取缓存:查询 → 提取出的过滤器
-
相关搜索缓存:查询 → 相关查询
数据结构:
-
字符串:简单的键值对(查询 → 嵌入向量)
-
有序集合:排序数据(按分数排名的热门查询)
-
RediSearch 索引:向量相似性搜索
-
哈希:结构化数据(查询元数据)
加载方式:
from app.shared.valkey_cache import get_valkey
valkey = get_valkey()
# ... (实现细节已省略)
决策矩阵
何时使用 NumPy
标准:
-
数据是数值型(浮点数、整数)
-
需要快速的向量操作(点积、余弦相似度)
-
数据以读取为主(很少更新)
-
数据量大(数百万个数字)
-
批处理(一次处理多个项目)
示例:
-
嵌入向量(产品、查询、短语)
-
特征向量
-
相似性矩阵
何时使用 JSON
标准:
-
数据是结构化的(对象、数组)
-
需要人类可读性
-
需要版本控制(Git)
-
数据偶尔变更(手动编辑)
-
数据量小到中等(<100 MB)
示例:
-
配置文件
-
短语映射
-
产品元数据
-
流水线参数
何时使用 Valkey
标准:
-
需要快速查找(微秒级)
-
数据频繁变更(实时查询)
-
需要 TTL 过期(缓存失效)
-
需要发布/订阅(实时更新)
-
需要向量搜索(RediSearch)
示例:
-
查询缓存
-
自动补全
-
热门查询
-
会话数据
-
速率限制
混合方法
我们结合使用所有三种技术以获得最佳性能:
流水线(离线处理)
NumPy:计算嵌入向量、相似性矩阵
JSON:存储映射、元数据、配置
Valkey:不使用(流水线离线运行)
搜索服务(实时查询)
NumPy:从磁盘加载嵌入向量(内存映射)
JSON:从磁盘加载映射(缓存在内存中)
Valkey:缓存实时查询、自动补全、热门查询
数据流
graph LR
Pipeline[SEO 流水线
离线]
subgraph Storage
NP[NumPy 文件
embeddings.npy]
JS[JSON 文件
mappings.json]
end
Search[搜索服务
实时]
VK[Valkey
缓存]
Pipeline --> NP
Pipeline --> JS
NP --> Search
JS --> Search
Search --> VK
VK --> Search性能比较
NumPy(内存映射):
-
加载时间:瞬时(零拷贝映射到虚拟内存)
-
查找时间:接近零(CPU 直接指针指向数组索引)
-
内存:最小(操作系统管理的页缓存,不驻留在进程 RAM 中)
JSON:
-
加载时间:高延迟(顺序解析和对象实例化)
-
查找时间:高效(标准哈希映射开销)
-
内存:占用大(整个序列化结构驻留在堆中)
Valkey:
-
加载时间:持久化(驻留在后台服务中)
-
查找时间:中等(包括网络往返和协议序列化)
-
内存:外部化(隔离在数据库进程内)
胜出者:NumPy 用于高吞吐量批处理,Valkey 用于分布式单键访问
向量相似性搜索
NumPy(cosine_similarity):
-
批处理操作:快速(通过 SIMD/向量化线性代数优化)
-
可并行化:高(可扩展到所有可用的物理 CPU 核心)
-
内存:线性(随嵌入维度直接扩展)
Valkey(RediSearch):
-
搜索速度:优越(利用专门的 HNSW/向量索引)
-
可并行化:有限(受引擎线程模型限制)
-
内存:密集(需要原始嵌入向量加上索引元数据)
胜出者:Valkey 用于亚感知延迟的实时搜索,NumPy 用于繁重的离线分析处理
配置查找
JSON:
-
加载时间:可变(与配置复杂性成正比)
-
查找时间:快速(原生字典访问)
-
编辑时间:无摩擦(人类可读的文本修改)
Valkey:
-
加载时间:即时(连接时即激活)
-
查找时间:受网络限制(取决于请求开销)
-
编辑时间:编程式(需要 CLI 或客户端 SET 命令)
胜出者:JSON 用于静态配置,Valkey 用于动态共享状态
参考文献
技术
-
NumPy - 数组计算库
-
JSON - 数据交换格式
-
Valkey - 内存数据存储(Redis 分支)
-
RediSearch - 向量搜索模块
技术概念
相关文章
总结
我们使用三种针对不同用例优化的存储技术:
NumPy(嵌入向量):
-
快速的向量操作(余弦相似度)
-
内存映射
-
批处理
-
标准 ML 格式
JSON(配置):
-
人类可读(易于调试)
-
版本控制(Git diff)
-
手动编辑(无需代码)
-
通用格式
Valkey(实时缓存):
-
快速查找(微秒级)
-
向量搜索(RediSearch)
-
TTL 过期(自动失效)
-
发布/订阅(实时更新)
混合方法:为每项任务使用合适的工具,组合使用以获得最佳性能。