DevInsight

개발하며 겪은 판단을 적어요

Backend
조회 3약 4분 읽기

작은 서비스도 버틸 수 있는가, 폴링 한계와 큐 도입 시점의 판단 기준

작은 서비스에서 DB 폴링으로 작업을 처리하다 보면 스루풋이 급격히 떨어지고 레이턴시가 늘어나는 신호가 나옵니다. 이 글에서는 폴링 기반 처리가 감당해야 하는 한도를 분석하고, 큐 시스템 도입이 필요한 시점의 구체적 판단 기준을 제시합니다. 단순히 '트래픽이 늘었다'는 증상이 아니라 시스템 자체의 포화 상태를 어떻게 인지하고 대응할지, 도입 후 새겨야 하는 운영 체크포인트까지 함께 다룹니다.

DevInsight에서 발행해요

#메시지 큐#폴링#백엔드 아키텍처#Redis#성능 최적화#시스템 스케일링#운영 판단#작업 처리 패턴

데이터베이스 폴링으로 작업을 처리하다 보면, 어느 순간 CPU는 여유 있는데 응답만 점점 늦어지는 상황을 마주하게 된다. 폴링 간격을 1초에서 100ms로 줄이면 데이터베이스 연결 수는 기하급수적으로 늘어나고, 커넥션 풀은 부서지기 시작한다. 이 글은 폴링 기반 처리가 감당해야 하는 한도를 분석하고, Redis 같은 메시지 큐 시스템을 도입해야 하는 시점의 구체적 판단 기준을 제시한다.

폴링이 취해야 하는 교과서적 답

작은 서비스에서는 폴링이 답이 될 수 있다. 데이터베이스에 WHERE status = 'pending' LIMIT 100 쿼리를 날리면서 주기적으로 작업을 집어내 처리하는 방식은 복잡한 인프라 없이 동작한다. Redis나 RabbitMQ 같은 별도 시스템을 프로비저닝하지 않아도 되고, 배포도 기존 애플리케이션에 코드 몇 줄 추가로 끝난다.

문제는 이 방식이 어디까지 버틸 수 있는가에 있다. 폴링 주기가 1초라면 최대 1초의 레이턴시가 발생한다. 주기를 100ms로 줄이면 평균 레이턴시는 50ms가 되지만, 데이터베이스에 매초 10회의 쿼리가 발생한다. 10개의 워커가 동시에 이런 폴링을 하면 데이터베이스에는 초당 100개의 폴링 쿼리가 추가로 발생한다.

이 정도면 작은 서비스라면 데이터베이스가 간당간적으로 버틸 수 있다. PostgreSQL의 커넥션 한계는 보통 100~200개이지만, 커넥션 풀을 적절히 튜닝하면 500개까지도 감당 가능하다. 문제는 폴링 주기를 더 짧게 줄였을 때 벌어지는 일이다. 10ms 폴링이라면? 데이터베이스에는 초당 100회의 쿼리가 발생한다. 10개 워커라면 1,000회. 이미 커넥션 한계에 부딪히는 지점이다.

시스템이 포화 상태에 진입하는 신호

증상은 단순하다. 응답 시간이 점점 늦어지면서도 CPU 사용률은 여전히 낮다. 메모리도 넉넉하다. 데이터베이스의 커넥션 수가 한계에 도달했음에도 불구하고, 데이터베이스 자체의 부하 지표는 높지 않아 보인다. 이런 상황에서 흔히 하는 실수는 폴링 주기를 더 짧게 줄이는 것이다.

폴링 주기를 짧게 줄이면 오히려 역효과가 발생한다. 데이터베이스의 락 경합이 심화되고, 커넥션 풀 고갈이 시작된다. 폴링 쿼리가 실패하면 워커는 다음 폴링 주기까지 기다릴 수밖에 없으며, 이는 레이턴시를 더욱 늘리는 악순환을 초래한다.

실제 포화 상태를 판단하는 가장 확실한 방법은 커넥션 풀의 대기 시간을 측정하는 것이다. 데이터베이스 커넥션을 요청했을 때 10ms가 걸렸다면 정상이다. 하지만 100ms, 500ms가 걸린다면? 그것이 폴링 시스템의 한계 신호다.

큐 도입이 가져오는 책임의 목록

Redis 같은 메시지 큐를 도입하면 새로운 운영 부담이 따른다. 단순히 폴링 쿼리 하나로 끝났던 것이 now 이 되었다. 큐 시스템의 클러스터링, 장애 조치, 메모리 관리, 메시지 만료 정책 등 고려해야 할 변수가 급격히 늘어난다.

Redis 운영은 생각보다 어렵다. 단일 인스턴스라면 문제를 일으킬 여지가 적지만, 장애 시 entire 시스템이 마비된다. 복제본을 두어도 복제 지연이 발생하고, 슬레이브가 마스터에서 분리될 때 순간의 일관성 문제가 생긴다. 심지어 Redis의 AOF(Append Only File) 설정 하나가 성능에 2~3배 차이를 낼 수 있다.

하지만 큐 시스템의 가장 큰 장점은 폴링 레이턴시의 근본적 해결이다. 워커가 폴링을 할 필요 없이, 새 작업이 도착하는 즉시 처리를 시작할 수 있다. 평균 처리 시간은 12ms로 줄어들고, 시스템의 스루풋은 510배 향상된다.

비용은 인프라가 정한다

폴링 시스템의 비용은 단순하다. 애플리케이션 서버와 데이터베이스 서버의 비용만 있으면 된다. 하지만 큐 시스템을 도입하면 Redis 인스턴스, 모니터링 에이전트, 백업 시스템 등 추가적인 비용이 발생한다.

AWS 기준으로 보자. 데이터베이스가 t3.micro(0.023 USD/시간)로 감당 가능하다고 가정하면, Redis도 cache.t3.micro(0.024 USD/시간)로 처리할 수 있다. 하지만 실제 운영에서는 HA를 위해 최소 2대의 Redis 노드가 필요하고, 백업과 모니터링을 위한 Lambda 함수나 CloudWatch 비용도 추가된다.

더 큰 문제는 운영 비용이다. 폴링 시스템에서는 데이터베이스 관리만으로 충분했다. Redis를 도입하면 Redis 전문가가 필요하고, 백업 전략, 장애 복구 절차, 성능 튜닝 등에 대한 지식이 필요하다. 팀의 규모가 작다면 이 비용이 무게감 있게 느껴질 수밖에 없다.

늦은 도입과 빠른 도입 실패는 둘 다 있다

흔한 실수는 두 가지다. 하나는 폴링 시스템이 아직 버틸 수 있는 상황에서 미리 큐를 도입하는 것이고, 다른 하나는 시스템이 포화 상태에 왔을 때 급하게 큐를 도입하는 것이다.

미리 큐를 도입하면 운영 부담이 커지고, 예산도 낭비된다. 개발도 복잡해지고 배포 파이프라인도 정교화해야 한다. 반면에 급하게 도입하면, 기존 폴링 코드와 큐 시스템이 공존하는 혼란한 상태가 되기 쉽다. 마이그레이션 기간 동안은 두 시스템을 동시에 운영해야 하는 부담이 생긴다.

도입 시점의 판단 기준

폴링 시스템에서 큐 도입을 고려해야 하는 시점은 명확하다.

첫 번째 기준: 커넥션 풀 대기 시간이 50ms를 넘을 때. 이는 폴링 쿼리가 데이터베이스를 과부하 상태에 러브했음을 의미한다.

두 번째 기준: 폴링 쿼리의 비중이 전체 데이터베이스 쿼리의 30%를 넘을 때. 이는 데이터베이스 리소스가 폴링에 지나치게 점령되고 있음을 뜻한다.

세 번째 기준: 평균 레이턴시가 100ms를 넘고, 동시 처리량이 포화 상태일 때. 폴링 레이턴시의 하한선에 도달했음을 의미한다.

이 세 가지 기준 중 하나라도 만족한다면, 큐 도입을 검토해야 한다. 반대로 이들이 모두 충족되지 않았다면, 폴링 시스템을 유지하는 것이 더 현명한 선택이다.

마이그레이션 전략

큐 도입을 결정했다면, 급하게 모든 것을 옮길 필요는 없다. 점진적인 접근이 더 안전하다.

먼저 작은 부하의 작업부터 큐로 옮겨본다. 예를 들어 알림 발송 같은 부수적인 작업을 우선 옮기면, 큐 시스템의 운영 경험을 쌓을 수 있다. 점차 주요 비즈니스 로직까지 확장해 나가는 것이 좋다.

마이그레이션 중에는 폴백(fallback) 메커니즘을 반드시 마련해야 한다. 큐 시스템에 문제가 발생하면 즉시 폴링 시스템으로 되돌릴 수 있는 능력이 필요하다. 이는 피처 플래그나 라우팅 로직으로 구현할 수 있다.

운영 체크포인트는 단순하다. Redis의 메모리 사용량, 큐의 대기 메시지 수, 워커의 처리 속도, 실패율 등을 모니터링해야 한다. 특히 메시지가 큐에서 오래 대기하는 현상은 워커의 처리 한계를 의미하므로 즉시 대응해야 한다.

결국 선택은 Trade-off다

폴링과 큐는 각자의 한계가 있다. 폴링은 단순하고 직관적이지만, 스케일 한계가 명확하다. 큐는 높은 처리량을 제공하지만 운영 복잡도가 늘어난다.

작은 서비스의 개발자에게 가장 중요한 것은 적절한 시점의 판단이다. 아직 폴링으로 버틸 수 있다면, 불필요한 복잡성을 추가하지 않는 것이 맞다. 하지만 시스템 지표가 한계에 부딪히고 있음을 인지했다면, 두려워하지 말고 큐로 넘어서야 한다.

핵심은 폴링이 '충분히 잘못'되지 않았다는 증거를 지표로 확보하는 것이다. 데이터베이스 커넥션 수, 폴링 쿼리의 비중, 평균 레이턴시, 워커의 CPU 사용률 등 여러 지표를 종합적으로 분석하면, 정답에 더 가까워질 수 있다.

댓글

댓글을 읽어오는 중입니다.

같이 읽으면 좋은 글

방금 읽은 주제와 이어지는 글을 골랐습니다.

Backend 전체 보기
Backend

1년 동안 DB 테이블로 큐 대신 쓰면서 내가 놓친 것들

DB 폴링 기반 작업 처리로 버틸 수 있는 한계와 Redis·메시지 큐 도입을 결정해야 하는 실전 신호, 그리고 큐를 도입할 때 따라오는 운영 부담을 사례 중심으로 정리한다. DB 테이블 하나로 작업 큐를 대신한 지 1년이 지났다. 처음엔 "언젠가 Redis나 RabbitMQ를 도입해야지"라고 생각했지만, 그 언젠가는 오지 않았다.

#메시지 큐#Redis#BullMQ#DB 폴링
Backend

웹훅은 믿지 말고, 시그니처로 검증하라

웹훅 수신부는 외부의 불신을 전제로 설계해야 한다. 재전송과 처리 지연, 심지어 위조 시도까지 뒤따르는 불안정한 요청을 믿으면 안 되기에, 시그니처 검증과 멱등 처리, 재전송 ID 추적, 지연 시 큐잉 판단 등 수신부 설계에서 빼먹기 쉬운 요소를 모두 챙겨야 비로소 안정적 수신이 시작된다. 특히 재전송은 멱등키 없이 또다시 도착해도 중복 처리되지 않도록, 지연이 길어지면 즉시 큐에 넣어 처리를 미루어야 한다는 점을 잊지 말자

#webhook#signature-verification#idempotency#retry-handling

이전 글

웹훅은 믿지 말고, 시그니처로 검증하라

다음 글

2,731개의 타입 에러와 마주했던 한 주, strict 옵션을 끄고 켜는 사이의 현실

DevInsight Digest

새 글이 쌓이면, 피드에서 바로 이어 읽으세요.

과장된 알림 대신 발행한 글 전체를 RSS로 제공합니다.

RSS 구독