feat(perf): implement HTTP connection pooling with httpx.AsyncClient singleton and Keep-Alive
- get_http_client: singleton AsyncClient with limits(max_keepalive=10, max_connections=20, keepalive_expiry=60s) - close_http_client: clean socket pool teardown in FastAPI lifespan shutdown - refactor dictionary fetchers to reuse pooled client and avoid 5x TLS handshakes per word lookup - add test_http_connection_pooling (22/22 pytest passed)
This commit is contained in:
1 parent
c4a6edee37
commit
438dfdb055
4 files changed
+211
-132
No files matched your search
@@ -1011,3 +1011,34 @@ def test_books_pdf_multipart_upload():
|
||||
|
||||
# Cleanup
|
||||
client.delete(f"/api/books/{book_id}")
|
||||
|
||||
|
||||
def test_http_connection_pooling():
|
||||
import pytest
|
||||
from app.dictionary import get_http_client, close_http_client, lookup_term
|
||||
import asyncio
|
||||
|
||||
# 1. Singleton returns same instance
|
||||
c1 = get_http_client()
|
||||
c2 = get_http_client()
|
||||
assert c1 is c2
|
||||
assert not c1.is_closed
|
||||
|
||||
# 2. Check connection limits configuration
|
||||
pool = c1._transport._pool
|
||||
assert pool._max_keepalive_connections == 10
|
||||
assert pool._max_connections == 20
|
||||
|
||||
# 3. Lookup using pooled client
|
||||
res = asyncio.run(lookup_term("resilience"))
|
||||
assert res["query"] == "resilience"
|
||||
assert "translation" in res
|
||||
|
||||
# 4. Clean closure on shutdown
|
||||
asyncio.run(close_http_client())
|
||||
assert c1.is_closed
|
||||
# Next call creates a new active client
|
||||
c3 = get_http_client()
|
||||
assert c3 is not c1
|
||||
assert not c3.is_closed
|
||||
asyncio.run(close_http_client())
|
||||
Reference in new issue
Block a user