supabase-async: ThreadPoolExecutor에서 httpx AsyncClient로 리팩토링
Source: Dev.to
supabase-async: ThreadPoolExecutor에서 httpx AsyncClient로 리팩토링
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()
Enter fullscreen mode
Exit fullscreen mode
문제점:
-
최대 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()
Enter fullscreen mode
Exit fullscreen mode
성능 개선
동시성 비교
지표 ThreadPoolExecutor(3) httpx(10)
최대 동시 요청 3개 10개
평균 응답 시간 450ms 150ms
메모리 사용량 250MB 180MB
초당 처리량 6.7 req/s 20 req/s
벤치마크
# 100개 동시 요청 처리 시간
ThreadPoolExecutor: 15초
httpx AsyncClient: 5초
→ 3배 빠름
Enter fullscreen mode
Exit fullscreen mode
마이그레이션 단계
- 클라이언트 초기화
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
Enter fullscreen mode
Exit fullscreen mode
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)
# ...
Enter fullscreen mode
Exit fullscreen mode
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")
Enter fullscreen mode
Exit fullscreen mode
실제 적용 결과
contest-agent에서 사용:
요청 성능 개선: 450ms → 150ms
메모리 절감: 250MB → 180MB (-28%)
동시성 증가: 3 → 10 (+233%)
Enter fullscreen mode
Exit fullscreen mode
주의사항
Connection pooling: httpx는 자동으로 커넥션 재사용
Exception handling: requests.HTTPError → httpx.HTTPError
Timeout: requests의 timeout 호환 유지
결론
ThreadPoolExecutor는 “async 모양”만 냈지만, httpx AsyncClient는 진정한 비동기 I/O를 제공합니다. 동시성이 3배 향상되고 메모리 사용량도 줄어듭니다.
특히 높은 동시 요청이 필요한 서비스(크롤링, API 게이트웨이)에서 큰 효과를 볼 수 있습니다.
