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

问题描述 投票:0回答:2

最近,我加入了一个使用 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 个

FullTextEntityManager
?如果有的话如何解决?

我们使用文件Spring boot、Hibernate Search、Lucene,并将索引存储在文件系统中。 实体用

@Indexed
注释,可搜索字段用
@Field
注释。

spring lucene hibernate-search
2个回答
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 将自动更新索引。


0
投票
import com.example.demo.model.Book;
import jakarta.persistence.EntityManager;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class EntityIndexer  implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private final EntityManager entityManager;

    public EntityIndexer(EntityManager entityManager) {
        this.entityManager = entityManager;
    }


    @Override
    @Transactional
    @Async
    public void onApplicationEvent(ContextRefreshedEvent event) {
        SearchSession searchSession = Search.session(entityManager);

        MassIndexer indexer = searchSession.massIndexer(Book.class).threadsToLoadObjects(7);
        try {
            indexer.startAndWait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }
}



© www.soinside.com 2019 - 2024. All rights reserved.