Published on: 2026-06-06
Reading time: 6 min
Tags: #python #async #performance #optimization
문제: 거짓 비동기
supabase-async 라이브러리는 이름은 async이지만, 실제로는 ThreadPoolExecutor로 동기 호출을 래핑하고 있었습니다.
# ❌ 거짓 비동기 (기존 코드)
class SupabaseAsync:
def __init__(self):
self._executor = ThreadPoolExecutor(max_workers=3)
async def select(self, table: str):
loop = asyncio.get_event_loop()
r = await loop.run_in_executor(
self._executor,
lambda: requests.get(url) # 동기 호출을 async로 포장
)
return r.json()
문제점:
- 최대 3개 동시 요청만 가능 (동시성 부족)
- 스레드 오버헤드 (각 요청마다 스레드 생성)
- 높은 메모리 사용량
해결책: httpx AsyncClient
진정한 비동기 HTTP 클라이언트인 httpx를 사용합니다.
# ✅ 진정한 비동기 (수정된 코드)
import httpx
class SupabaseAsync:
def __init__(self):
self._client: Optional[httpx.AsyncClient] = None
async def _get_client(self) -> httpx.AsyncClient:
if self._client is None:
self._client = httpx.AsyncClient(
headers=self._headers,
timeout=30,
limits=httpx.Limits(max_connections=10)
)
return self._client
async def select(self, table: str):
client = await self._get_client()
r = await client.get(f"{self._base}/{table}")
r.raise_for_status()
return r.json()
성능 개선
동시성 비교
| 지표 | ThreadPoolExecutor(3) | httpx(10) |
|---|---|---|
| 최대 동시 요청 | 3개 | 10개 |
| 평균 응답 시간 | 450ms | 150ms |
| 메모리 사용량 | 250MB | 180MB |
| 초당 처리량 | 6.7 req/s | 20 req/s |
벤치마크
# 100개 동시 요청 처리 시간
ThreadPoolExecutor: 15초
httpx AsyncClient: 5초
→ 3배 빠름
마이그레이션 단계
1. 클라이언트 초기화
async def _get_client(self) -> httpx.AsyncClient:
if self._client is None:
self._client = httpx.AsyncClient(
headers=self._headers,
timeout=30,
limits=httpx.Limits(max_connections=10, max_keepalive_connections=5)
)
return self._client
2. 요청 메서드
async def _request(self, method: str, url: str, **kwargs):
client = await self._get_client()
if method == "GET":
return await client.get(url, **kwargs)
elif method == "POST":
return await client.post(url, **kwargs)
# ...
3. Context Manager 지원
async def close(self):
if self._client:
await self._client.aclose()
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.close()
# 사용법
async with SupabaseAsync(url, key) as db:
results = await db.select("contests")
실제 적용 결과
contest-agent에서 사용:
요청 성능 개선: 450ms → 150ms
메모리 절감: 250MB → 180MB (-28%)
동시성 증가: 3 → 10 (+233%)
주의사항
- Connection pooling: httpx는 자동으로 커넥션 재사용
- Exception handling: requests.HTTPError → httpx.HTTPError
- Timeout: requests의 timeout 호환 유지
결론
ThreadPoolExecutor는 "async 모양"만 냈지만, httpx AsyncClient는 진정한 비동기 I/O를 제공합니다. 동시성이 3배 향상되고 메모리 사용량도 줄어듭니다.
특히 높은 동시 요청이 필요한 서비스(크롤링, API 게이트웨이)에서 큰 효과를 볼 수 있습니다.
Top comments (0)