RT实时索引

实时索引(或者简称为RT索引)是一个新的索引后端,你可以实时的插入、更新或者删除文档(行)。RT索引在版本1.10-beta中加入。查询时可以使 用SPhinxAPI、SphinxQL、SphinxSE,不过目前只能使用SphinxQL来更新。详细的SphinxQL参考请查看 第 7 章 SphinxQL 指南.

4.1. RT实时索引概览

和其他类型的索引类似,RT索引在 sphinx.conf中定义。它与磁盘索引有着明显的差异,a) 数据源设置不需要而且会被忽略,b) 你应该明确的定义所有的文本字段,而不仅仅是属性。下面是例子

例 4.1. RT实时索引定义

index rt
{
	type = rt
	path = /usr/local/sphinx/data/rt
	rt_field = title
	rt_field = content
	rt_attr_uint = gid
}

RT索引目前还在继续开发完善中(从版本1.10-beta开始)。 因此,他可能缺乏某些特定的功能:例如,前缀/中缀索引,MVA属性等目前尚不支持。但是,所有常规索引功能和搜索功能都已经实现,并且经过了内部的测试。我们还有一些实际运营生产环境(并非最不重要的部分)已经在使用RT索引,并且取得了较好的效果。

RT索引可以使用MySQL协议访问,支持INSERT, REPLACE, DELETE, 以及 SELECT等SQL语句. 以下是一个在演示索引上的操作会话例子:

$ mysql -h 127.0.0.1 -P 9306
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 1.10-dev (r2153)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> INSERT INTO rt VALUES ( 1, 'first record', 'test one', 123 );
Query OK, 1 row affected (0.05 sec)

mysql> INSERT INTO rt VALUES ( 2, 'second record', 'test two', 234 );
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM rt;
+------+--------+------+
| id   | weight | gid  |
+------+--------+------+
|    1 |      1 |  123 |
|    2 |      1 |  234 |
+------+--------+------+
2 rows in set (0.02 sec)

mysql> SELECT * FROM rt WHERE MATCH('test');
+------+--------+------+
| id   | weight | gid  |
+------+--------+------+
|    1 |   1643 |  123 |
|    2 |   1643 |  234 |
+------+--------+------+
2 rows in set (0.01 sec)

mysql> SELECT * FROM rt WHERE MATCH('@title test');
Empty set (0.00 sec)

并行和批量INSERT语法都支持,例如:可以先指定一个列的字段,然后同时插入多行。 删除可以使用DELETE语句,但是目前唯一支持的语法是DELETE FROM <index> WHERE id=<id>。REPLACE也支持,可以用于更新数据。

mysql> INSERT INTO rt ( id, title ) VALUES ( 3, 'third row' ), ( 4, 'fourth entry' );
Query OK, 2 rows affected (0.01 sec)

mysql> SELECT * FROM rt;
+------+--------+------+
| id   | weight | gid  |
+------+--------+------+
|    1 |      1 |  123 |
|    2 |      1 |  234 |
|    3 |      1 |    0 |
|    4 |      1 |    0 |
+------+--------+------+
4 rows in set (0.00 sec)

mysql> DELETE FROM rt WHERE id=2;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM rt WHERE MATCH('test');
+------+--------+------+
| id   | weight | gid  |
+------+--------+------+
|    1 |   1500 |  123 |
+------+--------+------+
1 row in set (0.00 sec)

mysql> INSERT INTO rt VALUES ( 1, 'first record on steroids', 'test one', 123 );
ERROR 1064 (42000): duplicate id '1'

mysql> REPLACE INTO rt VALUES ( 1, 'first record on steroids', 'test one', 123 );
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM rt WHERE MATCH('steroids');
+------+--------+------+
| id   | weight | gid  |
+------+--------+------+
|    1 |   1500 |  123 |
+------+--------+------+
1 row in set (0.01 sec)

RT索引中存储的数据可以在正常关闭后继续存在。如果开启了二进制日志,则可以在崩溃或者非正常关闭后继续存在,并在随后的启动中恢复。

4.2. RT实时索引使用注意事项

在版本1.10-beta时,RT索引还处于特殊功能测试阶段:虽然没有大的问题,但是还有一些总所周知的以至于可以搅局的问题,以及一些特定的使用限制。这些使用限制在下面一一列出:

  • 前缀和中缀索引尚不支持.

  • MVA属性尚不支持.

  • 常规的磁盘块优化尚未实现

  • 在索引初始化创建时,属性按照类别被重新排序,他们的顺序如下:uint, bigint, float, timestamp, string. 因此,在执行INSERT时,如果没有明确指定列名,就需要依次首先给出所有uint列的值,然后是bigint列的值,以此类推。

  • 默认保留的内存块大小(rt_mem_limit)为32M,对于大型索引而言可能性能不好,如果打算要索引GB级别的数据,你应该增大到256~1024M。

  • 较高比例的DELETE/REPLACE可导致大量的失效名单碎片和影响搜索性能。

  • 目前的系统对事务处理没有做强制限制;太多并发的INSERT/REPLACE事务可能会耗费大量的内存。

  • 在二进制日志损坏的情况下,恢复将在第一个损坏的事务处停止,即使在技术上可以继续搜寻随后完好的事务以及重新使用,也无济于事。当然,二进制日志文件中间损坏的情况(由于是片状磁盘/CDD/磁带?)是极为罕见的,甚至永远不会碰到。

4.3. RT实时索引原理

RT索引内部是区块化的。它保留了内存区块用于存储所有最新的变化。内存区块的内存使用由每个索引设置中的rt_mem_limit指令严格限制。一旦内存区块的增长超过这个限制,就会根据其内容创建一个新的磁盘区块,并将内存区块复位。因此,在RT索引中,虽然大多数的变化都会在内存中瞬间执行完成(毫秒级),这些内存区块溢满到磁盘区块建立的变化会有一定的延时(几秒钟)。

磁盘区块其实就是基于磁盘的索引。但是他们是RT索引的一部分并由其自动管理,所以无需手动配置,就算你想要管理目前也没有提供方式。因为只有在RT索引的内存区块达到了限制时才会创建一个新的磁盘区块,每个磁盘区块的大小基本上为rt_mem_limit 个字节。

一般而言,最好设置一个较大限制值,使得刷新频率和索引碎片(磁盘区块)都尽可能最小化。例如,在一个专门的搜索服务器上要处理一个较大的RT索引,建议将rt_mem_limit设置为1~2GB。全局的内存限制计划实现,但是在版本1.10-beta中尚未支持。

磁盘区块的全文索引数据并不会真的被修改,所以全文索引字段的变化(即删除行和更新)其实使用失效名单列表排除来自于磁盘区块的先前版本的行,但是实际上 并不会从物理上清除数据。因此,在一个具有较高全文索引更新率的情况下,最终可能被先前版本的行污染,以及降低搜索性能。已经计划从索引中物理清除以提高 性能,但是在版本1.10-beta中尚未支持。

内存区块中的数据会在正常关闭时写入到磁盘,并在启动时再次载入。但是在守护进程或者服务器崩溃的情况下,内存区块中的更新数据可能会丢失。为了防止这种情况的发生,可以使用事务专用的二进制日志。查看 第 4.4 节 “二进制日志” 了解详情。

在RT索引中全文变化是基于事务的。他们会被存储到每个线程处理池中,并在COMMIT时立刻应用生效。在单次COMMIT中提交大量的批量更新有助于索引的快速生成。

4.4. 二进制日志

二进制日志本质上是一种故障恢复机制。当开启二进制日志时,searchd将每条事务处理写入到二进制日志,并可以用于在非正常关闭后恢复数据。当正常关闭时,内存区块的数据会写入到磁盘,然后删除所有的二进制日志文件。

在正常的操作中,每当达到binlog_max_log_size 的限制 (默认为 128M)时,就会打开一个新的二进制日志文件。已有的,已关闭的二进制日志文件会一直保存,直到它们存储的内容被刷新到磁盘区块中。如果设置限制为0,则在searchd运行的过程中永远不会删除二进制日志文件;但是在正常关闭时,还是会删除掉。

目前有三种不同的二进制日志刷新策略,由binlog_flush选 项来控制,设置为0表示每秒将日志刷新一次到操作系统和同步到磁盘,设置为1表示表示每次事务处理都刷新和同步,设置为2(默认模式)表示每次事务处理时 刷新单每秒同步一次。同步相对而言是比较慢的,因为他需要将数据物理写入到磁盘,所以模式1是最安全的模式(每次提交的事物处理,都确保是写入到磁盘 的)。刷新日志到操作系统是为了防止searchd崩溃时数据丢失,如果操作系统没有崩溃则数据可以安全写入到磁盘。模式2是默认的模式。

在非正常关闭后再次重启此进行数据恢复,二进制日志的内容会被重新执行,最后正常存储的所有事务处理都会被还原。事务处理都会被校验,以确保二进制日志文件中损坏的垃圾数据不会被 重新执行;因而一个破坏的事务处理将被检测到,在目前版本中它将不会被重新执行。事务处理都从一个特殊标志和时间戳开始,这样子为技术上处理跳过破坏的事 务处理并从下一条正确的开始继续执行、或者从某个指定时间戳开始重新执行事务提供了可能(时间点恢复),但是在版本1.10-beta时尚未实现该功能。

二进制日志存在一个副作用,那就是在一个小的RT索上更新时,除非正常关机,否则内存区块完全充足导致二进制日志文件不断增长而又永远都不会被删除掉。二 进制日志基本上只追加相对于最后在磁盘上完好保存状态的增量,除非内存区块得到保存,否则它们不会被删除。一个不断增长的二进制日志对于磁盘使用和崩溃恢 复时间都不是好事。从版本2.0.1-beta开始,可以通过rt_flush_period指令配置searchd来周期性刷新内存区块到磁盘以解决这个问题。当周期性刷新启用时,searchd会保留一个独立的线程用于检查RT索引的内存区块是否需要写回到磁盘。一旦写回到磁盘,各个二进制日志都可以(能够)安全的删除。

请注意rt_flush_period 仅控制检查发生的频率。但是这并不保证特别是内存区块一定会得到保存。例如,定期重新保存一个巨大的内存区块但是仅更新了寥寥数行是没有任何意义的。searchd守护进程将会通过某些启发式方法判断是否需要真的执行刷新。