Perf: Add lightweight ListGroupData fragment for groups list (#6478)

Create a new ListGroupData fragment that excludes expensive recursive
count fields (scene_count_all, sub_group_count_all, etc. with depth: -1).
These fields cause 10+ second queries on large databases when loading
the groups list page.

The full GroupData fragment is preserved for detail views where the
recursive counts are needed.
This commit is contained in:
CJ 2026-01-05 18:48:16 -06:00 committed by GitHub
parent c0260781a5
commit 9b709ef614
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 50 additions and 8 deletions

View file

@ -1,3 +1,4 @@
# Full fragment for detail views - includes recursive counts
fragment GroupData on Group {
id
name
@ -39,3 +40,44 @@ fragment GroupData on Group {
title
}
}
# Lightweight fragment for list views - excludes expensive recursive counts
# The _all fields (depth: -1) cause 10+ second queries on large databases
fragment ListGroupData on Group {
id
name
aliases
duration
date
rating100
director
studio {
...SlimStudioData
}
tags {
...SlimTagData
}
containing_groups {
group {
...SlimGroupData
}
description
}
synopsis
urls
front_image_path
back_image_path
scene_count
performer_count
sub_group_count
o_counter
scenes {
id
title
}
}

View file

@ -2,7 +2,7 @@ query FindGroups($filter: FindFilterType, $group_filter: GroupFilterType) {
findGroups(filter: $filter, group_filter: $group_filter) {
count
groups {
...GroupData
...ListGroupData
}
}
}

View file

@ -23,12 +23,12 @@ import { ContainingGroupsMultiSet } from "./ContainingGroupsMultiSet";
import { IRelatedGroupEntry } from "./GroupDetails/RelatedGroupTable";
interface IListOperationProps {
selected: GQL.GroupDataFragment[];
selected: GQL.ListGroupDataFragment[];
onClose: (applied: boolean) => void;
}
export function getAggregateContainingGroups(
state: Pick<GQL.GroupDataFragment, "containing_groups">[]
state: Pick<GQL.ListGroupDataFragment, "containing_groups">[]
) {
const sortedLists: IRelatedGroupEntry[][] = state.map((o) =>
o.containing_groups
@ -144,7 +144,7 @@ export const EditGroupsDialog: React.FC<IListOperationProps> = (
let updateDirector: string | undefined;
let first = true;
state.forEach((group: GQL.GroupDataFragment) => {
state.forEach((group: GQL.ListGroupDataFragment) => {
const groupTagIDs = (group.tags ?? []).map((p) => p.id).sort();
const groupContainingGroupIDs = (group.containing_groups ?? []).sort(
(a, b) => a.group.id.localeCompare(b.group.id)

View file

@ -37,7 +37,7 @@ const Description: React.FC<{
};
interface IProps {
group: GQL.GroupDataFragment;
group: GQL.ListGroupDataFragment;
cardWidth?: number;
sceneNumber?: number;
selecting?: boolean;

View file

@ -7,7 +7,7 @@ import {
} from "../Shared/GridCard/GridCard";
interface IGroupCardGrid {
groups: GQL.GroupDataFragment[];
groups: GQL.ListGroupDataFragment[];
selectedIds: Set<string>;
zoomIndex: number;
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;

View file

@ -199,7 +199,7 @@ export const GroupList: React.FC<IGroupList> = PatchComponent(
}
function renderEditDialog(
selectedGroups: GQL.GroupDataFragment[],
selectedGroups: GQL.ListGroupDataFragment[],
onClose: (applied: boolean) => void
) {
return <EditGroupsDialog selected={selectedGroups} onClose={onClose} />;

View file

@ -16,7 +16,7 @@ import { GroupTag } from "./GroupTag";
interface IProps {
group: Pick<
GQL.GroupDataFragment,
GQL.ListGroupDataFragment,
"id" | "name" | "containing_groups" | "sub_group_count"
>;
}