在我们的应用程序中同时使用MassIndexer和Hibernate Search的手动索引是否正确?

时间:2019-01-24 19:41:33

标签: spring lucene hibernate-search

最近,我加入了一个使用Hibernate Search的项目。

我怀疑我们的应用存在故障,由于在2个地方使用FullTextEntityManager,导致其他后台作业忽略了新索引的数据:

1)在从UI执行目标数据搜索时,我们使用MassIndexer在第一个搜索请求上对数据进行索引,并且所有后续搜索请求都不会导致重新索引:

private final AtomicBoolean initialized = new AtomicBoolean(false);
...
public FullTextQuery buildTransactionSearchQuery(SearchRequestDTO request) {
    final FullTextEntityManager fullTextEntityManager = getFullTextEntityManager();

    final Query expression = buildTransactionSearchExpression(request.getFilter(), fullTextEntityManager);
    final FullTextQuery query = fullTextEntityManager.createFullTextQuery(expression, Transaction.class);

    return query;
}
...

private FullTextEntityManager getFullTextEntityManager() {
    final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);

    if (initialized.get()) {
        return fullTextEntityManager;
    } else {
        synchronized (initialized) {
            if (!initialized.getAndSet(true)) {
                try {
                    fullTextEntityManager.createIndexer().startAndWait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            return fullTextEntityManager;
        }
    }
}

2)在后台作业中:

@Scheduled(initialDelay = 1_000, fixedDelay = 5_000)
private void indexAuditValues() {
    Instant previousRunTime = ...; // assume data is set
    Instant currentTime = ...;

    int page = 0;
    boolean hasMore = true;

    while (hasMore) {
        hasMore = hsIndexingService.indexAuditValues(previousRunTime, currentTime, page++);
    }
}

@Transactional(readOnly = true)
public boolean indexAuditValues(Instant previousRunTime, Instant currentTime, int page) {
    PageRequest pageRequest = return new PageRequest(page, batchSize, Sort.Direction.ASC, AUDIT_VALUE_SORT_COLUMN);

    Page<AuditValue> pageResults = auditValueRepository.findByAuditTransactionLastModifiedDateBetween(previousRunTime, currentTime, pageRequest);

    FullTextEntityManager fullTextEntityManager = getFullTextEntityManager();

    List<AuditValue> content = pageResults.getContent();
    content.forEach(fullTextEntityManager::index);  // here we do index the data

    return pageResults.hasNext();
}

private FullTextEntityManager getFullTextEntityManager() {
    return Search.getFullTextEntityManager(entityManager);
}

最近,我们的用户报告说,新数据未出现在搜索页面上,是否可能由于在2个未同步的独立线程中使用了2个FullTextEntityManager而引起的?如果是,如何解决?

我们使用文件Spring Boot,Hibernate Search,Lucene并将索引存储在文件系统中。 实体用@Indexed注释,可搜索字段用@Field注释。

1 个答案:

答案 0 :(得分:1)

我不确定这是否是您的问题的一部分,但是无论如何我都会明确:FullTextEntityManager可以在两个单独的线程中使用,只要您使用的是不同的实体管理器即可。而且,如果您使用的是Spring,则很可能会这样做。所以那里一切都很好。

我在您的设置中看到的主要问题是,这两种方法可能同时执行(如果第一个搜索查询是在第一个计划索引之前或期间发送的)。但是在那种情况下,您宁愿在索引中得到重复的文档,也不愿丢失文档(由于质量索引器的工作方式)。所以我真的不知道怎么了。

我建议不要在查询方法中懒惰地执行质量索引,更重要的是避免在请求线程中等待可能长时间运行的操作(质量索引):这是一个主要的反模式。

理想情况下,仅在重新部署应用程序时(客户不使用该应用程序时)才应建立索引,并在重新启动后重新使用索引。这样一来,您就不必再等待大量索引的请求:当任何人访问该应用程序时,所有内容都已被索引。

但是您没有执行任何操作,因此我认为您有您的理由。如果您真的想在启动时对所有内容重新编制索引,并在大规模索引编制尚未结束的情况下阻止搜索请求,则下面的内容应该更安全。也许并非完美无缺(实际上取决于您的模型:我不知道是否可以更新审核值),但是更安全。

1)在从UI执行目标数据搜索时,阻止请求,直到初始索引结束为止[再一次,这是一个坏主意,但对每个人来说都是这样]。

// Assuming the background job class is named "IndexInitializer"
@Autowired
IndexInitializer indexInitializer;

...
public FullTextQuery buildTransactionSearchQuery(SearchRequestDTO request) {
    final FullTextEntityManager fullTextEntityManager = getFullTextEntityManager();

    final Query expression = buildTransactionSearchExpression(request.getFilter(), fullTextEntityManager);
    final FullTextQuery query = fullTextEntityManager.createFullTextQuery(expression, Transaction.class);

    return query;
}
...

private FullTextEntityManager getFullTextEntityManager() {
    indexInitializer.awaitInitialIndexing();
    return Search.getFullTextEntityManager(entityManager);
}

2)在后台作业中,在第一个刻度上使用质量索引器,并在随后的每个刻度上使用增量索引:

private final CountDownLatch initialIndexingsRemaining = new CountDownLatch(1);

public void awaitInitialIndexing() {
    initialIndexingsRemaining.await();
}

@Scheduled(initialDelay = 0, fixedDelay = 5_000)
private void indexAuditValues() {
    if (isInitialIndexingDone()) {
        doIncrementalIndexing();
    } else {
        doInitialIndexing();
    }
}

private boolean isInitialIndexingDone() {
    return initialIndexingsRemaining.await(0, TimeUnit.NANOSECONDS);
}

private void doInitialIndexing() {
    // Synchronization is only necessary here if the scheduled method may be called again before the previous execution is over. Not sure it's possible?
    synchronized (this) {
        if (isInitialIndexingDone()) {
            return;
        }
        try {
            fullTextEntityManager.createIndexer().startAndWait();
            initialIndexingsRemaining.countDown();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

private void doIncrementalIndexing() {
    Instant previousRunTime = ...; // assume data is set
    Instant currentTime = ...;

    int page = 0;
    boolean hasMore = true;

    while (hasMore) {
        hasMore = hsIndexingService.indexAuditValues(previousRunTime, currentTime, page++);
    }
}

@Transactional(readOnly = true)
public boolean indexAuditValues(Instant previousRunTime, Instant currentTime, int page) {
    PageRequest pageRequest = return new PageRequest(page, batchSize, Sort.Direction.ASC, AUDIT_VALUE_SORT_COLUMN);

    Page<AuditValue> pageResults = auditValueRepository.findByAuditTransactionLastModifiedDateBetween(previousRunTime, currentTime, pageRequest);

    FullTextEntityManager fullTextEntityManager = getFullTextEntityManager();

    List<AuditValue> content = pageResults.getContent();
    content.forEach(fullTextEntityManager::index);  // here we do index the data

    return pageResults.hasNext();
}

private FullTextEntityManager getFullTextEntityManager() {
    return Search.getFullTextEntityManager(entityManager);
}

另一方面,您也可以使用自动动态索引来代替手动的定期索引编制:当在Hibernate ORM中持久/更新/删除实体时,Hibernate Search将自动更新索引。