728x90
반응형
SMALL
개요
백엔드 API 시스템을 운영하다 보면, 개발 환경에서는 전혀 문제 없던 서비스가 실제 클라우드 환경에 올라간 뒤 뜻밖의 장애나 성능 저하를 겪는 경우가 많습니다. AWS, GCP 등에서 백엔드 시스템을 운영하면서 네트워크 세션 관리, 커넥션 풀, 캐시 동작 등에서 문제가 발생할 때가 있는데 이번 글에서는 FastAPI, MySQL, Redis 조합을 사용하며 겪은 실제 이슈 경험을 바탕으로, 환경별 증상, 원인 분석, 문제 해결 방법, 그리고 실전에서 얻은 운영 노하우를 정리했습니다.
Redis: “Connection closed by server” 오류와 캐싱 이슈
현상
- Redis 서버의 timeout이 5초로 지정된 경우, 5초 이상 유휴하면
redis.exceptions.ConnectionError: Connection closed by server.가 발생합니다. - 트래픽이 적거나 API 호출 간격이 긴 서비스일수록 빈번하게 재현됩니다.
- 한 번 연결이 끊기고 나면 다음 요청에서 응답 지연 또는 연속적인 예외가 발생할 수 있습니다.
원인
- fastapi-cache-2[redis] 라이브러리를 설치하면 redis 4.x가 기본적으로 설치되는데, 이 버전은 연결 실패 시 사실상 재시도를 하지 않습니다.
- 클라우드 환경(AWS/GCP 등)은 로컬과 달리 유휴 연결 정책이 매우 보수적이라, 예기치 않게 커넥션이 더 빨리 끊길 수 있습니다.
- Redis Sentinel, 클러스터 구성에서는 이런 현상이 더 자주 나타날 수 있습니다.
해결
- fastapi-cache-2 대신 redis 6.x 이상 최신 패키지를 직접 설치합니다.
- 연결, 응답, health check, 커넥션 풀 관련 파라미터를 서비스 환경에 맞게 튜닝합니다.
{
"socket_timeout": 3.0,
"socket_connect_timeout": 5.0,
"health_check_interval": 3,
"max_connections": 20,
}- 이 설정을 적용한 뒤에는 장애 재현이 사라졌고, Redis 캐시 조회 속도 및 서비스 안정성이 모두 개선되었습니다.
실무 팁
- Redis Sentinel 환경에서는 health_check_interval을 더 짧게(1~3초) 설정하고, max_connections를 넉넉히 할당하면 failover 시점에도 장애 없이 운영할 수 있습니다.
- 장애 패턴을 재현 테스트와 모니터링에 반영해두면, 실시간 감지 및 자동 복구에 도움이 됩니다.
MySQL: 장기 Idle 커넥션, Pool 관리와 네트워크 이슈
현상
- 서버 기동 이후 일정 시간 아무 요청 없이 idle 상태가 지속된 뒤, API 호출 시 DB 연결이 무한 대기(gateway timeout)되는 문제가 발생합니다.
- 주로 AWS VPC 같은 클라우드 내부망에서 재현되고, 로컬/테스트 환경에서는 발생하지 않습니다.
- 장애는 야간이나 이벤트 후 트래픽이 급감하는 시간대에 집중적으로 발생하는 경향이 있습니다.
원인
- AWS VPC 등 클라우드 네트워크는 일정 시간 이상 idle된 TCP 세션을 조용히 종료하는 설정이 되어있는 경우가 있습니다.
- 유휴 상태인 연결이 AWS 어플라이언스에서 drop → 클라이언트가 pre-ping 쿼리를 전송 → 패킷은 어플라이언스에서 버려지고, RST도 못 받음. 리눅스 TCP 스택이 net.ipv4.tcp_retries2=15 (기본값)에 따라 재전송(RTO backoff) 을 수행
마지막(15번째) 재전송이 실패한 후 약 924.6초가 지나야(≈15.4분) 애플리케이션에 “Broken pipe” 오류를 반환
즉, 클라이언트 레벨에서 연결이 끊어졌다는 걸 감지하기 위해 OS가 긴 재전송 주기와 횟수를 소진하는 동안 약 950초가 소요되는 것입니다. - API는 gateway timeout으로 이미 종료되기 때문에 한참 늦게 오는 응답 자체가 무의미해집니다.
해결 및 설정 예시
- SQLAlchemy의 pool_recycle 값을 네트워크 idle timeout보다 짧게(예: 150초) 설정합니다. idle timeout 전에 연결이 초기화 되므로 해당 이슈를 피할 수 있습니다.
- pool_pre_ping=True로 커넥션 유효성을 항상 체크합니다.
- TCP keepalive는 기본적으로 비활성화되어 있기 때문에 파이썬 코드에서 TCP keepalive를 명확히 활성화하여 idle timeout 전에 연결 상태가 계속 유지되게 합니다.
from sqlalchemy import create_engine, event, Engine
import socket
def create_engine_with_pool_settings(database_uri: str):
return create_engine(
database_uri,
pool_size=10,
max_overflow=20,
pool_pre_ping=True,
pool_recycle=150,
pool_timeout=30,
echo=True,
)
@event.listens_for(Engine, "connect")
def set_tcp_keepalive(dbapi_conn, connection_record):
sock = getattr(dbapi_conn, "_sock", None) # PyMySQL 기준
if not sock:
return
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 2)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)- 위 설정 후 AWS 등 클라우드 환경에서 idle 커넥션 이슈가 해결되고, pre-ping 쿼리 이후 대기 현상이 사라졌습니다.
실무 팁
- 커넥션 풀 크기(pool_size, max_overflow), timeout 등은 트래픽 패턴에 따라 주기적으로 재점검해야 합니다.
- 장애가 발생할 때는 DB 접근 직전, 네트워크 레벨 로그, 인프라 모니터링을 모두 확인해야 근본 원인을 빨리 찾을 수 있습니다.
- 배치나 이벤트 트래픽 등 비정상적 부하 시나리오에서도 별도 테스트를 권장합니다.
Redis (Fastapi-cache backend로 사용)
- Redis 또한 MySQL 연결과 동일한 원인의 이슈 또한 발생했고 다음과 같이 설정하여 모든 이슈를 해결했습니다. 해결 방법은 MySQL에서의 접근 방식과 유사합니다.
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
import socket
from redis.sentinel import Sentinel
import aioredis
@asynccontextmanager
async def lifespan(_: FastAPI):
kwargs = {
"socket_timeout": 3.0,
"socket_connect_timeout": 3.0,
"socket_keepalive": True,
"socket_keepalive_options": {
socket.TCP_KEEPIDLE: 120,
socket.TCP_KEEPINTVL: 10,
socket.TCP_KEEPCNT: 5,
},
"health_check_interval": 3,
"max_connections": 20,
}
if settings.SENTINELS:
sentinel = Sentinel(settings.SENTINELS, **kwargs)
redis = sentinel.master_for(settings.SENTINEL_MASTER_NAME, password=settings.REDIS_PASSWORD)
else:
redis = aioredis.from_url(settings.REDIS_URL, **kwargs)
FastAPICache.init(RedisBackend(redis), prefix=settings.CACHE_PREFIX)
yield
await redis.aclose()
app = FastAPI(..., lifespan=lifespan, ...)실무 팁
- FastAPI lifespan에서 Redis 세션 해제와 자원 반납 코드를 명확히 작성하면 서버 리소스 릭(leak) 방지에도 효과적입니다.
- 장애가 잦은 환경에서는 health_check_interval을 1~2초로 더 짧게 조정해 빠른 감지와 대응이 가능합니다.
- 장애 조치 매뉴얼을 문서화해두면 운영 안정성 향상에 큰 도움이 됩니다.
실전 운영 체크리스트 및 권장 베스트 프랙티스
- 클라우드 환경(AWS, GCP 등)에서 네트워크 idle timeout과 TCP 커넥션 특성을 반드시 파악할 것 (로컬, 개발/테스트, 운영환경마다 동작이 다를 수 있음)
- DB/Redis 연결 풀, keepalive, health check 등 파라미터를 서비스 환경에 맞게 직접 튜닝
- 장애, 세션 종료, 지연 등은 반드시 실제 운영/스테이징 환경에서 반복 테스트할 것
- 로그, 모니터링, APM 등으로 장애 시점과 원인을 신속하게 파악할 수 있는 체계를 갖출 것
- pool_size, max_overflow 등 풀 파라미터는 트래픽 증가나 야간 배치 등에도 적합한지 수시로 점검
- 장애 사례와 조치 방법은 반드시 문서화·공유하여 조직 전체의 안정성을 높일 것
마치며
실제 운영 중에 경험한 API 이슈와 그 해결 방법을 공유했습니다. FastAPI, SQLAlchemy, Redis, 클라우드 환경 등에서 백엔드 시스템을 운영하는 파이썬 개발자라면 언제든 비슷한 문제를 만날 수 있습니다.
단순히 코드 한 줄, 파라미터 한 줄로 해결되는 이슈가 아니었고, 네트워크, 인프라, 라이브러리 버전, 환경 설정 등 다양한 요인이 복합적으로 얽혀 있는 문제였습니다. 환경별 특성을 정확히 이해하고, 시뮬레이션 테스트와 모니터링, 사후 문서화를 병행하는 것이 실전에서 중요한 전략이라는걸 느낄 수 있었던 트러블슈팅 경험이었습니다.
728x90
반응형
LIST
'파이썬 (Python)' 카테고리의 다른 글
| Python 의존성 관리 도구 Poetry: 프로젝트를 간편하게 (1) | 2024.10.21 |
|---|---|
| 파이썬 (Python): 강력하고 유연한 프로그래밍 언어 (0) | 2023.06.18 |
| [파이썬 (Python)] 파이썬 관련 참고할만한 정보 모음 (0) | 2023.06.18 |