mirror of
https://github.com/immich-app/immich.git
synced 2026-10-17 05:12:35 +08:00
perf(server): check shared link asset access without scanning the album (#32411)
* perf(server): check shared link asset access without scanning the album checkSharedLinkAccess runs on every thumbnail, preview, playback and asset info request made through a shared link. It joined every asset of the link's album and only then filtered on an array overlap across four outer-joined columns, so each request cost O(album size). Split it into two branches (link assets, album assets) that find the requested ids through the asset pkey / livePhotoVideoId index and then probe the album_asset pkey. Median per request, PG 14, 1.36M-asset instance: | album behind the link | before | after | |-----------------------|--------|---------| | 290 assets | 2.1 ms | 0.17 ms | | 150k assets | 296 ms | 0.19 ms | * chore: clean up --------- Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
@@ -125,29 +125,53 @@ where
|
||||
and "asset"."id" in ($2)
|
||||
|
||||
-- AccessRepository.asset.checkSharedLinkAccess
|
||||
with
|
||||
"target" as (
|
||||
select
|
||||
array[$1]::uuid[] as "ids"
|
||||
),
|
||||
"link" as (
|
||||
select
|
||||
"shared_link"."id",
|
||||
"shared_link"."albumId"
|
||||
from
|
||||
"shared_link"
|
||||
where
|
||||
"shared_link"."id" = $2
|
||||
)
|
||||
select
|
||||
"asset"."id" as "assetId",
|
||||
"asset"."livePhotoVideoId" as "assetLivePhotoVideoId",
|
||||
"albumAssets"."id" as "albumAssetId",
|
||||
"albumAssets"."livePhotoVideoId" as "albumAssetLivePhotoVideoId"
|
||||
"asset"."id",
|
||||
"asset"."livePhotoVideoId"
|
||||
from
|
||||
"shared_link"
|
||||
left join "album" on "album"."id" = "shared_link"."albumId"
|
||||
and "album"."deletedAt" is null
|
||||
left join "shared_link_asset" on "shared_link_asset"."sharedLinkId" = "shared_link"."id"
|
||||
left join "asset" on "asset"."id" = "shared_link_asset"."assetId"
|
||||
and "asset"."deletedAt" is null
|
||||
left join "album_asset" on "album_asset"."albumId" = "album"."id"
|
||||
left join "asset" as "albumAssets" on "albumAssets"."id" = "album_asset"."assetId"
|
||||
and "albumAssets"."deletedAt" is null
|
||||
"asset"
|
||||
cross join "target"
|
||||
inner join lateral (
|
||||
select
|
||||
1 as "match"
|
||||
from
|
||||
"link"
|
||||
inner join "shared_link_asset" on "shared_link_asset"."sharedLinkId" = "link"."id"
|
||||
where
|
||||
"shared_link_asset"."assetId" = "asset"."id"
|
||||
union all
|
||||
select
|
||||
1 as "match"
|
||||
from
|
||||
"link"
|
||||
inner join "album" on "album"."id" = "link"."albumId"
|
||||
and "album"."deletedAt" is null
|
||||
inner join "album_asset" on "album_asset"."albumId" = "album"."id"
|
||||
where
|
||||
"album_asset"."assetId" = "asset"."id"
|
||||
limit
|
||||
$3
|
||||
) as "membership" on true
|
||||
where
|
||||
"shared_link"."id" = $1
|
||||
and array[
|
||||
"asset"."id",
|
||||
"asset"."livePhotoVideoId",
|
||||
"albumAssets"."id",
|
||||
"albumAssets"."livePhotoVideoId"
|
||||
] && array[$2]::uuid[]
|
||||
"asset"."deletedAt" is null
|
||||
and (
|
||||
"asset"."id" = any ("target"."ids")
|
||||
or "asset"."livePhotoVideoId" = any ("target"."ids")
|
||||
)
|
||||
|
||||
-- AccessRepository.assetFile.checkOwnerAccess
|
||||
select
|
||||
|
||||
@@ -236,43 +236,53 @@ class AssetAccess {
|
||||
}
|
||||
|
||||
return this.db
|
||||
.selectFrom('shared_link')
|
||||
.leftJoin('album', (join) => join.onRef('album.id', '=', 'shared_link.albumId').on('album.deletedAt', 'is', null))
|
||||
.leftJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'shared_link.id')
|
||||
.leftJoin('asset', (join) =>
|
||||
join.onRef('asset.id', '=', 'shared_link_asset.assetId').on('asset.deletedAt', 'is', null),
|
||||
.with('target', (qb) => qb.selectNoFrom(sql<string[]>`array[${sql.join([...assetIds])}]::uuid[]`.as('ids')))
|
||||
.with('link', (qb) =>
|
||||
qb
|
||||
.selectFrom('shared_link')
|
||||
.select(['shared_link.id', 'shared_link.albumId'])
|
||||
.where('shared_link.id', '=', sharedLinkId),
|
||||
)
|
||||
.leftJoin('album_asset', 'album_asset.albumId', 'album.id')
|
||||
.leftJoin('asset as albumAssets', (join) =>
|
||||
join.onRef('albumAssets.id', '=', 'album_asset.assetId').on('albumAssets.deletedAt', 'is', null),
|
||||
.selectFrom('asset')
|
||||
.crossJoin('target')
|
||||
.innerJoinLateral(
|
||||
(eb) =>
|
||||
eb
|
||||
.selectFrom('link')
|
||||
.innerJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'link.id')
|
||||
.select(sql.lit(1).as('match'))
|
||||
.whereRef('shared_link_asset.assetId', '=', 'asset.id')
|
||||
.unionAll(
|
||||
eb
|
||||
.selectFrom('link')
|
||||
.innerJoin('album', (join) =>
|
||||
join.onRef('album.id', '=', 'link.albumId').on('album.deletedAt', 'is', null),
|
||||
)
|
||||
.innerJoin('album_asset', 'album_asset.albumId', 'album.id')
|
||||
.select(sql.lit(1).as('match'))
|
||||
.whereRef('album_asset.assetId', '=', 'asset.id'),
|
||||
)
|
||||
.limit(1)
|
||||
.as('membership'),
|
||||
(join) => join.onTrue(),
|
||||
)
|
||||
.select([
|
||||
'asset.id as assetId',
|
||||
'asset.livePhotoVideoId as assetLivePhotoVideoId',
|
||||
'albumAssets.id as albumAssetId',
|
||||
'albumAssets.livePhotoVideoId as albumAssetLivePhotoVideoId',
|
||||
])
|
||||
.where('shared_link.id', '=', sharedLinkId)
|
||||
.where(
|
||||
sql`array["asset"."id", "asset"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"]`,
|
||||
'&&',
|
||||
sql`array[${sql.join([...assetIds])}]::uuid[] `,
|
||||
.select(['asset.id', 'asset.livePhotoVideoId'])
|
||||
.where('asset.deletedAt', 'is', null)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb('asset.id', '=', eb.fn.any('target.ids')),
|
||||
eb('asset.livePhotoVideoId', '=', eb.fn.any('target.ids')),
|
||||
]),
|
||||
)
|
||||
.execute()
|
||||
.then((rows) => {
|
||||
const allowedIds = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (row.assetId && assetIds.has(row.assetId)) {
|
||||
allowedIds.add(row.assetId);
|
||||
if (assetIds.has(row.id)) {
|
||||
allowedIds.add(row.id);
|
||||
}
|
||||
if (row.assetLivePhotoVideoId && assetIds.has(row.assetLivePhotoVideoId)) {
|
||||
allowedIds.add(row.assetLivePhotoVideoId);
|
||||
}
|
||||
if (row.albumAssetId && assetIds.has(row.albumAssetId)) {
|
||||
allowedIds.add(row.albumAssetId);
|
||||
}
|
||||
if (row.albumAssetLivePhotoVideoId && assetIds.has(row.albumAssetLivePhotoVideoId)) {
|
||||
allowedIds.add(row.albumAssetLivePhotoVideoId);
|
||||
if (row.livePhotoVideoId && assetIds.has(row.livePhotoVideoId)) {
|
||||
allowedIds.add(row.livePhotoVideoId);
|
||||
}
|
||||
}
|
||||
return allowedIds;
|
||||
|
||||
Reference in New Issue
Block a user