Lucene 索引文件即使没有对其执行添加、更新或删除操作也会不断变化

Lucene index files changing constantly even when there is no adding, updating, or deletion operations performed on it

我注意到,我的 lucene 索引段文件(文件名)总是不断变化,即使我没有执行任何添加、更新或删除操作。我正在执行的唯一操作是阅读和搜索。所以,我的问题是,Lucene 索引段文件是否仅通过读取和搜索操作在内部以某种方式更新?

我正在使用 Lucene.Net v4.8 beta,如果有的话。谢谢!

这是我如何发现这个问题的示例(我想获取索引大小)。假设 Lucene 索引已经存在,我使用以下代码获取索引大小:

示例:

private long GetIndexSize()
        {
            var reader = GetDirectoryReader("validPath");
            long size = 0;

            foreach (var fileName in reader.Directory.ListAll())
            {
                size += reader.Directory.FileLength(fileName);
            }

            return size;
        }
private DirectoryReader GetDirectoryReader(string path)
{
    var directory = FSDirectory.Open(path);
    var reader = DirectoryReader.Open(directory);
    return reader;
}

以上方法每5分钟调用一次。它在 98% 的时间里工作正常。然而,另外 2% 的时间,我会在 foreach 循环中得到错误 file not found,调试后,我看到 reader.Directory 中的文件在计数上发生了变化。索引在特定时间由另一项服务更新,但我可以保证在发生此错误的时间附近的任何地方都没有对索引进行更新。

由于您有多个进程 writing/reading 同一组文件,因此很难隔离正在发生的事情。 Lucene.NET 做了加锁和异常处理,保证进程间的操作可以同步起来,但是如果不加锁直接读取目录下的文件,需要做好处理IOException的准备。

解决方案取决于您需要索引大小的最新程度:

  1. 如果有点过时没关系,我建议在目录本身上使用 DirectoryInfo.EnumerateFiles。这可能比 Directory.ListAll() 更新一点,因为该方法将文件名存储在一个数组中,该数组在循环完成之前可能会过时。但是,您仍然需要捕获 FileNotFoundException 并忽略它并可能处理其他 IOExceptions.
  2. 如果您需要绝对最新的大小并计划执行需要索引为该大小的操作,则需要打开写锁以防止文件在获取值时发生更改。

private long GetIndexSize()
{
    // DirectoryReader is superfluous for this example. Also,
    // using a MMapDirectory (which DirectoryReader.Open() may return)
    // will use more RAM than simply using SimpleFSDirectory.
    var directory = new SimpleFSDirectory("validPath");
    long size = 0;

    // NOTE: The lock will stay active until this is disposed,
    // so if you have any follow-on actions to perform, the lock
    // should be obtained before calling this method and disposed
    // after you have completed all of your operations.
    using Lock writeLock = directory.MakeLock(IndexWriter.WRITE_LOCK_NAME);

    // Obtain exclusive write access to the directory
    if (!writeLock.Obtain(/* optional timeout */))
    {
         // timeout failed, either throw an exception or retry...
    }

    foreach (var fileName in directory.ListAll())
    {
        size += directory.FileLength(fileName);
    }

    return size;
}

当然,如果你走那条路,你的 IndexWriter 可能会抛出一个 LockObtainFailedException 并且你应该准备好在写入过程中处理它们。

无论你如何处理它,你都需要捕获和处理异常,因为 IO 本质上有很多地方可能出错。但具体如何处理取决于您的优先事项。

原答案

如果您打开了一个 IndexWriter 实例,Lucene.NET 将 运行 后台进程根据正在使用的 MergePolicy 合并片段。默认设置可用于大多数应用程序。

但是,可以通过 IndexWriterConfig.MergePolicy property 配置设置。默认情况下,它使用 TieredMergePolicy.

var config = new IndexWriterConfig(LuceneVersion.LUCENE_48, analyzer)
{
    MergePolicy = new TieredMergePolicy()
};

several properties on TieredMergePolicy 可用于更改用于合并的阈值。

或者,它可以更改为不同的 MergePolicy 实现。 Lucene.NET 附带:

NoMergePolicy class 可用于完全禁用合并。

If your application never needs to add documents to the index (for example, if the index is built as part of the application deployment), it is also possible to use a IndexReader from a Directory instance directly, which does not do any background segment merges.

合并调度程序也可以交换 and/or 使用 IndexWriterConfig.MergeScheduler property 配置。默认情况下,它使用 ConcurrentMergeScheduler.

var config = new IndexWriterConfig(LuceneVersion.LUCENE_48, analyzer)
{
    MergePolicy = new TieredMergePolicy(),
    MergeScheduler = new ConcurrentMergeScheduler()
};

Lucene.NET 4.8.0 中包含的合并调度程序是:

NoMergeScheduler class 可用于完全禁用合并。这与使用 NoMergePolicy 具有相同的效果,但也会阻止执行任何调度代码。