查询缓存将压缩的结果集存储在内存中,并在可能的情况下重用它们以响应后续查询。您可以使用以下指令进行配置:
- qcache_max_bytes,缓存查询存储的 RAM 使用限制。默认值为 16 MB。将
qcache_max_bytes设置为 0 会完全禁用查询缓存。 - qcache_thresh_msec,缓存的最小查询耗时。完成时间快于此值的查询将不会被缓存。默认值为 3000 毫秒,即 3 秒。
- qcache_ttl_sec,缓存条目的 TTL(存活时间)。查询结果将缓存此时长。默认值为 60 秒,即 1 分钟。
这些设置可以通过 SET GLOBAL 语句动态更改:
mysql> SET GLOBAL qcache_max_bytes=128000000;
这些更改会立即生效,不再满足约束条件的缓存结果集会立即被丢弃。在动态减少缓存大小时,最近最常使用(MRU)的结果集优先保留。
查询缓存的工作原理如下。启用时,每个全文搜索结果都会完整存储在内存中。这发生在全文匹配、过滤和排序之后,基本上我们存储了 total_found 个 {docid,weight} 对。压缩后的匹配项平均每个占用 2 到 12 字节,主要取决于相邻 docid 之间的差值。查询完成后,我们检查耗时和大小阈值,决定是保存压缩结果集以供重用,还是丢弃它。
请注意,查询缓存对 RAM 的影响不限于 qcache_max_bytes!例如,如果您同时运行 10 个并发查询,每个查询匹配最多 100 万条结果(过滤后),即使查询足够快且未被缓存,峰值临时 RAM 使用量也会在 40 MB 到 240 MB 之间。
当表、全文查询(即 MATCH() 内容)和排序器完全匹配且过滤条件兼容时,查询可以使用缓存。这意味着:
MATCH()中的全文部分必须逐字节匹配。添加一个额外空格,查询缓存就会认为这是一个不同的查询。- 排序器(及其参数,如果是用户定义的排序器)必须逐字节匹配。
- 过滤条件必须是原始过滤条件的超集。您可以添加额外的过滤条件仍然命中缓存。(在这种情况下,额外的过滤条件会应用于缓存结果。)但如果移除其中一个过滤条件,则会被视为新查询。
缓存条目会根据 TTL 过期,并且在表轮换、TRUNCATE 或 ATTACH 时失效。请注意,目前条目不会因任意 RT 表写入而失效!因此,缓存查询可能在其 TTL 期间返回较旧的结果。
您可以通过 SHOW STATUS 查看当前缓存状态,相关变量以 qcache_XXX 命名:
mysql> SHOW STATUS LIKE 'qcache%';
+-----------------------+----------+
| Counter | Value |
+-----------------------+----------+
| qcache_max_bytes | 16777216 |
| qcache_thresh_msec | 3000 |
| qcache_ttl_sec | 60 |
| qcache_cached_queries | 0 |
| qcache_used_bytes | 0 |
| qcache_hits | 0 |
+-----------------------+----------+
6 rows in set (0.00 sec)
排序规则主要影响字符串属性的比较。它们定义了字符集编码以及 Manticore 在执行涉及字符串属性的 ORDER BY 或 GROUP BY 时用于比较字符串的策略。
字符串属性在索引时按原样存储,并且不附加任何字符集或语言信息。只要 Manticore 仅需将字符串逐字存储并返回给调用应用程序,这种方式是可行的。然而,当你要求 Manticore 按字符串值排序时,请求立即变得模糊不清。
首先,单字节(ASCII、ISO-8859-1 或 Windows-1251)字符串需要与 UTF-8 字符串不同地处理,后者可能用可变字节数编码每个字符。因此,我们需要知道字符集类型,以便正确地将原始字节解释为有意义的字符。
其次,我们还需要知道特定语言的字符串排序规则。例如,在 en_US 区域设置中按美国规则排序时,带变音符的字符 ï(带分音符的小写字母 i)应放在 z 之后的某处。然而,在考虑法语规则和 fr_FR 区域设置时,它应放在 i 和 j 之间。其他规则集可能会完全忽略变音符,使 ï 和 i 可以任意混合。
第三,在某些情况下,我们可能需要区分大小写的排序,而在其他情况下,则需要不区分大小写的排序。
排序规则封装了以下所有内容:字符集、语言规则和大小写敏感性。Manticore 目前提供四种排序规则:
libc_cilibc_csutf8_general_cibinary
前两种排序规则依赖于多个标准 C 库(libc)调用,因此可以支持系统上安装的任何区域设置。它们分别提供不区分大小写(_ci)和区分大小写(_cs)的比较。默认情况下,它们使用 C 区域设置,实际上是按字节比较。要更改此设置,需要使用 collation_libc_locale 指令指定不同的可用区域设置。系统上可用的区域设置列表通常可以通过 locale 命令获得:
$ locale -a
C
en_AG
en_AU.utf8
en_BW.utf8
en_CA.utf8
en_DK.utf8
en_GB.utf8
en_HK.utf8
en_IE.utf8
en_IN
en_NG
en_NZ.utf8
en_PH.utf8
en_SG.utf8
en_US.utf8
en_ZA.utf8
en_ZW.utf8
es_ES
fr_FR
POSIX
ru_RU.utf8
ru_UA.utf8
具体的系统区域设置列表可能有所不同。请查阅操作系统文档以安装所需的额外区域设置。
utf8_general_ci 和 binary 区域设置内置于 Manticore。第一种是针对 UTF-8 数据的通用排序规则(没有所谓的语言定制);其行为应类似于 MySQL 中的 utf8_general_ci 排序规则。第二种是简单的按字节比较。
排序规则可以通过 SQL 在每个会话基础上使用 SET collation_connection 语句覆盖。所有后续的 SQL 查询将使用该排序规则。否则,所有查询将使用服务器默认排序规则,或使用 collation_server 配置指令中指定的排序规则。Manticore 目前默认使用 libc_ci 排序规则。
排序规则影响所有字符串属性的比较,包括 ORDER BY 和 GROUP BY 中的比较,因此根据所选排序规则,可能返回不同排序或分组的结果。请注意,排序规则不影响全文搜索;全文搜索请使用 charset_table。
当 Manticore 执行全扫描查询时,它可以使用普通扫描来检查每个文档是否符合过滤条件,或者采用额外的数据和/或算法来加速查询执行。Manticore 使用基于成本的优化器(CBO),也称为“查询优化器”,来决定采用哪种方法。
CBO 还可以提升全文查询的性能。详情见下文。
如果 CBO 认为替换一个或多个查询过滤器能提升性能,它可能会用以下实体之一替换:
- docid 索引 利用存储在
.spt扩展名文件中的特殊仅包含 docid 的二级索引。除了提升文档 ID 上的过滤器性能外,docid 索引还用于加速文档 ID 到行 ID 的查找,以及加快守护进程启动时大规模 killlist 的应用。 - 列式扫描 依赖于列式存储,仅能用于列式属性。它扫描每个值并对其进行过滤测试,但经过高度优化,通常比默认方法更快。
- 二级索引 默认为所有属性(除 JSON 外)生成。它们使用 PGM 索引 以及 Manticore 内置的倒排索引来检索对应于某个值或值范围的行 ID 列表。二级索引存储在扩展名为
.spidx和.spjidx的文件中。 有关如何为 JSON 属性生成二级索引的信息,请参见 json_secondary_indexes。
优化器使用各种属性统计信息来估算每条执行路径的成本,包括:
- 属性内数据分布信息(直方图,存储在
.sphi文件中)。直方图在数据索引时自动生成,是 CBO 的主要信息来源。 - 来自 PGM(二级索引)的信息,有助于估算需要读取的文档列表数量。这有助于评估文档列表合并性能并选择合适的合并算法(优先队列合并或位图合并)。
- 列式编码统计,用于估算列式数据解压性能。
- 列式最小-最大树。虽然 CBO 使用直方图估算应用过滤器后剩余的文档数量,但它还需要确定过滤器处理了多少文档。对于列式属性,部分评估最小-最大树可实现此目的。
- 全文字典。CBO 利用词项统计信息来估算全文树的评估成本。
优化器计算查询中每个过滤器的执行成本。由于某些过滤器可以被多种不同实体替代(例如,对于文档 ID,Manticore 可以使用普通扫描、docid 索引查找、列式扫描(如果文档 ID 是列式的)和二级索引),优化器会评估所有可用组合。但组合数最大限制为 1024。
为了估算查询执行成本,优化器计算执行查询时最重要操作的估计成本。它使用预设常数来表示每个操作的成本。
优化器比较每条执行路径的成本,选择成本最低的路径来执行查询。
在处理带有属性过滤器的全文查询时,查询优化器在两种可能的执行路径之间做出决定。一种是执行全文查询,检索匹配项,然后使用过滤器。另一种是用上述一个或多个实体替换过滤器,从中获取行 ID 并注入全文匹配树。这样,全文搜索结果将与全扫描结果相交。查询优化器估算全文树评估的成本和计算过滤器结果的最佳路径。基于此信息,优化器选择执行路径。
另一个需要考虑的因素是多线程查询执行(当启用 pseudo_sharding 时)。CBO 知道某些查询可以多线程执行,并将此纳入考虑。CBO 优先考虑较短的查询执行时间(即延迟)而非吞吐量。例如,如果使用列式扫描的查询可以多线程执行(占用多个 CPU 核心),且比单线程使用二级索引执行的查询更快,则优先选择多线程执行。
使用二级索引和 docid 索引的查询始终在单线程中运行,因为基准测试表明使它们多线程几乎没有收益。
目前,优化器仅使用 CPU 成本,不考虑内存或磁盘使用。
Manticore Search 支持将由机器学习模型生成的嵌入向量添加到每个文档中,然后对它们进行最近邻搜索。这使您能够构建诸如相似性搜索、推荐、语义搜索和基于自然语言处理算法的相关性排序等功能,还包括图像、视频和声音搜索。
嵌入向量是一种表示数据(如文本、图像或声音)的方法,将其表示为高维空间中的向量。这些向量被设计成使它们之间的距离反映所代表数据的相似性。该过程通常采用诸如词嵌入(例如 Word2Vec、BERT)用于文本,或神经网络用于图像的算法。向量空间的高维特性,每个向量包含多个分量,允许表示项目之间复杂且细微的关系。它们的相似性通过这些向量之间的距离来衡量,通常使用欧几里得距离或余弦相似度等方法。
Manticore Search 使用 HNSW 库实现 k-近邻(KNN)向量搜索。此功能是 Manticore Columnar Library 的一部分。
要运行 KNN 搜索,您必须先配置表。浮点向量和 KNN 搜索仅支持实时表(不支持普通表)。表需要至少有一个 float_vector 属性,作为数据向量。您需要指定以下属性:
-
knn_type:必填设置;目前仅支持hnsw。 -
knn_dims:必填设置,指定被索引向量的维度。 -
hnsw_similarity:必填设置,指定 HNSW 索引使用的距离函数。可接受的值有:L2- 平方 L2 距离IP- 内积COSINE- 余弦相似度
注意: 使用
COSINE相似度时,向量在插入时会自动归一化。这意味着存储的向量值可能与原始输入值不同,因为它们会被转换为单位向量(数学长度/模为 1.0 的向量),以实现高效的余弦相似度计算。此归一化保持向量的方向,同时标准化其长度。 -
hnsw_m:可选设置,定义图中最大出边连接数。默认值为 16。 -
hnsw_ef_construction:可选设置,定义构建时的时间/准确性权衡。
- SQL
- Config
create table test ( title text, image_vector float_vector knn_type='hnsw' knn_dims='4' hnsw_similarity='l2' );table test_vec {
type = rt
...
rt_attr_float_vector = image_vector
knn = {"attrs":[{"name":"image_vector","type":"hnsw","dims":4,"hnsw_similarity":"L2","hnsw_m":16,"hnsw_ef_construction":200}]}
}Query OK, 0 rows affected (0.01 sec)处理向量数据最简单的方法是使用自动嵌入。使用此功能,您创建一个带有 MODEL_NAME 和 FROM 参数的表,然后只需插入文本数据——Manticore 会自动为您生成嵌入向量。
创建自动嵌入表时,指定:
MODEL_NAME:要使用的嵌入模型FROM:用于生成嵌入的字段(为空表示所有文本/字符串字段)
支持的嵌入模型:
- Sentence Transformers:任何合适的基于 BERT 的 Hugging Face 模型(例如
sentence-transformers/all-MiniLM-L6-v2)——无需 API 密钥。Manticore 在创建表时下载模型。 - OpenAI:OpenAI 嵌入模型,如
openai/text-embedding-ada-002- 需要API_KEY='<OPENAI_API_KEY>'参数 - Voyage:Voyage AI 嵌入模型 - 需要
API_KEY='<VOYAGE_API_KEY>'参数 - Jina:Jina AI 嵌入模型 - 需要
API_KEY='<JINA_API_KEY>'参数
有关设置 float_vector 属性的更多信息,请参见这里。
- SQL
使用 sentence-transformers(无需 API 密钥)
CREATE TABLE products (
title TEXT,
description TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
MODEL_NAME='sentence-transformers/all-MiniLM-L6-v2' FROM='title'
);使用 OpenAI(需要 API_KEY 参数)
CREATE TABLE products_openai (
title TEXT,
description TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
MODEL_NAME='openai/text-embedding-ada-002' FROM='title,description' API_KEY='...'
);使用所有文本字段生成嵌入(FROM 为空)
CREATE TABLE products_all (
title TEXT,
description TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
MODEL_NAME='sentence-transformers/all-MiniLM-L6-v2' FROM=''
);使用自动嵌入时,不要在 INSERT 语句中指定向量字段。嵌入向量会自动从 FROM 参数指定的文本字段生成。
- SQL
仅插入文本数据 - 嵌入自动生成
INSERT INTO products (title) VALUES
('machine learning artificial intelligence'),
('banana fruit sweet yellow');插入多个字段 - 如果 FROM='title,description',则两者都用于生成嵌入
INSERT INTO products_openai (title, description) VALUES
('smartphone', 'latest mobile device with advanced features'),
('laptop', 'portable computer for work and gaming');插入空向量(文档将被排除在向量搜索之外)
INSERT INTO products (title, embedding_vector) VALUES
('no embedding item', ());搜索方式相同——提供查询文本,Manticore 会生成嵌入并找到相似文档:
- SQL
- JSON
SELECT id, knn_dist() FROM products WHERE knn(embedding_vector, 3, 'machine learning');使用文本查询和自动嵌入
POST /search
{
"table": "products",
"knn": {
"field": "embedding_vector",
"query": "machine learning",
"k": 3
}
}直接使用向量查询
POST /search
{
"table": "products",
"knn": {
"field": "embedding_vector",
"query": [0.1, 0.2, 0.3, 0.4],
"k": 3
}
}+------+------------+
| id | knn_dist() |
+------+------------+
| 1 | 0.12345678 |
| 2 | 0.87654321 |
+------+------------+
2 rows in set (0.00 sec){
"took": 0,
"timed_out": false,
"hits": {
"total": 2,
"total_relation": "eq",
"hits": [
{
"_id": 1,
"_score": 1,
"_knn_dist": 0.12345678,
"_source": {
"title": "machine learning artificial intelligence"
}
},
{
"_id": 2,
"_score": 1,
"_knn_dist": 0.87654321,
"_source": {
"title": "banana fruit sweet yellow"
}
}
]
}
}或者,您可以手动插入预先计算好的向量数据,确保其维度与创建表时指定的维度匹配。您也可以插入空向量;这意味着该文档将被排除在向量搜索结果之外。
重要提示: 当使用 hnsw_similarity='cosine' 时,向量在插入时会自动归一化为单位向量(数学长度/幅度为1.0的向量)。这种归一化保持了向量的方向,同时标准化了其长度,这是高效计算余弦相似度所必需的。这意味着存储的值将与您原始输入的值不同。
- SQL
- JSON
insert into test values ( 1, 'yellow bag', (0.653448,0.192478,0.017971,0.339821) ), ( 2, 'white bag', (-0.148894,0.748278,0.091892,-0.095406) );POST /insert
{
"table":"test_vec",
"id":1,
"doc": { "title" : "yellow bag", "image_vector" : [0.653448,0.192478,0.017971,0.339821] }
}
POST /insert
{
"table":"test_vec",
"id":2,
"doc": { "title" : "white bag", "image_vector" : [-0.148894,0.748278,0.091892,-0.095406] }
}Query OK, 2 rows affected (0.00 sec){
"table":"test",
"_id":1,
"created":true,
"result":"created",
"status":201
}
{
"table":"test",
"_id":2,
"created":true,
"result":"created",
"status":201
}现在,您可以使用 SQL 或 JSON 格式中的 knn 子句执行 KNN 搜索。两种接口都支持相同的基本参数,确保无论选择哪种格式,都能获得一致的体验:
- SQL:
select ... from <table name> where knn ( <field>, <k>, <query vector> [,<options>] ) - JSON:
POST /search { "table": "<table name>", "knn": { "field": "<field>", "query": "<text or vector>", "k": <k>, "ef": <ef>, "rescore": <rescore>, "oversampling": <oversampling> } }
参数说明:
field:这是包含向量数据的浮点向量属性的名称。k:表示返回的文档数量,是分层可导航小世界(HNSW)索引的关键参数。它指定单个 HNSW 索引应返回的文档数量。然而,最终结果中包含的文档数量可能会有所不同。例如,如果系统处理的是分割成磁盘块的实时表,每个块可能返回k个文档,导致总数超过指定的k(因为累计计数为num_chunks * k)。另一方面,如果在请求了k个文档后,根据特定属性过滤掉了一些文档,最终文档数可能少于k。需要注意的是,参数k不适用于 ramchunks。在 ramchunks 的上下文中,检索过程不同,因此k参数对返回文档数量的影响不适用。query:(推荐参数)搜索查询,可以是:- 文本字符串:如果字段配置了自动嵌入,则自动转换为嵌入向量。如果字段没有自动嵌入,将返回错误。
- 向量数组:与
query_vector功能相同。
query_vector:(遗留参数)作为数字数组的搜索向量。为向后兼容仍然支持。 注意: 在同一请求中使用query或query_vector中的一个,不要同时使用。ef:搜索过程中使用的动态列表大小的可选参数。ef越大,搜索越准确但越慢。rescore:启用 KNN 重新评分(默认禁用)。在 SQL 中设置为1,在 JSON 中设置为true以启用重新评分。KNN 搜索完成后,使用量化向量(可能带有过采样)进行距离计算,然后用原始(全精度)向量重新计算距离并重新排序结果,以提高排名准确性。oversampling:设置一个因子(浮点值),在执行 KNN 搜索时将k乘以该因子,导致使用量化向量检索的候选项多于所需数量。默认不应用过采样。如果启用重新评分,这些候选项可以稍后重新评估。过采样也适用于非量化向量。由于它增加了k,影响 HNSW 索引的工作方式,可能会导致结果准确性略有变化。
文档始终按与搜索向量的距离排序。您指定的任何额外排序条件将在此主要排序条件之后应用。要获取距离,有一个内置函数叫做 knn_dist()。
- SQL
- JSON
select id, knn_dist() from test where knn ( image_vector, 5, (0.286569,-0.031816,0.066684,0.032926), { ef=2000, oversampling=3.0, rescore=1 } );POST /search
{
"table": "test",
"knn":
{
"field": "image_vector",
"query": [0.286569,-0.031816,0.066684,0.032926],
"k": 5,
"ef": 2000,
"rescore": true,
"oversampling": 3.0
}
}+------+------------+
| id | knn_dist() |
+------+------------+
| 1 | 0.28146550 |
| 2 | 0.81527930 |
+------+------------+
2 rows in set (0.00 sec){
"took":0,
"timed_out":false,
"hits":
{
"total":2,
"total_relation":"eq",
"hits":
[
{
"_id": 1,
"_score":1,
"_knn_dist":0.28146550,
"_source":
{
"title":"yellow bag",
"image_vector":[0.653448,0.192478,0.017971,0.339821]
}
},
{
"_id": 2,
"_score":1,
"_knn_dist":0.81527930,
"_source":
{
"title":"white bag",
"image_vector":[-0.148894,0.748278,0.091892,-0.095406]
}
}
]
}
}HNSW 索引需要完全加载到内存中以执行 KNN 搜索,这可能导致显著的内存消耗。为了减少内存使用,可以应用标量量化——一种通过用有限数量的离散值表示每个分量(维度)来压缩高维向量的技术。Manticore 支持 8 位和 1 位量化,意味着每个向量分量从 32 位浮点压缩到 8 位甚至 1 位,分别减少了 4 倍或 32 倍的内存使用。这些压缩表示还允许更快的距离计算,因为可以在单个 SIMD 指令中处理更多的向量分量。虽然标量量化引入了一些近似误差,但通常是在搜索准确性和资源效率之间值得的权衡。为了获得更好的准确性,量化可以与重新评分和过采样结合使用:检索的候选项多于请求的数量,并使用原始 32 位浮点向量重新计算这些候选项的距离。
支持的量化类型包括:
8bit:每个向量分量量化为 8 位。1bit:每个向量分量量化为 1 位。使用非对称量化,查询向量量化为 4 位,存储向量量化为 1 位。这种方法比简单方法提供更高的精度,但性能有所折衷。1bitsimple:每个向量分量量化为 1 位。此方法比1bit更快,但通常准确性较低。
- SQL
create table test ( title text, image_vector float_vector knn_type='hnsw' knn_dims='4' hnsw_similarity='l2' quantization='1bit');Query OK, 0 rows affected (0.01 sec)注意:通过 id 查找相似文档需要 Manticore Buddy。如果无法使用,请确保已安装 Buddy。
基于特定文档的唯一ID查找相似文档是一项常见任务。例如,当用户查看某个特定项目时,Manticore Search 可以高效地识别并显示在向量空间中与其最相似的项目列表。操作方法如下:
- SQL:
select ... from <table name> where knn ( <field>, <k>, <document id> ) - JSON:
POST /search { "table": "<table name>", "knn": { "field": "<field>", "doc_id": <document id>, "k": <k> } }
参数说明:
field:这是包含向量数据的浮点向量属性的名称。k:表示返回的文档数量,是分层可导航小世界(HNSW)索引的关键参数。它指定单个 HNSW 索引应返回的文档数量。然而,最终结果中包含的文档实际数量可能会有所不同。例如,如果系统处理的是分割成磁盘块的实时表,每个块可能返回k个文档,导致总数超过指定的k(因为累计数量为num_chunks * k)。另一方面,如果在请求了k个文档后,根据特定属性过滤掉了一些文档,最终文档数量可能少于k。需要注意的是,参数k不适用于 ramchunks。在 ramchunks 的上下文中,检索过程不同,因此k参数对返回文档数量的影响不适用。document id:用于 KNN 相似度搜索的文档ID。
- SQL
- JSON
select id, knn_dist() from test where knn ( image_vector, 5, 1 );POST /search
{
"table": "test",
"knn":
{
"field": "image_vector",
"doc_id": 1,
"k": 5
}
}+------+------------+
| id | knn_dist() |
+------+------------+
| 2 | 0.81527930 |
+------+------------+
1 row in set (0.00 sec){
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"total_relation":"eq",
"hits":
[
{
"_id": 2,
"_score":1643,
"_knn_dist":0.81527930,
"_source":
{
"title":"white bag",
"image_vector":[-0.148894,0.748278,0.091892,-0.095406]
}
}
]
}
}- SQL
- JSON
select id, knn_dist() from test where knn ( image_vector, 5, (0.286569,-0.031816,0.066684,0.032926) ) and match('white') and id < 10;POST /search
{
"table": "test",
"knn":
{
"field": "image_vector",
"query": [0.286569,-0.031816,0.066684,0.032926],
"k": 5,
"filter":
{
"bool":
{
"must":
[
{ "match": {"_all":"white"} },
{ "range": { "id": { "lt": 10 } } }
]
}
}
}
}+------+------------+
| id | knn_dist() |
+------+------------+
| 2 | 0.81527930 |
+------+------------+
1 row in set (0.00 sec){
"took":0,
"timed_out":false,
"hits":
{
"total":1,
"total_relation":"eq",
"hits":
[
{
"_id": 2,
"_score":1643,
"_knn_dist":0.81527930,
"_source":
{
"title":"white bag",
"image_vector":[-0.148894,0.748278,0.091892,-0.095406]
}
}
]
}
}