From 4d84a476802cf0b374c1e35e2e4c7066d8579649 Mon Sep 17 00:00:00 2001 From: Sam Thornton Date: Wed, 18 Mar 2026 09:54:05 -0600 Subject: [PATCH] Fix updateinfo v2 timeout for RL8 AppStream by filtering prefetch The get_updateinfo_v2 endpoint was loading all advisory_packages rows for every matching advisory (all repos, arches, mirrors), then discarding ~95% of them in Python. For RL8 AppStream without a minor_version filter, this meant ~300k rows loaded to produce ~20k, consistently exceeding the 30-second server worker timeout. Fixes: - Use Prefetch with a filtered queryset on advisory__packages so only packages matching the requested repo and supported_product are loaded from the DB - Add composite index on advisory_packages(advisory_id, repo_name, supported_product_id) to support the filtered prefetch query - Add composite index on advisory_affected_products(supported_product_id, major_version, arch) to speed up the initial filter query Closes #77 --- .../20260318000000_add_updateinfo_perf_indexes.sql | 11 +++++++++++ apollo/schema.sql | 14 ++++++++++++++ apollo/server/routes/api_updateinfo.py | 11 +++++++++-- apollo/tests/test_api_updateinfo.py | 9 ++++++--- 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 apollo/migrations/20260318000000_add_updateinfo_perf_indexes.sql diff --git a/apollo/migrations/20260318000000_add_updateinfo_perf_indexes.sql b/apollo/migrations/20260318000000_add_updateinfo_perf_indexes.sql new file mode 100644 index 0000000..9815787 --- /dev/null +++ b/apollo/migrations/20260318000000_add_updateinfo_perf_indexes.sql @@ -0,0 +1,11 @@ +-- migrate:up +create index advisory_packages_advisory_repo_product_idx + on advisory_packages (advisory_id, repo_name, supported_product_id); + +create index advisory_affected_products_spid_major_arch_idx + on advisory_affected_products (supported_product_id, major_version, arch); + + +-- migrate:down +drop index if exists advisory_packages_advisory_repo_product_idx; +drop index if exists advisory_affected_products_spid_major_arch_idx; diff --git a/apollo/schema.sql b/apollo/schema.sql index 46c385a..9af1c94 100644 --- a/apollo/schema.sql +++ b/apollo/schema.sql @@ -1263,6 +1263,13 @@ CREATE INDEX advisory_affected_products_namex ON public.advisory_affected_produc CREATE INDEX advisory_affected_products_supported_product_idx ON public.advisory_affected_products USING btree (supported_product_id); +-- +-- Name: advisory_affected_products_spid_major_arch_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX advisory_affected_products_spid_major_arch_idx ON public.advisory_affected_products USING btree (supported_product_id, major_version, arch); + + -- -- Name: advisory_affected_products_variantx; Type: INDEX; Schema: public; Owner: - -- @@ -1298,6 +1305,13 @@ CREATE INDEX advisory_fixes_ticket_id ON public.advisory_fixes USING btree (tick CREATE INDEX advisory_packages_advisory_id ON public.advisory_packages USING btree (advisory_id); +-- +-- Name: advisory_packages_advisory_repo_product_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX advisory_packages_advisory_repo_product_idx ON public.advisory_packages USING btree (advisory_id, repo_name, supported_product_id); + + -- -- Name: advisory_packages_checksumx; Type: INDEX; Schema: public; Owner: - -- diff --git a/apollo/server/routes/api_updateinfo.py b/apollo/server/routes/api_updateinfo.py index d5789c9..6d6a210 100644 --- a/apollo/server/routes/api_updateinfo.py +++ b/apollo/server/routes/api_updateinfo.py @@ -5,8 +5,9 @@ from fastapi import APIRouter, Response from slugify import slugify -from apollo.db import AdvisoryAffectedProduct, SupportedProduct +from apollo.db import AdvisoryAffectedProduct, AdvisoryPackage, SupportedProduct from tortoise.exceptions import DoesNotExist +from tortoise.queryset import Prefetch from apollo.server.settings import COMPANY_NAME, MANAGING_EDITOR, UI_URL, get_setting from apollo.server.validation import Architecture @@ -451,7 +452,13 @@ async def get_updateinfo_v2( "advisory", "advisory__cves", "advisory__fixes", - "advisory__packages", + Prefetch( + "advisory__packages", + queryset=AdvisoryPackage.filter( + repo_name=repo, + supported_product_id=supported_product.id, + ), + ), "supported_product", ).all() diff --git a/apollo/tests/test_api_updateinfo.py b/apollo/tests/test_api_updateinfo.py index 1f92982..216a313 100644 --- a/apollo/tests/test_api_updateinfo.py +++ b/apollo/tests/test_api_updateinfo.py @@ -475,7 +475,8 @@ def test_get_updateinfo_with_arch_filter(self, mock_aap, mock_get_setting): @patch("apollo.server.routes.api_updateinfo.get_setting") @patch("apollo.server.routes.api_updateinfo.SupportedProduct") @patch("apollo.server.routes.api_updateinfo.AdvisoryAffectedProduct") - def test_get_updateinfo_v2_success(self, mock_aap, mock_sp, mock_get_setting): + @patch("apollo.server.routes.api_updateinfo.AdvisoryPackage") + def test_get_updateinfo_v2_success(self, mock_ap, mock_aap, mock_sp, mock_get_setting): """V2 endpoint returns valid XML with proper collection naming""" mock_get_setting.side_effect = self._mock_get_setting @@ -526,7 +527,8 @@ def test_get_updateinfo_v2_invalid_architecture(self, mock_sp): @patch("apollo.server.routes.api_updateinfo.SupportedProduct") @patch("apollo.server.routes.api_updateinfo.AdvisoryAffectedProduct") - def test_get_updateinfo_v2_no_advisories(self, mock_aap, mock_sp): + @patch("apollo.server.routes.api_updateinfo.AdvisoryPackage") + def test_get_updateinfo_v2_no_advisories(self, mock_ap, mock_aap, mock_sp): """V2 endpoint raises 404 when no advisories found""" mock_product = Mock() mock_product.id = 1 @@ -545,7 +547,8 @@ def test_get_updateinfo_v2_no_advisories(self, mock_aap, mock_sp): @patch("apollo.server.routes.api_updateinfo.get_setting") @patch("apollo.server.routes.api_updateinfo.SupportedProduct") @patch("apollo.server.routes.api_updateinfo.AdvisoryAffectedProduct") - def test_get_updateinfo_v2_with_minor_version(self, mock_aap, mock_sp, mock_get_setting): + @patch("apollo.server.routes.api_updateinfo.AdvisoryPackage") + def test_get_updateinfo_v2_with_minor_version(self, mock_ap, mock_aap, mock_sp, mock_get_setting): """V2 endpoint filters by optional minor_version parameter""" mock_get_setting.side_effect = self._mock_get_setting