fix: prevent ES CPU saturation from has_child scoring all posts
Without minimum_should_match=1 on the inner post bool query, Elasticsearch
defaults MSM to 0 whenever a filter clause is present. This caused has_child
to score every non-hidden post on every search request, saturating CPU on
shared ES nodes with large corpora (3.7M+ posts).
- Add BoolQuery subclass with create() override and minimumShouldMatch()
(spatie/elasticsearch-query-builder 1.x uses `new self()` in create(),
so subclassing requires overriding it)
- Set minimumShouldMatch(1) on the inner post query after adding the
is_hidden filter, so only genuinely matching posts are scored
- Remove the operator('or') clause from buildShouldClauses() — with
Turkish min_ngram=2, 'or' generates 2-gram tokens that match nearly
every post, causing near-total index scans
- Add ES client timeouts (connect: 2s, query: 10s) to prevent Apache
mod_php worker saturation when ES is slow or unreachable
This commit is contained in:
parent
c54f37349f
commit
d8eebb183a
|
|
@ -37,7 +37,7 @@ use Illuminate\Support\Str;
|
|||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Spatie\ElasticsearchQueryBuilder\Builder;
|
||||
use Spatie\ElasticsearchQueryBuilder\Queries\BoolQuery;
|
||||
use Blomstra\Search\Elasticsearch\BoolQuery;
|
||||
use Spatie\ElasticsearchQueryBuilder\Queries\TermQuery;
|
||||
use Spatie\ElasticsearchQueryBuilder\Sorts\Sort;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
|
@ -217,6 +217,10 @@ class SearchController extends ListDiscussionsController
|
|||
$postQuery->add(TermQuery::create('is_hidden', 'false'), 'filter');
|
||||
}
|
||||
|
||||
// Without minimum_should_match, ES default MSM is 0 when a filter clause is present,
|
||||
// causing has_child to score every non-hidden post instead of only matching ones.
|
||||
$postQuery->minimumShouldMatch(1);
|
||||
|
||||
$textQuery->add(
|
||||
HasChildQuery::create('post', $postQuery)->withInnerHits(),
|
||||
'should'
|
||||
|
|
@ -235,7 +239,6 @@ class SearchController extends ListDiscussionsController
|
|||
}
|
||||
if ($this->matchWords) {
|
||||
$should->add((new MatchQuery('content', $search))->operator('and')->boost(1.8 * $boost), 'should');
|
||||
$should->add((new MatchQuery('content', $search))->operator('or')->boost(0.8 * $boost), 'should');
|
||||
}
|
||||
|
||||
return $should;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of blomstra/search.
|
||||
*
|
||||
* Copyright (c) 2022 Blomstra Ltd.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE.md
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Blomstra\Search\Elasticsearch;
|
||||
|
||||
class BoolQuery extends \Spatie\ElasticsearchQueryBuilder\Queries\BoolQuery
|
||||
{
|
||||
protected ?int $minimumShouldMatch = null;
|
||||
|
||||
public static function create(): static
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function minimumShouldMatch(int $minimum): static
|
||||
{
|
||||
$this->minimumShouldMatch = $minimum;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = parent::toArray();
|
||||
|
||||
if ($this->minimumShouldMatch !== null) {
|
||||
$array['bool']['minimum_should_match'] = $this->minimumShouldMatch;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,13 @@ class Provider extends AbstractServiceProvider
|
|||
|
||||
$this->container->singleton(Elastic::class, function (Container $container) use ($settings, $config) {
|
||||
$builder = ClientBuilder::create()
|
||||
->setHosts([$settings->get('blomstra-search.elastic-endpoint')]);
|
||||
->setHosts([$settings->get('blomstra-search.elastic-endpoint')])
|
||||
->setConnectionParams([
|
||||
'client' => [
|
||||
'connect_timeout' => 2, // fail fast if ES is unreachable
|
||||
'timeout' => 10, // allow time for complex queries
|
||||
],
|
||||
]);
|
||||
|
||||
if ($config->inDebugMode()) {
|
||||
$builder->setLogger($container->make(LoggerInterface::class));
|
||||
|
|
|
|||
Loading…
Reference in New Issue