From 9e893b7cda0a12f4ff6625a557b3b09c11352cb9 Mon Sep 17 00:00:00 2001 From: Alexsander Hamir Date: Tue, 20 Jan 2026 10:44:31 -0800 Subject: [PATCH] Fix: Handle PostgreSQL cached plan errors during rolling deployments (#19424) --- litellm/proxy/utils.py | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 9ea2ea7d5c9..fcb678ef02a 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -2214,6 +2214,45 @@ class PrismaClient: raise e + async def _query_first_with_cached_plan_fallback( + self, sql_query: str + ) -> Optional[dict]: + """ + Execute a query with automatic fallback for PostgreSQL cached plan errors. + + This handles the "cached plan must not change result type" error that occurs + during rolling deployments when schema changes are applied while old pods + still have cached query plans expecting the old schema. + + Args: + sql_query: SQL query string to execute + + Returns: + Query result or None + + Raises: + Original exception if not a cached plan error + """ + try: + return await self.db.query_first(query=sql_query) + except Exception as e: + error_str = str(e) + if "cached plan must not change result type" in error_str: + # Force PostgreSQL to re-plan by invalidating the cache + # Add a unique comment to make the query different + sql_query_retry = sql_query.replace( + "SELECT", + f"SELECT /* cache_invalidated_{int(time.time() * 1000)} */" + ) + verbose_proxy_logger.warning( + "PostgreSQL cached plan error detected for token lookup, " + "retrying with fresh plan. This may occur during rolling deployments " + "when schema changes are applied." + ) + return await self.db.query_first(query=sql_query_retry) + else: + raise + @backoff.on_exception( backoff.expo, Exception, # base exception to catch for the backoff @@ -2545,7 +2584,7 @@ class PrismaClient: WHERE v.token = '{token}' """ - response = await self.db.query_first(query=sql_query) + response = await self._query_first_with_cached_plan_fallback(sql_query) if response is not None: if response["team_models"] is None: