≫ 保护和压缩表

实时表结构

可以使用一个称为 indexer 的特殊工具从外部源创建一个普通表,该工具从配置中读取“配方”,连接数据源,拉取文档,并构建表文件。这是一个耗时的过程。如果数据发生变化,表就会过时,需要从更新后的源重新构建。如果数据是增量变化的,例如一个博客或新闻推送,其中旧文档从不变更,只添加新文档,那么重建将花费越来越多的时间,因为每次都需要一次又一次地处理存档源。

解决这个问题的一种方式是使用多个表,而不是一个单一的大表。例如,你可以处理前几年生成的源并保存表。然后,只取当前年的源放入单独的表中,并根据需要频繁重建。然后你可以将这两个表作为分布式表的部分放置并用于查询。关键在于每次重建时,你最多只处理最近12个月的数据,而含有旧数据的表保持不变,无需重建。你还可以更进一步,将最近12个月的数据表拆分为月表、周表或者日表,依此类推。

这种方法是有效的,但你需要手动维护分布式表。也就是说,需要添加新的分片,删除旧的,并保持部分表的总数不宜过多(表数量过多搜索可能变慢,操作系统通常限制同时打开的文件数量)。为应对这一点,你可以通过运行 indexer --merge 手动合并几个表。但这只解决了表太多导致维护困难的问题。即使采用“每小时”重建索引,你仍然很可能在新数据到达源和重建表时间之间存在明显的时间差,而这段时间内数据无法被搜索。

实时表的设计旨在解决该问题。它由两部分组成:

  1. 一个特殊的基于内存的表(称为 RAM 分片),包含当前正在到达的数据部分。
  2. 一组普通表,称为磁盘分片,是过去构建的。

这与标准的 分布式表 非常相似,由几个本地表组成。

你不需要通过运行 indexer 构建这样的表,indexer 是读取配置中的“配方”和表数据源。相反,实时表提供了“插入”和“替换”现有文档的能力。执行“插入”命令时,你将新文档推送到服务器。服务器立刻从添加的文档构建一个小表并在线发布。因此,在“插入”命令完成后,你可以对所有表部分(包括刚添加的文档)执行搜索。

搜索服务器自动维护该表,因此你不用担心。但你可能想了解一些“它是如何维护的”细节。

首先,由于索引数据存储在内存中——断电怎么办?我会丢失表么?其实,在完成之前,服务器会将新数据保存到一个特殊的“二进制日志(binlog)”中。它由一个或多个文件组成,这些文件存在于持久存储中,随着越来越多的更改而增大。你可以调节新查询(或事务)存储到 binlog 的频率,以及执行 binlog 文件上的“同步”命令的频率,以强制操作系统将数据安全地写入存储。最保守的方法是每笔事务后都刷新并同步。这是最慢但最安全的方式。最省资源的方法是完全关闭 binlog,这是最快的,但你有丢失索引数据的风险。中间的变种,比如每秒刷新/同步也是支持的。

binlog 是专门为顺序保存新到事务设计的;它不是表,不能被搜索。它仅仅是保险策略,确保服务器不会丢失你的数据。如果突发故障发生,软件或硬件问题导致崩溃,服务器将加载 RAM 分片的最新可用转储文件,然后重放 binlog,重复存储的事务。最终,服务器将达到最后一次更改时的状态。

其次,限制是什么?如果我想处理比如说 10TB 的数据,但内存装不下怎么办!实时表的内存容量是有限的且可配置。当索引到一定数据量时,服务器通过合并小事务管理内存表部分,保持事务数量和总体大小较小。然而,这有时会导致插入延迟。当合并不再改善状况,且新插入达到 内存限制 时,服务器会将基于内存的表转成存储在磁盘上的普通表(称为磁盘分片)。此表加入 RT 表第二部分的表集合中并上线。内存被刷新,空间被释放。

当内存中的数据安全保存到磁盘时,发生以下情况:

  • 服务器将收集的数据保存为磁盘表
  • 或在干净关机时或通过 手动刷新 将内存部分转储

该表对应的 binlog 就不再需要,因此被丢弃。如果所有表都已经保存,binlog 将被删除。

第三,磁盘集合呢? 如果拥有许多磁盘部分会导致搜索变慢,那么我手动以分布式表方式制作它们,或者它们由RT表产生的磁盘部分(或者称作“块”)有什么区别呢?其实这两种情况下,你都可以将几个表合并成一个。例如,你可以合并昨天的按小时划分的多个表,保留一个“昨天的每日”表。手动维护时,你需要自己考虑模式和命令。而使用RT表,服务器提供了OPTIMIZE命令,它做同样的事情,但让你避免了不必要的内部细节。

第四,如果我的“文档”构成一个“迷你表”,且我不再需要它,我可以直接扔掉它。但如果它被“优化”过,也就是说和大量其他文档混合在一起,我怎么撤销或删除它呢? 是的,索引文档是“混合”在一起的,没有简单的方法能删除某一个文档而不重建整个表。而对于普通表,重建或合并只是正常的维护方式;对于实时表,这只保持了操作的简便性,但不保证“实时性”。为了解决这个问题,Manticore使用了一个技巧:当你删除一个由文档ID标识的文档时,服务器只是记录该编号。和其他被删除的文档一起,它们的ID被保存到所谓的kill-list中。当你对表执行搜索时,服务器首先检索所有匹配的文档,然后去除在kill-list中出现的文档(这是最基本的描述,实际上内部更为复杂)。关键是——为了实现“即时”删除,文档实际上并没有真正删除,而只是标记为“已删除”。它们仍占据不同表结构中的空间,本质上是垃圾。影响排名的词统计数据也不受影响,这意味着它的工作方式确实如声明:我们在所有文档中搜索,然后仅在最终结果中隐藏标记为已删除的文档。当一个文档被替换时,意味着它在表的旧部分被标记为已删除(杀死),并在最新部分重新插入。“通过killlist隐藏”的所有后果在此情况下同样适用。

当表的某个部分进行重建时,例如,当RAM块的某些事务(段)被合并,或者RAM块被转换成磁盘块,或者两个磁盘块合并时,服务器会对受影响的部分进行全面迭代,并从所有这些部分中物理排除被删除的文档。也就是说,如果它们存在于某些词的文档列表中,就会被清除。如果它是唯一的词,则会完全删除。

总结一下:删除分两个阶段进行:

  1. 首先,在实时中将文档标记为“已删除”,并在搜索结果中抑制它们。
  2. 在对RT表块执行某些操作时,最终彻底物理删除这些已删除的文档。

第五,如果RT表其集合中包含普通磁盘表,我能否直接将已有的旧磁盘表添加进去? 不可以。为避免不必要的复杂性和防止意外损坏,这是不允许的。但是,如果你的RT表刚创建且无数据,则可以通过ATTACH TABLE将你的磁盘表附加到它中。你的旧表将被移动到RT表内,并成为其一部分。

关于RT表结构的总结:它是一个巧妙组织的普通磁盘表集合,配有一个快速的内存表,用于实时插入和半实时删除文档。RT表拥有统一的模式、统一的设置,并且可以轻松维护,无需深入细节。

Last modified: August 28, 2025