Try to fix slow search query

This commit is contained in:
DariusIII
2026-03-17 13:33:33 +01:00
parent 29f5133d4a
commit fe538b3d47
2 changed files with 95 additions and 13 deletions
+55 -13
View File
@@ -103,29 +103,37 @@ class ReleaseSearchService
// Get order by clause
$orderBy = $this->getBrowseOrder($orderBy === '' ? 'posted_desc' : $orderBy);
// Build final SQL with pagination
$sql = sprintf(
'SELECT * FROM (%s) r ORDER BY r.%s %s LIMIT %d OFFSET %d',
$baseSql,
$orderBy[0], // @phpstan-ignore offsetAccess.notFound
$orderBy[1], // @phpstan-ignore offsetAccess.notFound
$limit,
$offset
);
$pagedIdsSql = $this->buildSearchPageIdsSql($whereSql, $orderBy, $limit, $offset);
// Check cache
$cacheKey = md5($this->getCacheVersion().$sql);
$cacheKey = md5($this->getCacheVersion().$pagedIdsSql.$baseSql);
$releases = Cache::get($cacheKey);
if ($releases !== null) {
return $releases;
}
// Execute query
$releases = Release::fromQuery($sql);
$pagedIds = array_map(
static fn (object $row): int => (int) $row->id,
DB::select($pagedIdsSql)
);
if ($pagedIds === []) {
return collect();
}
$pageWhereSql = sprintf('WHERE r.id IN (%s)', implode(',', $pagedIds));
$detailSql = sprintf(
'SELECT * FROM (%s) r ORDER BY FIELD(r.id, %s)',
$this->buildSearchBaseSql($pageWhereSql),
implode(',', $pagedIds)
);
// Execute query for the final page only.
$releases = Release::fromQuery($detailSql);
// Add total count for pagination
if ($releases->isNotEmpty()) {
$releases[0]->_totalrows = $this->getPagerCount($baseSql);
$releases[0]->_totalrows = $this->getReleasesCountForWhere($whereSql);
}
// Cache results
@@ -1209,6 +1217,40 @@ class ReleaseSearchService
);
}
/**
* Build the lightweight page-ID query for web search.
* This keeps the expensive joins out of the ORDER BY/LIMIT phase.
*
* @param array<string, mixed> $orderBy
*/
private function buildSearchPageIdsSql(string $whereSql, array $orderBy, int $limit, int $offset): string
{
return sprintf(
'SELECT r.id FROM releases r %s ORDER BY r.%s %s LIMIT %d OFFSET %d',
$whereSql,
$orderBy[0], // @phpstan-ignore offsetAccess.notFound
$orderBy[1], // @phpstan-ignore offsetAccess.notFound
$limit,
$offset
);
}
/**
* Get the total number of matching web-search releases without the expensive detail joins.
*/
private function getReleasesCountForWhere(string $whereSql): int
{
return $this->getPagerCount($this->buildSearchCountSql($whereSql));
}
/**
* Build the releases-only count query for web search pagination.
*/
private function buildSearchCountSql(string $whereSql): string
{
return sprintf('SELECT COUNT(*) as count FROM releases r %s', $whereSql);
}
/**
* Get the passworded releases clause.
*/
@@ -130,4 +130,44 @@ class ReleaseSearchServiceOrderingTest extends TestCase
$this->assertSame(550, $method->invoke($this->service, 50, 50));
$this->assertSame(2000, $method->invoke($this->service, 2500, 50));
}
/**
* Test that the paged ID query only selects release IDs from the releases table.
*/
public function test_build_search_page_ids_sql_uses_releases_only(): void
{
$reflection = new ReflectionClass(ReleaseSearchService::class);
$method = $reflection->getMethod('buildSearchPageIdsSql');
$sql = $method->invoke(
$this->service,
'WHERE r.passwordstatus = 0 AND r.id IN (1,2,3)',
['postdate', 'desc'],
50,
0
);
$this->assertSame(
'SELECT r.id FROM releases r WHERE r.passwordstatus = 0 AND r.id IN (1,2,3) ORDER BY r.postdate desc LIMIT 50 OFFSET 0',
$sql
);
$this->assertStringNotContainsString('JOIN', $sql);
}
/**
* Test that the lightweight count query is built directly against releases.
*/
public function test_build_search_count_sql_uses_releases_only_count_query(): void
{
$reflection = new ReflectionClass(ReleaseSearchService::class);
$method = $reflection->getMethod('buildSearchCountSql');
$sql = $method->invoke($this->service, 'WHERE r.passwordstatus = 0 AND r.id IN (1,2,3)');
$this->assertSame(
'SELECT COUNT(*) as count FROM releases r WHERE r.passwordstatus = 0 AND r.id IN (1,2,3)',
$sql
);
$this->assertStringNotContainsString('JOIN', $sql);
}
}