mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
performance:
- Performance: `xc_get_epg` now uses `select_related('epg_data__epg_source')` on all three channel fetch paths. Previously each request triggered 2 additional queries to resolve `channel.epg_data` and `channel.epg_data.epg_source`.
- Performance: `generate_m3u` now uses `prefetch_related` for streams when `?direct=true` is requested, eliminating N+1 stream queries (one per channel) on that code path.
- Performance: `EPGGridAPIView` (`apps/epg/api_views.py`) now uses `select_related('epg_data__epg_source')` on the `channels_with_custom_dummy` queryset, eliminating 2 extra queries per channel (for `epg_data` and `epg_source`) in the dummy EPG generation loop.
This commit is contained in:
parent
16984bc386
commit
54ab3e8981
3 changed files with 19 additions and 10 deletions
|
|
@ -73,6 +73,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Performance: `generate_m3u`, `generate_epg`, and `xc_get_live_streams` now use `select_related('channel_group', 'logo')` (or `select_related('logo')` for EPG) on every Channel queryset in `apps/output/views.py`. Previously each channel in the loop triggered a separate database query for its `logo` and `channel_group` foreign keys; with the JOIN-based prefetch this is reduced to a single query per request. On deployments with ~2 000 channels, `xc_get_live_streams` response time drops from ~2.5–4 s to ~250–450 ms. (Closes #1127) — Thanks [@xBOBxSAGETx](https://github.com/xBOBxSAGETx)
|
||||
- Performance: `generate_epg` now uses `select_related('epg_data__epg_source')` on all EPG channel querysets, eliminating N+1 database queries for `EPGSource` traversal per channel (~15 s improvement on ~2000-channel deployments; total EPG generation time dropped from ~87 s to ~72 s in benchmarks).
|
||||
- Performance: `xc_get_epg` now uses `select_related('epg_data__epg_source')` on all three channel fetch paths. Previously each request triggered 2 additional queries to resolve `channel.epg_data` and `channel.epg_data.epg_source`.
|
||||
- Performance: `generate_m3u` now uses `prefetch_related` for streams when `?direct=true` is requested, eliminating N+1 stream queries (one per channel) on that code path.
|
||||
- Performance: `EPGGridAPIView` (`apps/epg/api_views.py`) now uses `select_related('epg_data__epg_source')` on the `channels_with_custom_dummy` queryset, eliminating 2 extra queries per channel (for `epg_data` and `epg_source`) in the dummy EPG generation loop.
|
||||
- Performance: `generate_epg` now issues a single cross-channel `ProgramData` bulk query. `.values()` returns plain dicts, bypassing per-row Django model instantiation. Results are consumed in independent 5000-row keyset-paginated chunks. Combined with the `select_related` improvements above, EPG generation time on large deployments is significantly reduced.
|
||||
- Performance: `xc_get_live_streams` no longer calls `ChannelGroup.objects.get_or_create(name="Default Group")` once per null-group channel; replaced with a lazy-initialised closure that executes at most one query regardless of how many ungrouped channels are present.
|
||||
- AIO containers now connect to the internal PostgreSQL instance via a Unix domain socket instead of TCP loopback. Users who have `POSTGRES_HOST` explicitly set to `localhost` or `127.0.0.1` in their compose file are automatically migrated to the socket path; any other explicit value (external host/IP) is left untouched. — Thanks [@JCBird1012](https://github.com/JCBird1012)
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ class EPGGridAPIView(APIView):
|
|||
# Get channels with custom dummy EPG sources (generate on-demand with patterns)
|
||||
channels_with_custom_dummy = Channel.objects.filter(
|
||||
epg_data__epg_source__source_type='dummy'
|
||||
).distinct()
|
||||
).select_related('epg_data__epg_source').distinct()
|
||||
|
||||
# Log what we found
|
||||
without_count = channels_without_epg.count()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import ipaddress
|
||||
from django.http import HttpResponse, JsonResponse, Http404, HttpResponseForbidden, StreamingHttpResponse
|
||||
from rest_framework.response import Response
|
||||
from django.urls import reverse
|
||||
from apps.channels.models import Channel, ChannelProfile, ChannelGroup
|
||||
from apps.channels.models import Channel, ChannelProfile, ChannelGroup, Stream
|
||||
from django.db.models import Prefetch
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from apps.epg.models import ProgramData
|
||||
|
|
@ -11,9 +11,8 @@ from dispatcharr.utils import network_access_allowed
|
|||
from django.utils import timezone as django_timezone
|
||||
from django.shortcuts import get_object_or_404
|
||||
from datetime import datetime, timedelta
|
||||
import html # Add this import for XML escaping
|
||||
import json # Add this import for JSON parsing
|
||||
import time # Add this import for keep-alive delays
|
||||
import html
|
||||
import time
|
||||
from tzlocal import get_localzone
|
||||
from urllib.parse import urlparse
|
||||
import base64
|
||||
|
|
@ -175,6 +174,12 @@ def generate_m3u(request, profile_name=None, user=None):
|
|||
# Check if direct stream URLs should be used instead of proxy
|
||||
use_direct_urls = request.GET.get('direct', 'false').lower() == 'true'
|
||||
|
||||
# Prefetch streams only when direct URLs are requested (avoids N+1 per channel)
|
||||
if use_direct_urls:
|
||||
channels = channels.prefetch_related(
|
||||
Prefetch('streams', queryset=Stream.objects.order_by('channelstream__order'))
|
||||
)
|
||||
|
||||
# Get the source to use for tvg-id value
|
||||
# Options: 'channel_number' (default), 'tvg_id', 'gracenote'
|
||||
tvg_id_source = request.GET.get('tvg_id_source', 'channel_number').lower()
|
||||
|
|
@ -262,7 +267,8 @@ def generate_m3u(request, profile_name=None, user=None):
|
|||
stream_url = f"{base_url}/live/{xc_username}/{xc_password}/{channel.id}"
|
||||
elif use_direct_urls:
|
||||
# Try to get the first stream's direct URL
|
||||
first_stream = channel.streams.order_by('channelstream__order').first()
|
||||
all_streams = channel.streams.all()
|
||||
first_stream = all_streams[0] if all_streams else None
|
||||
if first_stream and first_stream.url:
|
||||
# Use the direct stream URL
|
||||
stream_url = first_stream.url
|
||||
|
|
@ -2253,7 +2259,7 @@ def xc_get_epg(request, user, short=False):
|
|||
# Hide adult content if user preference is set
|
||||
if (user.custom_properties or {}).get('hide_adult_content', False):
|
||||
filters["is_adult"] = False
|
||||
channel = Channel.objects.filter(**filters).first()
|
||||
channel = Channel.objects.filter(**filters).select_related('epg_data__epg_source').first()
|
||||
else:
|
||||
# User has specific limited profiles assigned
|
||||
filters = {
|
||||
|
|
@ -2265,12 +2271,12 @@ def xc_get_epg(request, user, short=False):
|
|||
# Hide adult content if user preference is set
|
||||
if (user.custom_properties or {}).get('hide_adult_content', False):
|
||||
filters["is_adult"] = False
|
||||
channel = Channel.objects.filter(**filters).distinct().first()
|
||||
channel = Channel.objects.filter(**filters).select_related('epg_data__epg_source').distinct().first()
|
||||
|
||||
if not channel:
|
||||
raise Http404()
|
||||
else:
|
||||
channel = get_object_or_404(Channel, id=channel_id)
|
||||
channel = get_object_or_404(Channel.objects.select_related('epg_data__epg_source'), id=channel_id)
|
||||
|
||||
if not channel:
|
||||
raise Http404()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue