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

Spring 我们在应用程序中同时使用MassIndexer和Hibernate搜索中的手动索引是否正确?,spring,lucene,hibernate-search,Spring,Lucene,Hibernate Search,最近,我加入了一个使用Hibernate搜索的项目 我怀疑我们的应用程序有一个小故障,由于在两个位置使用FullTextEntityManager,导致其他后台作业忽略新索引的数据: 1) 在从UI搜索目标数据时,我们使用MassIndexer在第一次搜索请求时为数据编制索引,所有后续搜索请求都不会导致重新编制索引: private final AtomicBoolean initialized = new AtomicBoolean(false); ... public FullTextQue

最近,我加入了一个使用Hibernate搜索的项目

我怀疑我们的应用程序有一个小故障,由于在两个位置使用
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);
}
@Scheduled(初始延迟=1\u 000,固定延迟=5\u 000)
私有void索引auditvalues(){
Instant previousRunTime=…;//假定已设置数据
瞬时电流时间=。。。;
int page=0;
布尔hasMore=true;
while(hasMore){
hasMore=hsIndexingService.indexauditvalue(以前的运行时、当前时间、page++);
}
}
@事务(只读=真)
公共布尔索引AuditValue(Instant previousRunTime、Instant currentTime、int page){
PageRequest PageRequest=返回新的PageRequest(页面、批次大小、Sort.Direction.ASC、审核值、排序列);
Page pageResults=auditValueRepository.FindByauditTransactionLastModifiedBetween(previousRunTime、currentTime、pageRequest);
FullTextEntityManager FullTextEntityManager=getFullTextEntityManager();
List content=pageResults.getContent();
content.forEach(fullTextEntityManager::index);//这里我们对数据进行索引
返回pageResults.hasNext();
}
私有FullTextEntityManager getFullTextEntityManager(){
返回Search.getFullTextEntityManager(entityManager);
}
最近,我们的用户报告说新数据没有出现在搜索页面上,这可能是因为在两个不同步的单独线程中使用了2
FullTextEntityManager
s吗?如果是,如何解决

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

注释。我不确定这是否是您问题的一部分,但我还是要澄清:
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);
}
private final CountDownLatch initialIndexingsRemaining=新的CountDownLatch(1);
公共索引(){
initialIndexingsMaining.await();
}
@计划(初始延迟=0,固定延迟=5_000)
私有void索引auditvalues(){
如果(IsInitialinIndexingDone()){
doIncrementalIndexing();
}否则{
doInitialIndexing();
}
}
私有布尔值IsInitialinIndexingDone(){
返回initialIndexingsMaining.Wait(0,时间单位纳秒);
}
private void doInitialIndexing(){
//只有在上一次执行结束前可能再次调用调度的方法时,才需要在此进行同步。不确定是否可能?
已同步(此){
如果(IsInitialinIndexingDone()){
返回;
}
试一试{
fullTextEntityManager.createIndexer().startAndWait();
initialIndexingsMaining.countDown();
}捕捉(中断异常e){
Thread.currentThread().interrupt();
}
}
}
私有无效doIncrementalIndexing(){
Instant previousRunTime=…;//假定已设置数据
瞬时电流时间=。。。;
int page=0;
布尔hasMore=true;
while(hasMore){
hasMore=hsIndexingService.indexauditvalue(以前的运行时、当前时间、page++);
}
}
@事务(只读=真)
公共布尔索引AuditValue(Instant previousRunTime、Instant currentTime、int page){
PageRequest PageRequest=返回新的PageRequest(页面、批次大小、Sort.Direction.ASC、审核值、排序列);
Page pageResults=auditValueRepository.FindByauditTransactionLastModifiedBetween(previousRunTime、currentTime、pageRequest);
FullTextEntityManager FullTextEntityManager=getFullTextEntityManager();
List content=pageResults.getContent();
content.forEach(fullTextEntityManager::index);//这里我们对数据进行索引
返回pageResults.hasNext();
}
私有FullTextEntityManager getFullTextEntityManager(){
返回Search.getFullTextEntityManager(entityManager);
}