From dda896bbdc7069f715b955faa0706244f5125ce6 Mon Sep 17 00:00:00 2001 From: ZXYxc Date: Wed, 26 Aug 2026 16:34:55 +0800 Subject: [PATCH] feat(proxy): allow disabling background health persistence --- litellm/proxy/_types.py | 4 ++ litellm/proxy/proxy_server.py | 7 ++- .../proxy_server/test_background_health.py | 45 +++++++++++++++++-- ui/litellm-dashboard/src/lib/http/schema.d.ts | 6 +++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index ed35691c6ec..e165b66f935 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2493,6 +2493,10 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase): description="for `/models` endpoint, infers available model based on environment keys (e.g. OPENAI_API_KEY)", ) background_health_checks: bool | None = Field(None, description="run health checks in background") + persist_background_health_check_results: bool = Field( + True, + description="persist background health check results to the database", + ) health_check_interval: int = Field(300, description="background health check interval in seconds") health_check_concurrency: int | None = Field( None, diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 9b5d1b9bfea..4e6037bcb4c 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -3437,9 +3437,10 @@ def _schedule_background_health_check_db_save( model_list: list, healthy_endpoints: list, unhealthy_endpoints: list, + persist_results: bool = True, ): """Fire-and-forget: persist health check results to DB if prisma is available.""" - if prisma_client is None: + if prisma_client is None or not persist_results: return import time as time_module @@ -3608,11 +3609,12 @@ async def _run_background_health_check(): ) background_health_check_loop_active = True verbose_proxy_logger.info( - "background_health_check_loop_started interval_seconds=%s max_concurrency=%s shared=%s details=%s thread_count=%d rss_mb=%s", + "background_health_check_loop_started interval_seconds=%s max_concurrency=%s shared=%s details=%s persist_results=%s thread_count=%d rss_mb=%s", health_check_interval, health_check_concurrency, use_shared_health_check, health_check_details, + general_settings.get("persist_background_health_check_results", True), threading.active_count(), _rss_mb_for_log(), ) @@ -3744,6 +3746,7 @@ async def _run_background_health_check(): _llm_model_list, healthy_endpoints, unhealthy_endpoints, + persist_results=general_settings.get("persist_background_health_check_results", True), ) # Write health state to router cache for health-check-driven routing diff --git a/tests/test_litellm/proxy/proxy_server/test_background_health.py b/tests/test_litellm/proxy/proxy_server/test_background_health.py index dca93e137ac..d32d4a57e62 100644 --- a/tests/test_litellm/proxy/proxy_server/test_background_health.py +++ b/tests/test_litellm/proxy/proxy_server/test_background_health.py @@ -19,14 +19,15 @@ from unittest.mock import AsyncMock, MagicMock import pytest -import litellm.proxy.proxy_server as proxy_server +from litellm.proxy import proxy_server +from litellm.proxy._types import ConfigGeneralSettings from litellm.proxy.proxy_server import ( _adaptive_router_flusher_loop, _get_endpoint_exception_status, _get_process_rss_mb, + _rss_mb_for_log, _run_background_health_check, _run_direct_health_check_with_instrumentation, - _rss_mb_for_log, _schedule_background_health_check_db_save, _write_health_state_to_router_cache, ) @@ -159,6 +160,18 @@ async def test_run_direct_health_check_raises_non_kwarg_typeerror(monkeypatch): # --------------------------------------------------------------------------- +def test_background_health_check_db_persistence_config_defaults_enabled(): + assert ConfigGeneralSettings().persist_background_health_check_results is True + + +def test_background_health_check_db_persistence_config_can_be_disabled(): + settings = ConfigGeneralSettings.model_validate( + {"persist_background_health_check_results": False} + ) + + assert settings.persist_background_health_check_results is False + + @pytest.mark.asyncio async def test_schedule_background_health_check_db_save_creates_task(monkeypatch): captured = {} @@ -219,6 +232,24 @@ def test_schedule_background_health_check_db_save_noop_when_prisma_none(): ) +def test_schedule_background_health_check_db_save_noop_when_persistence_disabled( + monkeypatch, +): + create_task = MagicMock() + monkeypatch.setattr(asyncio, "create_task", create_task) + + _schedule_background_health_check_db_save( + prisma_client=MagicMock(), + shared_health_manager=None, + model_list=[], + healthy_endpoints=[], + unhealthy_endpoints=[], + persist_results=False, + ) + + create_task.assert_not_called() + + @pytest.mark.asyncio async def test_schedule_background_health_check_db_save_invalid_no_event_loop_raises( monkeypatch, @@ -461,6 +492,11 @@ async def test_run_background_health_check_runs_one_cycle_then_cancels(monkeypat monkeypatch.setattr(proxy_server, "use_shared_health_check", False) monkeypatch.setattr(proxy_server, "redis_usage_cache", None) monkeypatch.setattr(proxy_server, "prisma_client", None) + monkeypatch.setattr( + proxy_server, + "general_settings", + {"persist_background_health_check_results": False}, + ) monkeypatch.setattr(proxy_server, "background_health_check_loop_active", False) monkeypatch.setattr( proxy_server, @@ -481,8 +517,9 @@ async def test_run_background_health_check_runs_one_cycle_then_cancels(monkeypat "_run_direct_health_check_with_instrumentation", _fake_direct, ) + scheduled = MagicMock() monkeypatch.setattr( - proxy_server, "_schedule_background_health_check_db_save", lambda *a, **kw: None + proxy_server, "_schedule_background_health_check_db_save", scheduled ) monkeypatch.setattr( proxy_server, "_write_health_state_to_router_cache", lambda *a, **kw: None @@ -508,10 +545,12 @@ async def test_run_background_health_check_runs_one_cycle_then_cancels(monkeypat { "healthy_count": proxy_server.health_check_results["healthy_count"], "unhealthy_count": proxy_server.health_check_results["unhealthy_count"], + "persist_results": scheduled.call_args.kwargs["persist_results"], "sleep_invoked": sleep_calls["n"] >= 1, } ) == { "healthy_count": 1, "unhealthy_count": 1, + "persist_results": False, "sleep_invoked": True, } diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index e0b8cf19159..ecfdc9194e6 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -24279,6 +24279,12 @@ export interface components { * @description Default upstream request timeout in seconds for native and custom pass-through endpoints that use pass_through_request. Defaults to 600 when unset. */ pass_through_request_timeout?: number | null; + /** + * Persist Background Health Check Results + * @description persist background health check results to the database + * @default true + */ + persist_background_health_check_results: boolean; /** * Plugins * @description external services registered as embeddable UI plugins