diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts
index 69d4345f44..b7d3e7d4ab 100644
--- a/server/src/entities/asset.entity.ts
+++ b/server/src/entities/asset.entity.ts
@@ -1,6 +1,6 @@
-import { DeduplicateJoinsPlugin, ExpressionBuilder, Kysely, Selectable, SelectQueryBuilder, sql } from 'kysely';
+import { DeduplicateJoinsPlugin, ExpressionBuilder, Kysely, SelectQueryBuilder, sql } from 'kysely';
 import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
-import { Assets, DB } from 'src/db';
+import { DB } from 'src/db';
 import { AlbumEntity } from 'src/entities/album.entity';
 import { AssetFaceEntity } from 'src/entities/asset-face.entity';
 import { AssetFileEntity } from 'src/entities/asset-files.entity';
@@ -181,15 +181,13 @@ export class AssetEntity {
 }
 
 export function withExif<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
-  return qb
-    .leftJoin('exif', 'assets.id', 'exif.assetId')
-    .select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo'));
+  return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo'));
 }
 
 export function withExifInner<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
   return qb
     .innerJoin('exif', 'assets.id', 'exif.assetId')
-    .select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo'));
+    .select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo'));
 }
 
 export function withSmartSearch<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
@@ -268,48 +266,6 @@ export function withLibrary(eb: ExpressionBuilder<DB, 'assets'>) {
   );
 }
 
-export function withStackedAssets<O>(qb: SelectQueryBuilder<DB, 'assets' | 'asset_stack', O>) {
-  return qb
-    .innerJoinLateral(
-      (eb: ExpressionBuilder<DB, 'assets' | 'asset_stack'>) =>
-        eb
-          .selectFrom('assets as stacked')
-          .select((eb) => eb.fn<Selectable<Assets>[]>('array_agg', [eb.table('stacked')]).as('assets'))
-          .whereRef('asset_stack.id', '=', 'stacked.stackId')
-          .whereRef('asset_stack.primaryAssetId', '!=', 'stacked.id')
-          .as('s'),
-      (join) =>
-        join.on((eb) =>
-          eb.or([eb('asset_stack.primaryAssetId', '=', eb.ref('assets.id')), eb('assets.stackId', 'is', null)]),
-        ),
-    )
-    .select('s.assets');
-}
-
-export function withStack<O>(
-  qb: SelectQueryBuilder<DB, 'assets', O>,
-  { assets, count }: { assets: boolean; count: boolean },
-) {
-  return qb
-    .leftJoinLateral(
-      (eb) =>
-        eb
-          .selectFrom('asset_stack')
-          .selectAll('asset_stack')
-          .whereRef('assets.stackId', '=', 'asset_stack.id')
-          .$if(assets, withStackedAssets)
-          .$if(count, (qb) =>
-            // There is no `selectNoFrom` method for expression builders
-            qb.select(
-              sql`(select count(*) as "assetCount" where "asset_stack"."id" = "assets"."stackId")`.as('assetCount'),
-            ),
-          )
-          .as('stacked_assets'),
-      (join) => join.onTrue(),
-    )
-    .select((eb) => eb.fn('to_jsonb', [eb.table('stacked_assets')]).as('stack'));
-}
-
 export function withAlbums<O>(qb: SelectQueryBuilder<DB, 'assets', O>, { albumId }: { albumId?: string }) {
   return qb
     .select((eb) =>
@@ -352,6 +308,18 @@ export function truncatedDate<O>(size: TimeBucketSize) {
   return sql<O>`date_trunc(${size}, "localDateTime" at time zone 'UTC') at time zone 'UTC'`;
 }
 
+export function withTagId<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagId: string) {
+  return qb.where((eb) =>
+    eb.exists(
+      eb
+        .selectFrom('tags_closure')
+        .innerJoin('tag_asset', 'tag_asset.tagsId', 'tags_closure.id_descendant')
+        .whereRef('tag_asset.assetsId', '=', 'assets.id')
+        .where('tags_closure.id_ancestor', '=', tagId),
+    ),
+  );
+}
+
 const joinDeduplicationPlugin = new DeduplicateJoinsPlugin();
 
 /** TODO: This should only be used for search-related queries, not as a general purpose query builder */
diff --git a/server/src/interfaces/memory.interface.ts b/server/src/interfaces/memory.interface.ts
index b1dbcbef85..327822adc1 100644
--- a/server/src/interfaces/memory.interface.ts
+++ b/server/src/interfaces/memory.interface.ts
@@ -7,7 +7,7 @@ export const IMemoryRepository = 'IMemoryRepository';
 
 export interface IMemoryRepository extends IBulkAsset {
   search(ownerId: string): Promise<MemoryEntity[]>;
-  get(id: string): Promise<MemoryEntity | null>;
+  get(id: string): Promise<MemoryEntity | undefined>;
   create(
     memory: Omit<Insertable<Memories>, 'data'> & { data: OnThisDayData },
     assetIds: Set<string>,
diff --git a/server/src/interfaces/person.interface.ts b/server/src/interfaces/person.interface.ts
index d1404d829a..4719f047ec 100644
--- a/server/src/interfaces/person.interface.ts
+++ b/server/src/interfaces/person.interface.ts
@@ -1,4 +1,4 @@
-import { Insertable, Updateable } from 'kysely';
+import { Insertable, Selectable, Updateable } from 'kysely';
 import { AssetFaces, FaceSearch, Person } from 'src/db';
 import { AssetFaceEntity } from 'src/entities/asset-face.entity';
 import { PersonEntity } from 'src/entities/person.entity';
@@ -49,7 +49,7 @@ export interface DeleteFacesOptions {
 
 export type UnassignFacesOptions = DeleteFacesOptions;
 
-export type SelectFaceOptions = Partial<{ [K in keyof AssetFaceEntity]: boolean }>;
+export type SelectFaceOptions = (keyof Selectable<AssetFaces>)[];
 
 export interface IPersonRepository {
   getAll(options?: Partial<PersonEntity>): AsyncIterableIterator<PersonEntity>;
@@ -74,10 +74,10 @@ export interface IPersonRepository {
     id: string,
     relations?: FindOptionsRelations<AssetFaceEntity>,
     select?: SelectFaceOptions,
-  ): Promise<AssetFaceEntity | null>;
+  ): Promise<AssetFaceEntity | undefined>;
   getFaces(assetId: string): Promise<AssetFaceEntity[]>;
   getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]>;
-  getRandomFace(personId: string): Promise<AssetFaceEntity | null>;
+  getRandomFace(personId: string): Promise<AssetFaceEntity | undefined>;
   getStatistics(personId: string): Promise<PersonStatistics>;
   reassignFace(assetFaceId: string, newPersonId: string): Promise<number>;
   getNumberOfPeople(userId: string): Promise<PeopleStatistics>;
diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql
index 89c9e3b4a9..8f17146633 100644
--- a/server/src/queries/album.repository.sql
+++ b/server/src/queries/album.repository.sql
@@ -82,11 +82,31 @@ select
         where
           "shared_links"."albumId" = "albums"."id"
       ) as agg
-  ) as "sharedLinks"
+  ) as "sharedLinks",
+  (
+    select
+      json_agg("asset") as "assets"
+    from
+      (
+        select
+          "assets".*,
+          to_json("exif") as "exifInfo"
+        from
+          "assets"
+          inner join "exif" on "assets"."id" = "exif"."assetId"
+          inner join "albums_assets_assets" on "albums_assets_assets"."assetsId" = "assets"."id"
+        where
+          "albums_assets_assets"."albumsId" = "albums"."id"
+          and "assets"."deletedAt" is null
+          and "assets"."isArchived" = $1
+        order by
+          "assets"."fileCreatedAt" desc
+      ) as "asset"
+  ) as "assets"
 from
   "albums"
 where
-  "albums"."id" = $1
+  "albums"."id" = $2
   and "albums"."deletedAt" is null
 
 -- AlbumRepository.getByAssetId
diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql
index d50069f0a9..d540b90f18 100644
--- a/server/src/queries/asset.repository.sql
+++ b/server/src/queries/asset.repository.sql
@@ -23,7 +23,7 @@ with
       )
     select
       "a".*,
-      to_jsonb("exif") as "exifInfo"
+      to_json("exif") as "exifInfo"
     from
       "today"
       inner join lateral (
@@ -56,7 +56,7 @@ select
   (
     (now() at time zone 'UTC')::date - ("localDateTime" at time zone 'UTC')::date
   ) / 365 as "yearsAgo",
-  jsonb_agg("res") as "assets"
+  json_agg("res") as "assets"
 from
   "res"
 group by
@@ -109,34 +109,28 @@ select
           "assets"."id" = "tag_asset"."assetsId"
       ) as agg
   ) as "tags",
-  to_jsonb("exif") as "exifInfo",
-  to_jsonb("stacked_assets") as "stack"
+  to_json("exif") as "exifInfo",
+  to_json("stacked_assets") as "stack"
 from
   "assets"
   left join "exif" on "assets"."id" = "exif"."assetId"
+  left join "asset_stack" on "asset_stack"."id" = "assets"."stackId"
   left join lateral (
     select
       "asset_stack".*,
-      "s"."assets"
+      array_agg("stacked") as "assets"
     from
-      "asset_stack"
-      inner join lateral (
-        select
-          array_agg("stacked") as "assets"
-        from
-          "assets" as "stacked"
-        where
-          "asset_stack"."id" = "stacked"."stackId"
-          and "asset_stack"."primaryAssetId" != "stacked"."id"
-      ) as "s" on (
-        "asset_stack"."primaryAssetId" = "assets"."id"
-        or "assets"."stackId" is null
-      )
+      "assets" as "stacked"
     where
-      "assets"."stackId" = "asset_stack"."id"
-  ) as "stacked_assets" on true
+      "stacked"."stackId" = "asset_stack"."id"
+      and "stacked"."id" != "asset_stack"."primaryAssetId"
+      and "stacked"."deletedAt" is null
+      and "stacked"."isArchived" = $1
+    group by
+      "asset_stack"."id"
+  ) as "stacked_assets" on "asset_stack"."id" is not null
 where
-  "assets"."id" = any ($1::uuid [])
+  "assets"."id" = any ($2::uuid [])
 
 -- AssetRepository.deleteAll
 delete from "assets"
@@ -278,14 +272,33 @@ order by
 -- AssetRepository.getTimeBucket
 select
   "assets".*,
-  to_jsonb("exif") as "exifInfo"
+  to_json("exif") as "exifInfo",
+  to_json("stacked_assets") as "stack"
 from
   "assets"
   left join "exif" on "assets"."id" = "exif"."assetId"
+  left join "asset_stack" on "asset_stack"."id" = "assets"."stackId"
+  left join lateral (
+    select
+      "asset_stack".*,
+      count("stacked") as "assetCount"
+    from
+      "assets" as "stacked"
+    where
+      "stacked"."stackId" = "asset_stack"."id"
+      and "stacked"."deletedAt" is null
+      and "stacked"."isArchived" = $1
+    group by
+      "asset_stack"."id"
+  ) as "stacked_assets" on "asset_stack"."id" is not null
 where
-  "assets"."deletedAt" is null
-  and "assets"."isVisible" = $1
-  and date_trunc($2, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $3
+  (
+    "asset_stack"."primaryAssetId" = "assets"."id"
+    or "assets"."stackId" is null
+  )
+  and "assets"."deletedAt" is null
+  and "assets"."isVisible" = $2
+  and date_trunc($3, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $4
 order by
   "assets"."localDateTime" desc
 
@@ -368,25 +381,23 @@ limit
 -- AssetRepository.getAllForUserFullSync
 select
   "assets".*,
-  to_jsonb("exif") as "exifInfo",
-  to_jsonb("stacked_assets") as "stack"
+  to_json("exif") as "exifInfo",
+  to_json("stacked_assets") as "stack"
 from
   "assets"
   left join "exif" on "assets"."id" = "exif"."assetId"
+  left join "asset_stack" on "asset_stack"."id" = "assets"."stackId"
   left join lateral (
     select
       "asset_stack".*,
-      (
-        select
-          count(*) as "assetCount"
-        where
-          "asset_stack"."id" = "assets"."stackId"
-      ) as "assetCount"
+      count("stacked") as "assetCount"
     from
-      "asset_stack"
+      "assets" as "stacked"
     where
-      "assets"."stackId" = "asset_stack"."id"
-  ) as "stacked_assets" on true
+      "stacked"."stackId" = "asset_stack"."id"
+    group by
+      "asset_stack"."id"
+  ) as "stacked_assets" on "asset_stack"."id" is not null
 where
   "assets"."ownerId" = $1::uuid
   and "isVisible" = $2
@@ -400,25 +411,23 @@ limit
 -- AssetRepository.getChangedDeltaSync
 select
   "assets".*,
-  to_jsonb("exif") as "exifInfo",
-  to_jsonb("stacked_assets") as "stack"
+  to_json("exif") as "exifInfo",
+  to_json("stacked_assets") as "stack"
 from
   "assets"
   left join "exif" on "assets"."id" = "exif"."assetId"
+  left join "asset_stack" on "asset_stack"."id" = "assets"."stackId"
   left join lateral (
     select
       "asset_stack".*,
-      (
-        select
-          count(*) as "assetCount"
-        where
-          "asset_stack"."id" = "assets"."stackId"
-      ) as "assetCount"
+      count("stacked") as "assetCount"
     from
-      "asset_stack"
+      "assets" as "stacked"
     where
-      "assets"."stackId" = "asset_stack"."id"
-  ) as "stacked_assets" on true
+      "stacked"."stackId" = "asset_stack"."id"
+    group by
+      "asset_stack"."id"
+  ) as "stacked_assets" on "asset_stack"."id" is not null
 where
   "assets"."ownerId" = any ($1::uuid [])
   and "isVisible" = $2
diff --git a/server/src/queries/library.repository.sql b/server/src/queries/library.repository.sql
index 347990f04c..b0b20fd8a2 100644
--- a/server/src/queries/library.repository.sql
+++ b/server/src/queries/library.repository.sql
@@ -112,7 +112,7 @@ order by
 
 -- LibraryRepository.getStatistics
 select
-  count("assets"."id") filter (
+  count(*) filter (
     where
       (
         "assets"."type" = $1
@@ -130,8 +130,17 @@ select
 from
   "libraries"
   inner join "assets" on "assets"."libraryId" = "libraries"."id"
-  inner join "exif" on "exif"."assetId" = "assets"."id"
+  left join "exif" on "exif"."assetId" = "assets"."id"
 where
   "libraries"."id" = $6
 group by
   "libraries"."id"
+select
+  0::int as "photos",
+  0::int as "videos",
+  0::int as "usage",
+  0::int as "total"
+from
+  "libraries"
+where
+  "libraries"."id" = $1
diff --git a/server/src/queries/view.repository.sql b/server/src/queries/view.repository.sql
index 948f60fd4d..b368684cae 100644
--- a/server/src/queries/view.repository.sql
+++ b/server/src/queries/view.repository.sql
@@ -14,7 +14,7 @@ where
 -- ViewRepository.getAssetsByOriginalPath
 select
   "assets".*,
-  to_jsonb("exif") as "exifInfo"
+  to_json("exif") as "exifInfo"
 from
   "assets"
   left join "exif" on "assets"."id" = "exif"."assetId"
diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts
index bae91349f5..e32a53e82d 100644
--- a/server/src/repositories/album.repository.ts
+++ b/server/src/repositories/album.repository.ts
@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectRepository } from '@nestjs/typeorm';
 import { ExpressionBuilder, Insertable, Kysely, sql, Updateable } from 'kysely';
 import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
 import { InjectKysely } from 'nestjs-kysely';
@@ -8,7 +7,6 @@ import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/
 import { AlbumUserCreateDto } from 'src/dtos/album.dto';
 import { AlbumEntity } from 'src/entities/album.entity';
 import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
-import { Repository } from 'typeorm';
 
 const userColumns = [
   'id',
@@ -64,6 +62,8 @@ const withAssets = (eb: ExpressionBuilder<DB, 'albums'>) => {
         .select((eb) => eb.fn.toJson('exif').as('exifInfo'))
         .innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
         .whereRef('albums_assets_assets.albumsId', '=', 'albums.id')
+        .where('assets.deletedAt', 'is', null)
+        .where('assets.isArchived', '=', false)
         .orderBy('assets.fileCreatedAt', 'desc')
         .as('asset'),
     )
@@ -73,12 +73,9 @@ const withAssets = (eb: ExpressionBuilder<DB, 'albums'>) => {
 
 @Injectable()
 export class AlbumRepository implements IAlbumRepository {
-  constructor(
-    @InjectRepository(AlbumEntity) private repository: Repository<AlbumEntity>,
-    @InjectKysely() private db: Kysely<DB>,
-  ) {}
+  constructor(@InjectKysely() private db: Kysely<DB>) {}
 
-  @GenerateSql({ params: [DummyValue.UUID, {}] })
+  @GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] })
   async getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | undefined> {
     return this.db
       .selectFrom('albums')
diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts
index ed1ef73a08..a53b1dbd92 100644
--- a/server/src/repositories/asset.repository.ts
+++ b/server/src/repositories/asset.repository.ts
@@ -19,7 +19,7 @@ import {
   withLibrary,
   withOwner,
   withSmartSearch,
-  withStack,
+  withTagId,
   withTags,
 } from 'src/entities/asset.entity';
 import { AssetFileType, AssetStatus, AssetType } from 'src/enum';
@@ -122,13 +122,13 @@ export class AssetRepository implements IAssetRepository {
                   ),
                 )
                 .where('assets.deletedAt', 'is', null)
-                .limit(10)
+                .limit(20)
                 .as('a'),
             (join) => join.onTrue(),
           )
           .innerJoin('exif', 'a.id', 'exif.assetId')
           .selectAll('a')
-          .select((eb) => eb.fn('to_jsonb', [eb.table('exif')]).as('exifInfo')),
+          .select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')),
       )
       .selectFrom('res')
       .select(
@@ -136,7 +136,7 @@ export class AssetRepository implements IAssetRepository {
           'yearsAgo',
         ),
       )
-      .select((eb) => eb.fn('jsonb_agg', [eb.table('res')]).as('assets'))
+      .select((eb) => eb.fn.jsonAgg(eb.table('res')).as('assets'))
       .groupBy(sql`("localDateTime" at time zone 'UTC')::date`)
       .orderBy(sql`("localDateTime" at time zone 'UTC')::date`, 'desc')
       .limit(10)
@@ -159,7 +159,29 @@ export class AssetRepository implements IAssetRepository {
       .$if(!!library, (qb) => qb.select(withLibrary))
       .$if(!!owner, (qb) => qb.select(withOwner))
       .$if(!!smartSearch, withSmartSearch)
-      .$if(!!stack, (qb) => withStack(qb, { assets: !!stack!.assets, count: false }))
+      .$if(!!stack, (qb) =>
+        qb
+          .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
+          .$if(!stack!.assets, (qb) => qb.select((eb) => eb.fn.toJson(eb.table('asset_stack')).as('stack')))
+          .$if(!!stack!.assets, (qb) =>
+            qb
+              .leftJoinLateral(
+                (eb) =>
+                  eb
+                    .selectFrom('assets as stacked')
+                    .selectAll('asset_stack')
+                    .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets'))
+                    .whereRef('stacked.stackId', '=', 'asset_stack.id')
+                    .whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId')
+                    .where('stacked.deletedAt', 'is', null)
+                    .where('stacked.isArchived', '=', false)
+                    .groupBy('asset_stack.id')
+                    .as('stacked_assets'),
+                (join) => join.on('asset_stack.id', 'is not', null),
+              )
+              .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')),
+          ),
+      )
       .$if(!!tags, (qb) => qb.select(withTags))
       .execute();
 
@@ -175,7 +197,22 @@ export class AssetRepository implements IAssetRepository {
       .select(withFacesAndPeople)
       .select(withTags)
       .$call(withExif)
-      .$call((qb) => withStack(qb, { assets: true, count: false }))
+      .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
+      .leftJoinLateral(
+        (eb) =>
+          eb
+            .selectFrom('assets as stacked')
+            .selectAll('asset_stack')
+            .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets'))
+            .whereRef('stacked.stackId', '=', 'asset_stack.id')
+            .whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId')
+            .where('stacked.deletedAt', 'is', null)
+            .where('stacked.isArchived', '=', false)
+            .groupBy('asset_stack.id')
+            .as('stacked_assets'),
+        (join) => join.on('asset_stack.id', 'is not', null),
+      )
+      .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack'))
       .where('assets.id', '=', anyUuid(ids))
       .execute() as any as Promise<AssetEntity[]>;
   }
@@ -287,19 +324,25 @@ export class AssetRepository implements IAssetRepository {
       .$if(!!stack, (qb) =>
         qb
           .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
-          .leftJoinLateral(
-            (eb) =>
-              eb
-                .selectFrom('assets as stacked')
-                .selectAll('asset_stack')
-                .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets'))
-                .where('stacked.deletedAt', 'is', null)
-                .whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId')
-                .whereRef('stacked.stackId', '=', 'asset_stack.id')
-                .as('stacked_assets'),
-            (join) => join.on('asset_stack.id', 'is not', null),
-          )
-          .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')),
+          .$if(!stack!.assets, (qb) => qb.select((eb) => eb.fn.toJson(eb.table('asset_stack')).as('stack')))
+          .$if(!!stack!.assets, (qb) =>
+            qb
+              .leftJoinLateral(
+                (eb) =>
+                  eb
+                    .selectFrom('assets as stacked')
+                    .selectAll('asset_stack')
+                    .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets'))
+                    .whereRef('stacked.stackId', '=', 'asset_stack.id')
+                    .whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId')
+                    .where('stacked.deletedAt', 'is', null)
+                    .where('stacked.isArchived', '=', false)
+                    .groupBy('asset_stack.id')
+                    .as('stacked_assets'),
+                (join) => join.on('asset_stack.id', 'is not', null),
+              )
+              .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')),
+          ),
       )
       .$if(!!files, (qb) => qb.select(withFiles))
       .$if(!!tags, (qb) => qb.select(withTags))
@@ -567,7 +610,8 @@ export class AssetRepository implements IAssetRepository {
             .$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
             .$if(!!options.isDuplicate, (qb) =>
               qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
-            ),
+            )
+            .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
         )
         .selectFrom('assets')
         .select('timeBucket')
@@ -583,7 +627,7 @@ export class AssetRepository implements IAssetRepository {
     );
   }
 
-  @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH }] })
+  @GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH, withStacked: true }] })
   async getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]> {
     return hasPeople(this.db, options.personId ? [options.personId] : undefined)
       .selectAll('assets')
@@ -592,12 +636,33 @@ export class AssetRepository implements IAssetRepository {
       .$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
       .$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
       .$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
-      .$if(!!options.withStacked, (qb) => withStack(qb, { assets: true, count: false })) // TODO: optimize this; it's a huge performance hit
+      .$if(!!options.withStacked, (qb) =>
+        qb
+          .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
+          .where((eb) =>
+            eb.or([eb('asset_stack.primaryAssetId', '=', eb.ref('assets.id')), eb('assets.stackId', 'is', null)]),
+          )
+          .leftJoinLateral(
+            (eb) =>
+              eb
+                .selectFrom('assets as stacked')
+                .selectAll('asset_stack')
+                .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount'))
+                .whereRef('stacked.stackId', '=', 'asset_stack.id')
+                .where('stacked.deletedAt', 'is', null)
+                .where('stacked.isArchived', '=', false)
+                .groupBy('asset_stack.id')
+                .as('stacked_assets'),
+            (join) => join.on('asset_stack.id', 'is not', null),
+          )
+          .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack')),
+      )
       .$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
       .$if(options.isDuplicate !== undefined, (qb) =>
         qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
       )
       .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
+      .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
       .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
       .where('assets.isVisible', '=', true)
       .where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, ''))
@@ -689,7 +754,19 @@ export class AssetRepository implements IAssetRepository {
       .selectFrom('assets')
       .selectAll('assets')
       .$call(withExif)
-      .$call((qb) => withStack(qb, { assets: false, count: true }))
+      .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
+      .leftJoinLateral(
+        (eb) =>
+          eb
+            .selectFrom('assets as stacked')
+            .selectAll('asset_stack')
+            .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount'))
+            .whereRef('stacked.stackId', '=', 'asset_stack.id')
+            .groupBy('asset_stack.id')
+            .as('stacked_assets'),
+        (join) => join.on('asset_stack.id', 'is not', null),
+      )
+      .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack'))
       .where('assets.ownerId', '=', asUuid(ownerId))
       .where('isVisible', '=', true)
       .where('updatedAt', '<=', updatedUntil)
@@ -705,7 +782,19 @@ export class AssetRepository implements IAssetRepository {
       .selectFrom('assets')
       .selectAll('assets')
       .$call(withExif)
-      .$call((qb) => withStack(qb, { assets: false, count: true }))
+      .leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
+      .leftJoinLateral(
+        (eb) =>
+          eb
+            .selectFrom('assets as stacked')
+            .selectAll('asset_stack')
+            .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount'))
+            .whereRef('stacked.stackId', '=', 'asset_stack.id')
+            .groupBy('asset_stack.id')
+            .as('stacked_assets'),
+        (join) => join.on('asset_stack.id', 'is not', null),
+      )
+      .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack'))
       .where('assets.ownerId', '=', anyUuid(options.userIds))
       .where('isVisible', '=', true)
       .where('updatedAt', '>', options.updatedAfter)
diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts
index ca279d7dea..0e1ec94c32 100644
--- a/server/src/repositories/library.repository.ts
+++ b/server/src/repositories/library.repository.ts
@@ -1,5 +1,5 @@
 import { Injectable } from '@nestjs/common';
-import { ExpressionBuilder, Insertable, Kysely, Updateable } from 'kysely';
+import { ExpressionBuilder, Insertable, Kysely, sql, Updateable } from 'kysely';
 import { jsonObjectFrom } from 'kysely/helpers/postgres';
 import { InjectKysely } from 'nestjs-kysely';
 import { DB, Libraries } from 'src/db';
@@ -100,10 +100,10 @@ export class LibraryRepository implements ILibraryRepository {
     const stats = await this.db
       .selectFrom('libraries')
       .innerJoin('assets', 'assets.libraryId', 'libraries.id')
-      .innerJoin('exif', 'exif.assetId', 'assets.id')
+      .leftJoin('exif', 'exif.assetId', 'assets.id')
       .select((eb) =>
         eb.fn
-          .count('assets.id')
+          .countAll()
           .filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)]))
           .as('photos'),
       )
@@ -118,8 +118,17 @@ export class LibraryRepository implements ILibraryRepository {
       .where('libraries.id', '=', id)
       .executeTakeFirst();
 
+    // possibly a new library with 0 assets
     if (!stats) {
-      return;
+      const zero = sql<number>`0::int`;
+      return this.db
+        .selectFrom('libraries')
+        .select(zero.as('photos'))
+        .select(zero.as('videos'))
+        .select(zero.as('usage'))
+        .select(zero.as('total'))
+        .where('libraries.id', '=', id)
+        .executeTakeFirst();
     }
 
     return {
diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts
index 7e59b92e68..3a32e750b7 100644
--- a/server/src/repositories/memory.repository.ts
+++ b/server/src/repositories/memory.repository.ts
@@ -22,8 +22,8 @@ export class MemoryRepository implements IMemoryRepository {
   }
 
   @GenerateSql({ params: [DummyValue.UUID] })
-  get(id: string): Promise<MemoryEntity | null> {
-    return this.getByIdBuilder(id).executeTakeFirst() as unknown as Promise<MemoryEntity | null>;
+  get(id: string): Promise<MemoryEntity | undefined> {
+    return this.getByIdBuilder(id).executeTakeFirst() as unknown as Promise<MemoryEntity | undefined>;
   }
 
   async create(memory: Insertable<Memories>, assetIds: Set<string>): Promise<MemoryEntity> {
@@ -71,6 +71,10 @@ export class MemoryRepository implements IMemoryRepository {
 
   @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
   async addAssetIds(id: string, assetIds: string[]): Promise<void> {
+    if (assetIds.length === 0) {
+      return;
+    }
+
     await this.db
       .insertInto('memories_assets_assets')
       .values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId })))
@@ -80,6 +84,10 @@ export class MemoryRepository implements IMemoryRepository {
   @Chunked({ paramIndex: 1 })
   @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
   async removeAssetIds(id: string, assetIds: string[]): Promise<void> {
+    if (assetIds.length === 0) {
+      return;
+    }
+
     await this.db
       .deleteFrom('memories_assets_assets')
       .where('memoriesId', '=', id)
diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts
index c810b0def2..fdcecd9d0a 100644
--- a/server/src/repositories/person.repository.ts
+++ b/server/src/repositories/person.repository.ts
@@ -1,7 +1,6 @@
 import { Injectable } from '@nestjs/common';
-import { ExpressionBuilder, Insertable, Kysely, SelectExpression, sql } from 'kysely';
+import { ExpressionBuilder, Insertable, Kysely, sql } from 'kysely';
 import { jsonObjectFrom } from 'kysely/helpers/postgres';
-import _ from 'lodash';
 import { InjectKysely } from 'nestjs-kysely';
 import { AssetFaces, DB, FaceSearch, Person } from 'src/db';
 import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
@@ -212,22 +211,16 @@ export class PersonRepository implements IPersonRepository {
     id: string,
     relations?: FindOptionsRelations<AssetFaceEntity>,
     select?: SelectFaceOptions,
-  ): Promise<AssetFaceEntity | null> {
-    return (this.db
+  ): Promise<AssetFaceEntity | undefined> {
+    return this.db
       .selectFrom('asset_faces')
-      .$if(!!select, (qb) =>
-        qb.select(
-          Object.keys(
-            _.omitBy({ ...select!, faceSearch: undefined, asset: undefined }, _.isUndefined),
-          ) as SelectExpression<DB, 'asset_faces'>[],
-        ),
-      )
+      .$if(!!select, (qb) => qb.select(select!))
       .$if(!select, (qb) => qb.selectAll('asset_faces'))
       .select(withPerson)
       .select(withAsset)
       .$if(!!relations?.faceSearch, (qb) => qb.select(withFaceSearch))
       .where('asset_faces.id', '=', id)
-      .executeTakeFirst() ?? null) as Promise<AssetFaceEntity | null>;
+      .executeTakeFirst() as Promise<AssetFaceEntity | undefined>;
   }
 
   @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
@@ -335,6 +328,10 @@ export class PersonRepository implements IPersonRepository {
   }
 
   async createAll(people: Insertable<Person>[]): Promise<string[]> {
+    if (people.length === 0) {
+      return [];
+    }
+
     const results = await this.db.insertInto('person').values(people).returningAll().execute();
     return results.map(({ id }) => id);
   }
@@ -387,8 +384,12 @@ export class PersonRepository implements IPersonRepository {
   @GenerateSql({ params: [[{ assetId: DummyValue.UUID, personId: DummyValue.UUID }]] })
   @ChunkedArray()
   getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]> {
-    const { assetIds, personIds }: { assetIds: string[]; personIds: string[] } = { assetIds: [], personIds: [] };
+    if (ids.length === 0) {
+      return Promise.resolve([]);
+    }
 
+    const assetIds: string[] = [];
+    const personIds: string[] = [];
     for (const { assetId, personId } of ids) {
       assetIds.push(assetId);
       personIds.push(personId);
@@ -405,12 +406,12 @@ export class PersonRepository implements IPersonRepository {
   }
 
   @GenerateSql({ params: [DummyValue.UUID] })
-  getRandomFace(personId: string): Promise<AssetFaceEntity | null> {
-    return (this.db
+  getRandomFace(personId: string): Promise<AssetFaceEntity | undefined> {
+    return this.db
       .selectFrom('asset_faces')
       .selectAll('asset_faces')
       .where('asset_faces.personId', '=', personId)
-      .executeTakeFirst() ?? null) as Promise<AssetFaceEntity | null>;
+      .executeTakeFirst() as Promise<AssetFaceEntity | undefined>;
   }
 
   @GenerateSql()
diff --git a/server/src/repositories/trash.repository.ts b/server/src/repositories/trash.repository.ts
index c1db31a3db..06e75a8d2e 100644
--- a/server/src/repositories/trash.repository.ts
+++ b/server/src/repositories/trash.repository.ts
@@ -38,6 +38,10 @@ export class TrashRepository implements ITrashRepository {
 
   @GenerateSql({ params: [[DummyValue.UUID]] })
   async restoreAll(ids: string[]): Promise<number> {
+    if (ids.length === 0) {
+      return 0;
+    }
+
     const { numUpdatedRows } = await this.db
       .updateTable('assets')
       .where('status', '=', AssetStatus.TRASHED)
diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts
index b18eb7dfd8..3f454f178f 100644
--- a/server/src/services/person.service.spec.ts
+++ b/server/src/services/person.service.spec.ts
@@ -368,7 +368,6 @@ describe(PersonService.name, () => {
       personMock.getFaceById.mockResolvedValue(faceStub.face1);
       personMock.reassignFace.mockResolvedValue(1);
       personMock.getById.mockResolvedValue(personStub.noName);
-      personMock.getRandomFace.mockResolvedValue(null);
       await expect(
         sut.reassignFacesById(authStub.admin, personStub.noName.id, {
           id: faceStub.face1.id,
@@ -391,7 +390,6 @@ describe(PersonService.name, () => {
       personMock.getFaceById.mockResolvedValue(faceStub.face1);
       personMock.reassignFace.mockResolvedValue(1);
       personMock.getById.mockResolvedValue(personStub.noName);
-      personMock.getRandomFace.mockResolvedValue(null);
       await expect(
         sut.reassignFacesById(authStub.admin, personStub.noName.id, {
           id: faceStub.face1.id,
@@ -771,8 +769,6 @@ describe(PersonService.name, () => {
 
   describe('handleRecognizeFaces', () => {
     it('should fail if face does not exist', async () => {
-      personMock.getFaceByIdWithAssets.mockResolvedValue(null);
-
       expect(await sut.handleRecognizeFaces({ id: faceStub.face1.id })).toBe(JobStatus.FAILED);
 
       expect(personMock.reassignFaces).not.toHaveBeenCalled();
diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts
index 45732c4e7c..b1a7ec75a2 100644
--- a/server/src/services/person.service.ts
+++ b/server/src/services/person.service.ts
@@ -145,7 +145,7 @@ export class PersonService extends BaseService {
     for (const personId of changeFeaturePhoto) {
       const assetFace = await this.personRepository.getRandomFace(personId);
 
-      if (assetFace !== null) {
+      if (assetFace) {
         await this.personRepository.update({ id: personId, faceAssetId: assetFace.id });
         jobs.push({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: personId } });
       }
@@ -444,7 +444,7 @@ export class PersonService extends BaseService {
     const face = await this.personRepository.getFaceByIdWithAssets(
       id,
       { person: true, asset: true, faceSearch: true },
-      { id: true, personId: true, sourceType: true, faceSearch: true },
+      ['id', 'personId', 'sourceType'],
     );
     if (!face || !face.asset) {
       this.logger.warn(`Face ${id} not found`);
@@ -544,7 +544,7 @@ export class PersonService extends BaseService {
     }
 
     const face = await this.personRepository.getFaceByIdWithAssets(person.faceAssetId);
-    if (face === null) {
+    if (!face) {
       this.logger.error(`Could not generate person thumbnail: face ${person.faceAssetId} not found`);
       return JobStatus.FAILED;
     }