From c241ab72dc57b311d668fec742c001dc30b9eab0 Mon Sep 17 00:00:00 2001 From: ogt Date: Sat, 11 Jul 2026 20:21:19 +0800 Subject: [PATCH] perf(iwooos): cache security control plane reads --- .../iwooos_security_asset_control_plane.py | 56 +++++++++++++------ ...est_iwooos_security_asset_control_plane.py | 24 ++++++++ 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/apps/api/src/services/iwooos_security_asset_control_plane.py b/apps/api/src/services/iwooos_security_asset_control_plane.py index 816290061..715bef153 100644 --- a/apps/api/src/services/iwooos_security_asset_control_plane.py +++ b/apps/api/src/services/iwooos_security_asset_control_plane.py @@ -17,6 +17,7 @@ logger = structlog.get_logger(__name__) _SCHEMA_VERSION = "iwooos_security_asset_control_plane_v1" _QUERY_TIMEOUT_SECONDS = 6.0 _SOURCE_QUERY_TIMEOUT_SECONDS = 4.0 +_CACHE_TTL_SECONDS = 30.0 _DISCOVERY_FRESHNESS_SECONDS = 2 * 60 * 60 _WINDOW_HOURS = 24 @@ -1328,24 +1329,45 @@ async def _read_public_source( class IwoooSSecurityAssetControlPlaneService: """Read aggregate production security state with a bounded DB timeout.""" + def __init__(self) -> None: + self._cached_payload: dict[str, Any] | None = None + self._cached_at_monotonic = 0.0 + self._refresh_lock = asyncio.Lock() + async def get_snapshot(self) -> dict[str, Any]: - try: - return await asyncio.wait_for( - self._load_snapshot(), timeout=_QUERY_TIMEOUT_SECONDS - ) - except TimeoutError: - logger.warning("iwooos_security_asset_control_plane_timeout") - return build_unavailable_iwooos_security_asset_control_plane( - "database_timeout" - ) - except Exception as exc: # noqa: BLE001 - public readback must degrade safely - logger.warning( - "iwooos_security_asset_control_plane_unavailable", - error_type=type(exc).__name__, - ) - return build_unavailable_iwooos_security_asset_control_plane( - "database_query_failed" - ) + loop = asyncio.get_running_loop() + if ( + self._cached_payload is not None + and loop.time() - self._cached_at_monotonic < _CACHE_TTL_SECONDS + ): + return self._cached_payload + + async with self._refresh_lock: + if ( + self._cached_payload is not None + and loop.time() - self._cached_at_monotonic < _CACHE_TTL_SECONDS + ): + return self._cached_payload + try: + payload = await asyncio.wait_for( + self._load_snapshot(), timeout=_QUERY_TIMEOUT_SECONDS + ) + except TimeoutError: + logger.warning("iwooos_security_asset_control_plane_timeout") + payload = build_unavailable_iwooos_security_asset_control_plane( + "database_timeout" + ) + except Exception as exc: # noqa: BLE001 - public readback must degrade safely + logger.warning( + "iwooos_security_asset_control_plane_unavailable", + error_type=type(exc).__name__, + ) + payload = build_unavailable_iwooos_security_asset_control_plane( + "database_query_failed" + ) + self._cached_payload = payload + self._cached_at_monotonic = loop.time() + return payload async def _load_snapshot(self) -> dict[str, Any]: source_results = await asyncio.gather( diff --git a/apps/api/tests/test_iwooos_security_asset_control_plane.py b/apps/api/tests/test_iwooos_security_asset_control_plane.py index f4f2c8946..3ec1b7f0a 100644 --- a/apps/api/tests/test_iwooos_security_asset_control_plane.py +++ b/apps/api/tests/test_iwooos_security_asset_control_plane.py @@ -290,6 +290,30 @@ def test_service_redacts_database_exception_details(monkeypatch) -> None: assert "private-host" not in text +def test_service_reuses_short_lived_public_aggregate_cache(monkeypatch) -> None: + service = IwoooSSecurityAssetControlPlaneService() + calls = 0 + expected = _ready_payload() + + async def load_once(): + nonlocal calls + calls += 1 + return expected + + monkeypatch.setattr(service, "_load_snapshot", load_once) + + async def read_twice(): + first = await service.get_snapshot() + second = await service.get_snapshot() + return first, second + + first, second = asyncio.run(read_twice()) + + assert calls == 1 + assert first is expected + assert second is expected + + def test_service_uses_canonical_awoooi_project_scope(monkeypatch) -> None: service = IwoooSSecurityAssetControlPlaneService() requested_projects: list[str] = []