# 实时表结构

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

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

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

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

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

这与标准的 [分布式表](../Creating_a_table/Creating_a_distributed_table/Creating_a_distributed_table.md) 非常相似，由几个本地表组成。

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

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

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

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

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

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

* 服务器将收集的数据保存为磁盘表
* 或在干净关机时或通过 [手动刷新](../Securing_and_compacting_a_table/Flushing_RAM_chunk_to_disk.md#FLUSH-TABLE) 将内存部分转储

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

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

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

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

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

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

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

