Sonic
Sonic是一个快速、轻量级且无模式的搜索后端。它能够摄取搜索文本和标识符元组,然后在微秒级时间内对其进行查询。
在某些使用场景下,Sonic可以作为超重且功能齐全的搜索后端(如Elasticsearch)的简单替代品。它能够规范化自然语言搜索查询,自动完成搜索查询,并提供最相关的查询结果。Sonic是一个标识符索引,而非文档索引;当被查询时,它返回的是ID,这些ID可以用于在外部数据库中引用匹配的文档。
在设计Sonic时,我们特别注重性能和代码整洁度。它旨在做到无崩溃、超快速,并对服务器资源施加最小压力(我们的测量显示,Sonic在负载下对搜索查询的响应时间在微秒级范围内,占用约30MB内存,CPU占用率低;查看我们的基准测试)。
在Rust版本:rustc 1.74.1 (a28077b28 2023-12-04)
上测试
🇫🇷 在法国南特精心打造。
:newspaper: Sonic项目最初在我的个人博客文章中宣布。
"Sonic"是Sonic项目的吉祥物。我将它画成一只迷幻的嬉皮士刺猬。
谁在使用?
👋 你使用Sonic并想在这里列出吗?联系我。
演示
Sonic已集成到Crisp平台上的所有Crisp搜索产品中。截至2019年,它在一台每月5美元的1-vCPU SSD云服务器上用于索引5亿个对象。Crisp用户使用它来搜索他们的消息、对话、联系人、帮助中心文章等。
你可以在Crisp帮助中心上实时测试Sonic,并了解Sonic搜索结果的速度和相关性。你还可以从那里测试搜索建议:为一个词至少输入2个字符,就会得到一个完整词的建议(按tab键展开建议)。搜索和建议都由Sonic提供支持。
Sonic在帮助中心文章中的模糊搜索表现出色。查找任何单词或短语组合,立即获得结果。
特性
- 搜索词存储在集合中,按桶组织;你可以使用单个桶,或者如果需要在不同的索引中搜索,可以为平台上的每个用户使用一个桶。
- 搜索结果返回对象标识符,如果需要丰富搜索结果,可以从外部数据库解析这些标识符。这使得Sonic成为一个简单的词索引,指向标识符结果。Sonic在其索引中不存储任何直接的文本数据,但它仍然保留一个词图用于自动完成和错误纠正。
- 搜索查询中的拼写错误会被纠正,如果搜索查询中的某个词没有足够的精确匹配结果,Sonic会尝试纠正该词并尝试替代词。你在搜索时可以犯错误。
- 在索引中插入和删除项目;改变索引的操作很轻量,可以在服务器运行时提交。后台任务处理程序负责整合索引,使你推送或弹出的条目能够快速用于搜索。
- 实时自动完成任何单词,通过建议操作。这有助于在你的最终用户搜索界面中构建快速的单词建议功能。
- 完全兼容Unicode,支持全球80多种使用最广泛的语言。Sonic在猜测文本语言后,会从任何文本中删除无用的停用词(例如英语中的"the")。这确保了任何被搜索或摄入的文本在进入索引之前都是干净的;查看支持的语言。
- 简单的协议(Sonic Channel),允许你搜索索引、管理数据摄入(推入索引、从索引中弹出、刷新集合、刷新桶等)并执行管理操作。Sonic Channel被设计为资源占用少且易于集成;阅读协议规范。
- 易于使用的库,让你可以从应用程序连接到Sonic;查看库。
如何使用?
安装
Sonic是用Rust构建的。要安装它,你可以从Sonic发布页面下载版本,使用cargo install
或从master
分支拉取源代码。
👉 每个发布的二进制文件都附带一个.asc
签名文件,可以使用@valeriansaliou的GPG公钥进行验证::key:valeriansaliou.gpg.pub.asc。
👉 从包安装:
Sonic为基于Debian的系统(Debian、Ubuntu等)提供预构建包。
重要提示:Sonic目前仅提供针对Debian 12(代号:bookworm
)的64位包。你可能仍然能够在其他Debian版本以及Ubuntu上使用它们,尽管它们依赖于特定版本的glibc
,这可能在较旧或较新的系统上不可用。
首先,添加Sonic APT仓库(例如,对于Debian bookworm
):
echo "deb [signed-by=/usr/share/keyrings/valeriansaliou_sonic.gpg] https://packagecloud.io/valeriansaliou/sonic/debian/ bookworm main" > /etc/apt/sources.list.d/valeriansaliou_sonic.list
curl -fsSL https://packagecloud.io/valeriansaliou/sonic/gpgkey | gpg --dearmor -o /usr/share/keyrings/valeriansaliou_sonic.gpg
apt-get update
然后,安装Sonic包:
apt-get install sonic
接着,编辑预填充的Sonic配置文件:
nano /etc/sonic.cfg
最后,重启Sonic:
service sonic restart
👉 从源代码安装:
如果你从Git拉取了源代码,可以使用cargo
构建:
cargo build --release
你可以在./target/release
目录中找到构建好的二进制文件。
安装build-essential
、clang
、libclang-dev
、libc6-dev
、g++
和llvm-dev
以便能够编译所需的RocksDB依赖。
注意,在构建Sonic时可以启用以下可选功能:allocator-jemalloc
、tokenizer-chinese
和tokenizer-japanese
(某些功能可能默认已启用)。
👉 从Cargo安装:
你可以直接使用cargo install
安装Sonic:
cargo install sonic-server
确保你的$PATH
正确配置以源自Crates二进制文件,然后使用sonic
命令运行Sonic。
安装build-essential
、clang
、libclang-dev
、libc6-dev
、g++
和llvm-dev
以便能够编译所需的RocksDB依赖。
👉 从Docker Hub安装:
通过Docker运行Sonic可能会很方便。你可以在Docker Hub上找到预构建的Sonic镜像,名为valeriansaliou/sonic。
首先,拉取 valeriansaliou/sonic
镜像:
docker pull valeriansaliou/sonic:v1.4.9
然后,为其提供配置文件并运行(将 /path/to/your/sonic/config.cfg
替换为你的配置文件路径):
docker run -p 1491:1491 -v /path/to/your/sonic/config.cfg:/etc/sonic.cfg -v /path/to/your/sonic/store/:/var/lib/sonic/store/ valeriansaliou/sonic:v1.4.9
在配置文件中,确保:
channel.inet
设置为0.0.0.0:1491
(这允许从容器外部访问 Sonic)store.kv.path
设置为/var/lib/sonic/store/kv/
(这允许 Sonic 访问外部 KV 存储目录)store.fst.path
设置为/var/lib/sonic/store/fst/
(这允许 Sonic 访问外部 FST 存储目录)
Sonic 将可通过 tcp://localhost:1491
访问。
👉 从其他来源安装(非官方):
其他安装来源包括:
- Homebrew (macOS):
brew install sonic
(查看 formula)
注意,这些来源是非官方的,意味着它们不由 Sonic 项目所有者拥有或维护。与 Sonic 项目提供的最新版本相比,这些来源提供的 Sonic 版本可能过时。
配置
使用示例 config.cfg 配置文件,并根据你的环境进行调整。
如果你想微调配置,可以阅读我们的详细配置文档。
运行 Sonic
Sonic 可以这样运行:
./sonic -c /path/to/config.cfg
执行搜索和管理对象
搜索和对象管理(即数据摄入)仅通过 Sonic Channel 协议处理。为了保持 Sonic 的简单性(类似于 Redis),Sonic 不提供 HTTP 端点或类似功能;当你需要与 Sonic 搜索数据库交互时,通过 Sonic Channel 连接是唯一的方式。
Sonic 提供官方库,让你能轻松将 Sonic 集成到你的应用中。点击下面的库以查看库集成文档和代码。
如果你想了解原始 Sonic Channel 基于 TCP 的协议详情,可以阅读我们的详细协议文档。如果你想编写自己的 Sonic Channel 库,这可能会很有用。
📦 Sonic Channel 库
1️⃣ 官方库
Sonic 为你的编程语言提供官方 Sonic 集成库(官方意味着这些库已经由核心维护者审查和验证):
- NodeJS:
- PHP:
- Rust:
2️⃣ 社区库
以下是社区提供的 Sonic 集成列表(非常感谢他们的贡献!):
- Rust:
- sonic_client 由 @FrontMage 开发
- Python:
- asonic 由 @moshe 开发
- python-sonic-client 由 @xmonader 开发
- pysonic-channel 由 @AlongWY 开发
- Ruby:
- sonic-ruby 由 @atipugin 开发
- Go:
- go-sonic 由 @alexisvisco 开发
- go-sonic 由 @OGKevin 开发
- PHP:
- php-sonic 由 @touhonoob 开发
- laravel-scout-sonic 由 @james2doyle 开发
- Java:
- java-sonic 由 @touhonoob 开发
- jsonic 由 @alohaking 开发
- Deno:
- deno-sonic 由 @erfanium 开发
- Elixir:
- Crystal:
- sonic-crystal 由 @babelian 开发
- Nim:
- .NET:
- nsonic 由 @spikensbror 开发
ℹ️ 找不到适合你编程语言的库?构建你自己的库并在此处被引用!(联系我)
支持哪些文本语言?
Sonic 在其词法系统中支持广泛的语言。如果某种语言不在此列表中,你仍然可以将该语言推送到搜索索引,但停用词不会被排除,这可能导致搜索结果质量降低。
词法系统支持的语言有:
- 🇿🇦 南非荷兰语
- 🇸🇦 阿拉伯语
- 🇦🇲 亚美尼亚语
- 🇦🇿 阿塞拜疆语
- 🇧🇩 孟加拉语
- 🇧🇬 保加利亚语
- 🇲🇲 缅甸语
- 🏳 加泰罗尼亚语
- 🇨🇳 中文(简体)
- 🇹🇼 中文(繁体)
- 🇭🇷 克罗地亚语
- 🇨🇿 捷克语
- 🇩🇰 丹麦语
- 🇳🇱 荷兰语
- 🇬🇧 英语
- 🏳 世界语
- 🇪🇪 爱沙尼亚语
- 🇫🇮 芬兰语
- 🇫🇷 法语
- 🇬🇪 格鲁吉亚语
- 🇩🇪 德语
- 🇬🇷 希腊语
- 🇮🇳 古吉拉特语
- 🇮🇱 希伯来语
- 🇮🇳 印地语
- 🇭🇺 匈牙利语
- 🇮🇩 印度尼西亚语
- 🇮🇹 意大利语
- 🇯🇵 日语
- 🇮🇳 卡纳达语
- 🇰🇭 高棉语
- 🇰🇷 韩语
- 🏳 拉丁语
- 🇱🇻 拉脱维亚语
- 🇱🇹 立陶宛语
- 🇮🇳 马拉雅拉姆语
- 🇮🇳 马拉地语
- 🇳🇵 尼泊尔语
- 🇮🇷 波斯语
- 🇵🇱 波兰语
- 🇵🇹 葡萄牙语
- 🇮🇳 旁遮普语
- 🇷🇺 俄语
- 🇷🇸 塞尔维亚语
- 🇸🇰 斯洛伐克语
- 🇸🇮 斯洛文尼亚语
- 🇪🇸 西班牙语
- 🇸🇪 瑞典语
- 🇵🇭 他加禄语
- 🇮🇳 泰米尔语
- 🇹🇭 泰语
- 🇹🇷 土耳其语
- 🇺🇦 乌克兰语
- 🇵🇰 乌尔都语
- 🇻🇳 越南语
- 🇮🇱 意第绪语
- 🇿🇦 祖鲁语
它的速度和轻量级程度如何?
Sonic 从一开始就是为 Crisp 而构建的。随着 Crisp 的增长和将越来越多的搜索数据索引到全文搜索 SQL 数据库中,我们决定是时候切换到一个合适的搜索后端系统了。在审查 Elasticsearch(ELS)和其他系统时,我们发现这些都是功能齐全的重量级系统,无法很好地适应 Crisp 基于免费增值的成本结构。
最终,我们决定构建自己的搜索后端,设计成简单且对资源要求轻量的系统。
您可以使用以下命令运行函数级基准测试:cargo bench --features benchmark
👩🔬 基准测试 #1
➡️ 场景
我们提取了 Crisp 自身客户支持团队使用的所有消息。
我们想将所有这些消息导入到一个全新的 Sonic 实例中,然后对我们建立的索引执行搜索。我们将测量 Sonic 执行每个操作所花费的时间(即通过 Sonic Channel 执行的每个 PUSH
和 QUERY
命令),并将结果按每 1,000 次操作分组(这会输出每 1,000 次操作的平均时间)。
➡️ 环境
我们的基准测试在以下计算机上运行:
- 设备:MacBook Pro(视网膜显示屏,15 英寸,2014 年中)
- 操作系统:MacOS 10.14.3
- 磁盘:512GB SSD(以 AFS 文件系统格式化)
- CPU:2.5 GHz Intel Core i7
- 内存:16 GB 1600 MHz DDR3
Sonic 的编译方式如下:
- Sonic 版本:1.0.1
- Rustc 版本:
rustc 1.35.0-nightly (719b0d984 2019-03-13)
- 编译器标志:
release
配置(-03
和lto
)
我们的数据集如下:
- 对象数量:约 1,000,000 条消息
- 总大小:约 100MB 的原始消息文本(这不包括标识符和其他元数据)
➡️ 脚本
我们用于执行基准测试的脚本是:
- PUSH 脚本:sonic-benchmark_batch-push.js
- QUERY 脚本:sonic-benchmark_batch-query.js
⏬ 结果
我们的发现:
- 我们导入了约100万条长度不一的消息(有些非常长,如电子邮件);
- 导入后,搜索索引在磁盘上占用20MB(KV) + 1.4MB(FST);
- 导入过程中CPU使用率平均为单核75%;
- 在我们的基准测试中,Sonic进程的内存使用峰值为28MB;
- 我们使用了单个Sonic Channel TCP连接,这将导入限制在单线程(我们本可以将其负载均衡到与CPU数量相同的多个Sonic Channel连接);
- 我们获得的导入RPS接近每秒4,000次操作(每线程);
- 我们获得的搜索查询RPS接近每秒1,000次操作(每线程);
- 在使用的4核超线程CPU上,我们本可以将操作并行化到8个虚拟核心,从而理论上将导入RPS提高到每秒32,000次操作,而搜索查询RPS将提高到每秒8,000次操作(不过在某个时点可能会受到SSD的限制);
每次操作的比较结果(针对单个对象):
我们从批处理操作中抽取了8个结果样本,这些样本产生了总共1,000个结果(100万项,每1,000项批量进行一次测量报告)。
这并不是很科学,但应该能让你清楚了解Sonic的性能。
每次操作所花费的时间:
操作 | 平均 | 最佳 | 最差 |
---|---|---|---|
PUSH | 275μs | 190μs | 363μs |
QUERY | 880μs | 852μs | 1ms |
从终端看到的批量PUSH结果(从初始索引:0个对象):
[批量PUSH基准测试图片]
从终端看到的批量QUERY结果(在索引:1,000,000个对象上):
[批量QUERY基准测试图片]
限制
-
索引数据限制: Sonic设计用于在每个集合中分割成数千个搜索桶的大型搜索索引。IID(即内部ID)在索引中存储为32位数字,理论上每个桶可以索引多达约42亿个对象(即OID)。我们观察到30%到40%的存储节省,这证明了在大型数据库上的权衡是合理的(相比Sonic使用64位IID)。此外,Sonic仅以滑动窗口方式保留给定词的N个最近推送的结果(滑动窗口宽度可配置)。
-
搜索查询限制: 出于存储紧凑性考虑,Sonic的自然语言处理系统(NLP)不在句子级别工作(我们保持FST图的浅层以减少时间和空间复杂度)。它在词级别工作,因此能够进行按词搜索并基于用户输入预测词,但无法预测句子中的下一个词。
-
实时限制: 每次向桶图推送或弹出词时都需要重建FST。由于这相当耗费资源,Sonic会批量进行重建周期。如果你刚刚向索引推送了一个新词,但在
SUGGEST
命令中还看不到它,请等待下一个重建周期启动,或在control
通道中使用TRIGGER consolidate
强制执行。 -
互操作性限制: Sonic Channel协议是读写Sonic搜索索引中搜索条目的唯一方式。Sonic不提供任何HTTP API。Sonic Channel的设计考虑了性能和最小网络占用。如果你需要从不受支持的编程语言访问Sonic,你可以提出问题或参考node-sonic-channel实现,并在你的目标编程语言中构建它。
-
硬件限制: Sonic直接在文件系统上执行搜索;即它不将索引放入RAM。搜索查询会导致大量随机访问磁盘,这意味着在老式HDD上会相当慢,而在较新的SSD上会超快。请仅将Sonic数据库存储在SSD支持的文件系统上。
:fire: 报告漏洞
如果你在Sonic中发现漏洞,非常欢迎你直接向@valeriansaliou报告,方法是发送加密电子邮件至valerian@valeriansaliou.name。请勿在公开的GitHub问题中报告漏洞,因为恶意人员可能会利用它们来攻击运行未修补Sonic实例的生产服务器。
:warning: 你必须使用@valeriansaliou的GPG公钥加密你的电子邮件: :key:valeriansaliou.gpg.pub.asc。