【问题标题】:Is it correct that we use both, MassIndexer and manual indexing from Hibernate Search in our application?我们在应用程序中同时使用 MassIndexer 和 Hibernate Search 中的手动索引是否正确?
【发布时间】:2019-01-24 19:41:32
【问题描述】:

最近,我加入了一个使用 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个FullTextEntityManagers?如果是,如何解决?

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

【问题讨论】:

    标签: spring lucene hibernate-search


    【解决方案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 将自动更新索引。

    【讨论】:

    • 感谢您的详细解答!问题出在 Hibernate 转换器驱动程序中(来自 DB 的日期被 DB 驱动程序错误地转换)。它为Instant 对象增加了 +2 小时,并且每当我们请求数据时,它都会将过滤器移动 2 小时。为了避免这个问题,我们决定使用LocalDateTime,它不会在 DB 和 Hibernate 之间产生这种转变。至于后台作业,在应用程序的 2 个实例上同步索引是一种临时性黑客攻击,由于我们将添加具有集中存储的 Elastic Search,因此很快就会删除此黑客攻击。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-15
    • 1970-01-01
    • 2011-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-28
    相关资源
    最近更新 更多