perf: reduce memory usage by ~200MB+ at startup (#2013)

* perf: reduce memory usage by ~200MB+ at startup

Two key optimizations:

1. Use importlib.util.find_spec() instead of __import__() in dependency
   checking. find_spec() only locates modules without executing them,
   avoiding loading all 36 dependencies (~222MB) into memory at startup.

2. Introduce shared aiohttp.ClientSession via httpclient module.
   Previously, every HTTP request created a new ClientSession, which
   creates a new TCPConnector and SSL context, loading system root
   certificates each time (~270MB total allocations observed via memray).
   Now all HTTP client code reuses shared sessions.

   - satori.py and coze_server_api/client.py are left unchanged as they
     create one session per adapter lifecycle (not per-request).

Profiling data (memray):
- Peak memory: 403MB
- SSL context creation: 270MB / 6.7M allocations (67% of total)
- Dependency import: 222MB (55% of peak)
- Expected reduction: 150-350MB at startup

* fix: remove unused aiohttp imports (ruff F401)

* style: ruff format
This commit is contained in:
Junyan Chin
2026-02-27 20:09:03 +08:00
committed by GitHub
parent 2dc5999583
commit 88132dff8a
13 changed files with 370 additions and 321 deletions
+18 -16
View File
@@ -3,6 +3,8 @@ from __future__ import annotations
import asyncio
import logging
import aiohttp
from langbot.pkg.utils import httpclient
import uuid
from typing import TYPE_CHECKING
@@ -119,23 +121,23 @@ class WebhookPusher:
dict | None: The response JSON if successful, None otherwise
"""
try:
async with aiohttp.ClientSession() as session:
async with session.post(
url,
json=payload,
headers={'Content-Type': 'application/json'},
timeout=aiohttp.ClientTimeout(total=15),
) as response:
if response.status >= 400:
self.logger.warning(f'Webhook {url} returned status {response.status}')
session = httpclient.get_session()
async with session.post(
url,
json=payload,
headers={'Content-Type': 'application/json'},
timeout=aiohttp.ClientTimeout(total=15),
) as response:
if response.status >= 400:
self.logger.warning(f'Webhook {url} returned status {response.status}')
return None
else:
self.logger.debug(f'Successfully pushed to webhook {url}')
try:
return await response.json()
except Exception as json_error:
self.logger.debug(f'Failed to parse JSON response from webhook {url}: {json_error}')
return None
else:
self.logger.debug(f'Successfully pushed to webhook {url}')
try:
return await response.json()
except Exception as json_error:
self.logger.debug(f'Failed to parse JSON response from webhook {url}: {json_error}')
return None
except asyncio.TimeoutError:
self.logger.warning(f'Timeout pushing to webhook {url}')
return None