Performance: Stream reorder in ChannelTableStreams now completes in a single PATCH request instead of three.

This commit is contained in:
SergeantPanda 2026-04-19 09:56:26 -05:00
parent 452a493df3
commit bf60bbab54
3 changed files with 35 additions and 7 deletions

View file

@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Optimized `ChannelTableStreams` (the expanded stream list inside each channel row) to reduce mount cost: moved pure helper functions and static values (`getCoreRowModel`, `defaultColumn`, stat categorization/formatting) outside the component so they're created once; stabilized the TanStack column definitions by removing `data` and `expandedAdvancedStats` from the `useMemo` dependency array (cell renderers receive the row at render time); switched advanced-stats toggle tracking from `useState` to a `useRef` + per-cell local state so toggling one stream's stats doesn't recreate the entire column array and table instance; memoized `dataIds`, `removeStream`, `handleDragEnd`, and `handleWatchStream` with `useMemo`/`useCallback`; extracted `StreamInfoCell` as a `React.memo` component with its own memoized stat categorization.
- Fixed `getChannelStreams` store selector to return a stable empty-array reference instead of creating a new `[]` on every call for channels without streams, preventing unnecessary re-renders via the `shallow` comparator.
- Memoized individual rows in `CustomTableBody` so that expanding/collapsing a channel only re-renders the 1-2 affected rows instead of all rows on the page. Callback functions (`renderBodyCell`, `expandedRowRenderer`) are stored in refs so memoized rows always use the latest version without the function references themselves defeating the memo comparator.
- Stream reorder in `ChannelTableStreams` now completes in a single PATCH request instead of three. Previously dragging a stream to a new position triggered a PATCH, then `requeryStreams()` (re-fetching all streams), then `requeryChannels()` (re-fetching the entire paginated channel list with all embedded stream objects). The reorder now uses a dedicated `API.reorderChannelStreams()` path that issues only the PATCH, then updates the store in-place by reordering the existing stream objects without any network round-trips. On failure, `requeryChannels()` is called to restore correct state.
- Fixed N+1 `UPDATE` queries in the stream-order write path. `ChannelSerializer.update()` and the bulk-edit view were calling `ChannelStream.save(update_fields=["order"])` once per stream whose position changed. Both now collect all modified `ChannelStream` objects and issue a single `ChannelStream.objects.bulk_update(…, ["order"])` call. Also removed an accidental `print(normalized_ids)` debug statement left in the serializer.
- Eliminated repeated DB queries in the `ts_proxy` hot path. `StreamManager`, `StreamGenerator`, and `ProxyServer` were each calling `Channel.objects.get(uuid=...)` on every retry, reconnect, failover, and buffering event solely to retrieve `channel.name` for log events. `StreamManager` and `StreamGenerator` now fetch the channel name once at construction via a lightweight `values_list` query and store it as `self.channel_name`. `ProxyServer` caches the name in a `_channel_names` dict keyed by channel ID at channel-start time and pops it at channel-stop time. (Fixes #1138)
### Changed

View file

@ -637,6 +637,35 @@ export default class API {
}
}
/**
* Lightweight stream reorder: PATCHes only the stream order for a channel
* without triggering requeryStreams or requeryChannels. The caller is
* responsible for optimistic UI updates.
*/
static async reorderChannelStreams(channelId, streamIds) {
try {
await request(`${host}/api/channels/channels/${channelId}/`, {
method: 'PATCH',
body: { id: channelId, streams: streamIds },
});
// Update the channelsTable store in-place with the new stream order
const store = useChannelsTableStore.getState();
const channel = store.channels.find((c) => c.id === channelId);
if (channel) {
// Reorder the existing stream objects to match streamIds
const streamMap = new Map(channel.streams.map((s) => [s.id, s]));
const reorderedStreams = streamIds
.map((id) => streamMap.get(id))
.filter(Boolean);
store.updateChannel({ ...channel, streams: reorderedStreams });
}
} catch (e) {
errorNotification('Failed to reorder streams', e);
// On failure, requery to restore correct state
await API.requeryChannels();
}
}
static async updateChannels(ids, values) {
const body = [];
for (const id of ids) {

View file

@ -643,13 +643,10 @@ const ChannelStreams = ({ channel }) => {
const newIndex = currentIds.indexOf(over.id);
const retval = arrayMove(prevData, oldIndex, newIndex);
const { streams: _, ...channelUpdate } = channel;
API.updateChannel({
...channelUpdate,
streams: retval.map((row) => row.id),
}).then(() => {
API.requeryChannels();
});
API.reorderChannelStreams(
channel.id,
retval.map((row) => row.id)
);
return retval;
});