Speed up tags

old-stable24
Varun Patil 2022-10-27 12:54:51 -07:00
parent 759076c4ac
commit de845bd543
4 changed files with 66 additions and 20 deletions

View File

@ -278,7 +278,7 @@ class ApiController extends Controller
// Run actual query // Run actual query
$list = []; $list = [];
$t = (int) ($this->request->getParam('t')); $t = (int) $this->request->getParam('t');
if ($t & 1) { // personal if ($t & 1) { // personal
$list = array_merge($list, $this->timelineQuery->getAlbums($user->getUID())); $list = array_merge($list, $this->timelineQuery->getAlbums($user->getUID()));
} }

View File

@ -22,20 +22,27 @@ class TimelineQuery
$this->connection = $connection; $this->connection = $connection;
} }
public static function debugQuery(IQueryBuilder $query, string $sql = '') public static function debugQuery(IQueryBuilder &$query, string $sql = '')
{ {
// Print the query and exit // Print the query and exit
$sql = empty($sql) ? $query->getSQL() : $sql; $sql = empty($sql) ? $query->getSQL() : $sql;
$params = $query->getParameters();
$sql = str_replace('*PREFIX*', 'oc_', $sql); $sql = str_replace('*PREFIX*', 'oc_', $sql);
foreach ($params as $key => $value) { self::replaceQueryParams($query, $sql);
$sql = str_replace(':'.$key, $query->getConnection()->getDatabasePlatform()->quoteStringLiteral($value), $sql);
}
echo "{$sql}"; echo "{$sql}";
exit; exit;
} }
public static function replaceQueryParams(IQueryBuilder &$query, string $sql)
{
$params = $query->getParameters();
foreach ($params as $key => $value) {
$sql = str_replace(':'.$key, $query->getConnection()->getDatabasePlatform()->quoteStringLiteral($value), $sql);
}
return $sql;
}
public function getInfoById(int $id): array public function getInfoById(int $id): array
{ {
$qb = $this->connection->getQueryBuilder(); $qb = $this->connection->getQueryBuilder();

View File

@ -179,9 +179,9 @@ trait TimelineQueryDays
return $day; return $day;
} }
private function executeQueryWithCTEs(IQueryBuilder &$query) private function executeQueryWithCTEs(IQueryBuilder &$query, string $psql = '')
{ {
$sql = $query->getSQL(); $sql = empty($psql) ? $query->getSQL() : $psql;
$params = $query->getParameters(); $params = $query->getParameters();
$types = $query->getParameterTypes(); $types = $query->getParameterTypes();
@ -196,7 +196,7 @@ trait TimelineQueryDays
/** /**
* Get all folders inside a top folder. * Get all folders inside a top folder.
*/ */
private function joinSubfoldersRecursive( private function addSubfolderJoinParams(
IQueryBuilder &$query, IQueryBuilder &$query,
Folder &$folder, Folder &$folder,
bool $archive bool $archive
@ -226,7 +226,6 @@ trait TimelineQueryDays
// Add query parameters // Add query parameters
$query->setParameter('topFolderId', $topFolderId, IQueryBuilder::PARAM_INT); $query->setParameter('topFolderId', $topFolderId, IQueryBuilder::PARAM_INT);
$query->setParameter('excludedFolderId', $excludedFolderId, IQueryBuilder::PARAM_INT); $query->setParameter('excludedFolderId', $excludedFolderId, IQueryBuilder::PARAM_INT);
$query->innerJoin('f', 'cte_folders', 'cte_f', $query->expr()->eq('f.parent', 'cte_f.fileid'));
} }
/** /**
@ -253,7 +252,8 @@ trait TimelineQueryDays
$pathOp = null; $pathOp = null;
if ($recursive) { if ($recursive) {
// Join with folders CTE // Join with folders CTE
$this->joinSubfoldersRecursive($query, $folder, $archive); $this->addSubfolderJoinParams($query, $folder, $archive);
$query->innerJoin('f', 'cte_folders', 'cte_f', $query->expr()->eq('f.parent', 'cte_f.fileid'));
} else { } else {
// If getting non-recursively folder only check for parent // If getting non-recursively folder only check for parent
$pathOp = $query->expr()->eq('f.parent', $query->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT)); $pathOp = $query->expr()->eq('f.parent', $query->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT));

View File

@ -58,7 +58,17 @@ trait TimelineQueryTags
$query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.fileid', 'stom.objectid')); $query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.fileid', 'stom.objectid'));
// WHERE these photos are in the user's requested folder recursively // WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $folder, true, false); // This is a hack to speed up the query instead of using joinFilecache
// The problem is objectid is VARCHAR(64) and fileid is BIGINT(20), so a
// join is extremely slow. Instead, we use a subquery to check existence.
//
// https://blog.sqlauthority.com/2010/06/05/sql-server-convert-in-to-exists-performance-talk/
$this->addSubfolderJoinParams($query, $folder, false);
$query->innerJoin('m', 'filecache', 'f', $query->expr()->andX(
$query->expr()->eq('f.fileid', 'm.fileid'),
$query->createFunction('EXISTS (SELECT 1 from *PREFIX*cte_folders WHERE *PREFIX*cte_folders.fileid = `f`.parent)')
));
// GROUP and ORDER by tag name // GROUP and ORDER by tag name
$query->groupBy('st.name'); $query->groupBy('st.name');
@ -80,11 +90,17 @@ trait TimelineQueryTags
public function getTagPreviews(array &$tags, Folder &$folder) public function getTagPreviews(array &$tags, Folder &$folder)
{ {
// This is really horrible but will have to do for now
$sql = '';
foreach ($tags as &$tag) { foreach ($tags as &$tag) {
if (!empty($sql)) {
$sql .= ' UNION ALL ';
}
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
// SELECT all photos with this tag // SELECT all photos with this tag
$query->select('f.fileid', 'f.etag')->from( $query->select('f.fileid', 'f.etag', 'stom.systemtagid')->from(
'systemtag_object_mapping', 'systemtag_object_mapping',
'stom' 'stom'
)->where( )->where(
@ -96,19 +112,42 @@ trait TimelineQueryTags
$query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.fileid', 'stom.objectid')); $query->innerJoin('stom', 'memories', 'm', $query->expr()->eq('m.fileid', 'stom.objectid'));
// WHERE these photos are in the user's requested folder recursively // WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $folder, true, false); // See the function above for an explanation of this hack
$this->addSubfolderJoinParams($query, $folder, false);
$query->innerJoin('m', 'filecache', 'f', $query->expr()->andX(
$query->expr()->eq('f.fileid', 'm.fileid'),
$query->createFunction('EXISTS (SELECT 1 from *PREFIX*cte_folders WHERE *PREFIX*cte_folders.fileid = `f`.parent)')
));
// MAX 4 // MAX 4
$query->setMaxResults(4); $query->setMaxResults(4);
// FETCH tag previews // Replace parameters
$cursor = $this->executeQueryWithCTEs($query); $thisSql = self::replaceQueryParams($query, $query->getSQL());
$tag['previews'] = $cursor->fetchAll();
// Post-process // Add clause
foreach ($tag['previews'] as &$row) { $sql .= "({$thisSql})";
$row['fileid'] = (int) $row['fileid']; }
// FETCH tag previews
$cursor = $this->executeQueryWithCTEs($query, $sql);
$ans = $cursor->fetchAll();
// Post-process
$previewMap = [];
foreach ($ans as &$row) {
$row['fileid'] = (int) $row['fileid'];
$key = (int) $row['systemtagid'];
unset($row['systemtagid']);
if (!isset($previewMap[$key])) {
$previewMap[$key] = [];
} }
$previewMap[$key][] = $row;
}
// Add previews to tags
foreach ($tags as &$tag) {
$tag['previews'] = $previewMap[$tag['id']] ?? [];
} }
} }
} }