Leaky Locks의 필요성: Redis TTL을 비용이 많이 드는 AI 작업의 Failure Cooldown으로 활용
Source: Dev.to
내가 예상하지 못한 문제: 락을 해제하는 것이 비용이 되다
내 작업 큐는 단순했다: 사용자가 문서를 제출 → AI가 평가 → 결과를 저장.
AI 호출은 실패할 수 있다—드물지만 발생한다. 모델이 기대한 출력 형식을 무시하거나 속도 제한에 걸릴 수 있다. 나는 예외를 잡고, 로그를 남기고, 작업을 실패로 표시한 뒤 finally 블록에서 락을 해제했다.
사용자가 다시 시도했을 때 AI가 또 실패하면서 재시도 폭풍이 발생했고, 이는 LLM API를 과도하게 호출하게 만들어 실제 비용이 급증했다.
락 만료를 쿨다운 메커니즘으로 사용하기
# Acquire a lock with a 5‑minute TTL
lock_key = f"ai_job_lock:{job_id}"
acquired = redis.set(lock_key, "1", nx=True, ex=300) # nx=True → set only if not exists
# If the lock already exists, the job is either running or cooling down
if not acquired:
return
try:
result = await call_llm_api(data)
save_result(result)
# Release lock only on success
redis.delete(lock_key)
except Exception as e:
log.error(f"Failed: {e}")
# Do NOT release the lock; the TTL provides the cooldown window락은 5분 동안 그대로 유지되고, 그 이후에 Redis가 자동으로 삭제한다. 이것은 메모리 누수가 아니라 자폭 키이다.
Without TTL: fail → retry → retry → retry → 20 calls in 60 s
With TTL: fail → blocked → blocked → retry at t=5 min왜 이렇게 하면 되는가
- 속도 제한 – 즉시 재시도하면 같은 제한에 걸린다.
- 네트워크 일시 장애 – 보통 몇 분 안에 해결되며 즉시 해결되지 않는다.
- 프롬프트 문제 – 재시도 빈도와 관계없이 스스로 해결되지 않는다.
시스템이 이미 과부하 상태일 때, 더 많은 비용이 드는 LLM 호출을 추가하면 상황이 악화된다. TTL 기반 쿨다운은 외부 서비스가 회복할 시간을 주면서 사용자에게 보이는 지연 시간을 낮게 유지한다.
내가 실제로 마음에 드는 부분
복잡한 재시도 로직, 지수 백오프, 상태 머신이 필요 없다—시간만 있으면 된다.
# All the code you need
acquired = redis.set(lock_key, "1", nx=True, ex=300)
# On failure, just let it ride; the lock expires naturally작업 도중 워커가 크래시가 나도, 락은 여전히 만료되므로 복구 서비스가 나중에 작업을 다시 잡아올 수 있다. 쿨다운 기간 동안 새로운 재시도 요청은 락을 획득하지 못하고 LLM을 호출하지 않은 채 바로 종료된다.
적용되지 않는 경우
- 재시도가 사실상 비용이 들지 않는 저비용 작업.
- 즉시 재시도가 반드시 필요한 작업.
- 사용자가 동기식, 즉각적인 응답을 기대하는 시나리오.
언급할 가치가 있는 위험
락 기간 불일치
작업이 TTL보다 오래 실행되면, 락이 만료된 시점에 다른 워커가 작업을 잡을 수 있다. 최악의 실행 시간을 초과하지 않도록 TTL을 충분히 크게 잡거나, 락을 갱신하는 하트비트를 구현하라.
결정적인 실패
쿨다운은 일시적인 문제(속도 제한, 네트워크 결함)에는 도움이 되지만, 잘못된 입력이나 깨진 프롬프트와 같은 영구적인 문제에는 효과가 없다. 이런 경우는 영구 실패로 분류하고 5분마다 루프하도록 두지 말라.
사용자에게 보여지는 피드백
사용자가 재시도했지만 진행 상황이 보이지 않으면 시스템이 고장난 것처럼 느껴진다. “재시도 가능까지 X 분 남음” 같은 안내나 작업 상태 표시기를 제공해 사용자가 시스템이 대기 중임을 알 수 있게 하라.
마무리
우리는 종종 복잡한 재시도 메커니즘, 서킷 브레이커, 폴백 시스템을 구축하는 데 많은 노력을 들인다. 때로 가장 간단한 답은 실패하게 두고, 잠시 기다렸다가 나중에 다시 시도하는 것이다. Redis TTL은 그 “잠시 기다림”을 자동으로 제공하며, 추가 코드가 거의 없고 실수로 우회되는 위험도 없다.
다른 사람도 이 방법을 써봤거나 더 좋은 접근법이 있다면 공유해 주세요!