~/infra-dev.md
INFRA_DEV

백그라운드 워커는 왜 HTTP 서버가 아니라 큐 소비자여야 할까

10분 읽기·2026.06.10·출처 5·00
오늘의 질문

오래 걸리는 작업을 서버리스에서 처리할 때, 왜 요청을 오래 붙잡기보다 큐에 넣고 워커가 당겨가기가 더 안전할까?

infra-dev.md
SURVIVE.exe

백그라운드 워커는 왜 HTTP 서버가 아니라 큐 소비자여야 할까

1. 왜 지금 봐야 하나

Cloud Run release notes에 따르면 Cloud Run worker pools는 2026년 4월 14일 GA가 되었다. 이 변화가 중요한 이유는 “서버리스 = HTTP 요청을 받아서 응답하는 서비스”라는 습관을 조금 깨기 때문이다. 실제 제품에는 HTTP보다 더 자주 실패하는 일이 있다. 결제 후 영수증 발송, 이미지 변환, LLM 후처리, Kafka/Pub/Sub 소비, 웹훅 재시도, 정산 배치처럼 오래 걸리고, 밀릴 수 있고, 중복 실행될 수 있는 일들이다.

이런 작업을 웹 요청 안에서 끝내려고 하면 세 가지 문제가 생긴다.

  1. 사용자는 작업 전체가 끝날 때까지 기다려야 한다.
  2. 트래픽이 튀면 HTTP 서비스가 작업 큐 역할까지 떠안는다.
  3. 실패했을 때 “응답 실패인지, 작업 실패인지, 중복 실행인지”가 섞인다.

Worker pool은 이 문제를 “사용자 요청을 받는 서비스”와 “큐에서 일을 가져가는 소비자”로 나누게 해준다. 특히 Pub/Sub나 Kafka처럼 소비자가 직접 메시지를 pull하는 구조에서는 처리 속도를 워커 쪽에서 조절할 수 있다. 이것이 오늘의 핵심인 backpressure다.

2. 핵심 개념

Backpressure는 시스템이 “지금은 더 못 받겠다”는 신호를 앞단에 전달하는 능력이다. 개발자 입장에서는 거창한 이론보다 아래 문장으로 기억하면 된다.

작업 생산 속도와 처리 속도가 다를 때, 큐는 시간을 벌어주고 워커는 처리 속도를 조절한다.

HTTP push 방식에서는 앞단이 작업을 밀어 넣는다. 요청이 많아지면 함수나 서비스가 더 많이 호출되고, downstream API·DB·LLM provider가 같이 압박을 받는다. 반대로 pull 방식에서는 워커가 자신이 감당할 수 있는 만큼 가져간다. 처리량이 부족하면 큐 depth 또는 Kafka offset lag가 쌓이고, 이것을 관측해서 워커 수를 늘릴지, 생산 속도를 제한할지, 일부 기능을 degrade할지 판단할 수 있다.

Cloud Run worker pool은 Cloud Run 서비스와 다르게 load-balanced endpoint나 URL이 없는 continuous background work용 리소스다. 문서에 따르면 worker pool revision은 immutable이고, 태그로 배포해도 digest로 고정된다. 즉 운영 관점에서는 “웹 엔드포인트 없는 장기 실행 컨테이너 집합”에 가깝다.

단, 중요한 제한도 있다. worker pool 자체 문서는 “worker pools do not support autoscaling”이라고 설명한다. 그래서 queue depth 기반 자동 확장은 별도 메커니즘이 필요하다. Google Cloud 문서는 Pub/Sub queue volume을 보고 Cloud Run External Metrics Autoscaling, 즉 CREMA로 worker pool instance 수를 조정하는 튜토리얼을 제공한다. Kafka 쪽도 offset lag 기반 autoscaler를 별도 도구로 다룬다.

3. 최신 이슈와 연결

Google Cloud Blog의 Estée Lauder 사례는 worker pool을 단순 기능 소개가 아니라 아키텍처 전환으로 보여준다. 사용자-facing FastAPI 서비스는 메시지를 Pub/Sub에 넣고, worker pool은 always-on consumer로 LLM inference와 후속 처리를 담당했다. 글은 전통적인 서버리스 모델이 background work를 HTTP push 형태로 밀어 넣으면 timeout, overscaling, traffic surge 때의 message loss가 생길 수 있다고 설명한다.

Kafka autoscaler 글은 왜 일반적인 CPU 기반 autoscaling이 부족한지도 잘 보여준다. Kafka consumer의 진짜 작업량 신호는 CPU가 아니라 offset lag다. CPU가 낮아도 lag가 계속 쌓이면 소비가 뒤처지고 있는 것이고, CPU가 높아도 lag가 줄고 있다면 현재 워커 수가 충분할 수 있다.

Pub/Sub 튜토리얼도 같은 방향이다. worker pool을 pull subscription consumer로 두고, CREMA가 undelivered message 수를 보고 instance 수를 조정한다. 여기서 배울 점은 “큐가 있으니 끝”이 아니라, 큐의 backlog를 운영 지표로 삼아야 한다는 것이다.

4. 개발자 관점 해석

작은 팀이 서버리스 백그라운드 작업을 설계할 때 흔한 실수는 “비동기 처리”를 “나중에 실행되는 HTTP 호출” 정도로만 생각하는 것이다. 하지만 운영에서는 다음 질문이 더 중요하다.

  • 작업이 두 번 실행되어도 결과가 망가지지 않는가?
  • 처리 속도보다 생산 속도가 빠를 때 어디에 쌓이는가?
  • 실패 메시지는 언제까지 재시도하고, 언제 dead-letter로 보낼 것인가?
  • 워커를 늘리면 비용만 느는가, downstream rate limit도 같이 터지는가?
  • 배포 중인 새 revision이 메시지를 잘못 처리하면 어떻게 canary할 것인가?

Worker pool은 이 질문을 더 명확하게 만든다. 웹 서비스는 빠르게 접수하고, 큐는 durability와 buffering을 담당하고, 워커는 처리량과 재시도를 담당한다. 이 분리는 신뢰성을 높이지만 비용과 운영 포인트도 늘린다.

비용 tradeoff도 있다. Google Cloud Blog는 worker pools가 장기 실행 background task에서 instance-billed Cloud Run services보다 CPU·memory 비용이 최대 40% 낮을 수 있다고 설명한다. 하지만 Pub/Sub, Artifact Registry, Cloud Build, Parameter Manager, CREMA scaling service도 비용 항목이다. “워커가 싸다”가 아니라 “항상 켜둘 작업을 HTTP 서비스로 억지 운영하는 것보다 나을 수 있다”로 받아들여야 한다.

보안 면에서는 엔드포인트가 없다는 점이 장점이다. 외부 HTTP endpoint를 노출하지 않으면 공격면이 줄어든다. 대신 worker service account가 Pub/Sub, Kafka, Secret Manager, DB에 접근하므로 IAM 범위가 더 중요해진다. CREMA 같은 autoscaler에도 worker pool을 조정할 권한과 metric 조회 권한이 필요하므로, scaler 권한과 consumer 권한을 분리해야 한다.

5. 내 프로젝트에 적용할 체크포인트

아래 체크리스트로 지금 운영 중인 background job을 점검해보자.

  • 작업 분리: 사용자 요청 안에서 끝내야 하는 일과 큐로 넘겨도 되는 일을 구분했는가?
  • 큐 선택: Pub/Sub, Kafka, Redis queue, SQS 중 어떤 시스템이 메시지 보존, 순서, fan-out, 비용 요구에 맞는가?
  • 처리량 신호: CPU가 아니라 queue depth, undelivered messages, oldest message age, Kafka offset lag를 보고 있는가?
  • 재시도 정책: 무한 재시도와 즉시 재시도가 downstream 장애를 증폭하지 않는가?
  • 멱등성: message id, 주문 id, idempotency key, unique constraint로 중복 실행을 흡수하는가?
  • 순서 보장: 모든 메시지 순서가 필요한가, 아니면 사용자·주문·tenant 단위의 부분 순서만 필요한가?
  • DLQ: poison message가 전체 소비를 막지 않도록 dead-letter 경로와 재처리 절차가 있는가?
  • 배포 전략: worker revision canary에서 일부 instance만 새 코드로 돌려볼 수 있는가?
  • 권한 분리: producer, consumer, autoscaler service account가 서로 다른 최소 권한을 가지는가?
  • 비용 상한: backlog가 쌓일 때 자동 확장이 downstream 비용과 LLM API 비용을 폭발시키지 않도록 cap이 있는가?

6. 오늘 10분 액션

오늘은 코드를 많이 바꾸지 말고, 현재 프로젝트의 “숨은 백그라운드 작업” 하나를 찾아 설계표를 만들어보자.

  1. 최근 1주일 로그에서 3초 이상 걸린 API route나 serverless function을 하나 고른다.
  2. 그 함수 안의 작업을 즉시 응답에 필요한 일나중에 해도 되는 일로 나눈다.
  3. 나중에 해도 되는 일에 대해 아래 네 줄을 적는다.
    • 메시지 키: 예) order_id, user_id, job_id
    • 중복 방지 방법: 예) DB unique constraint, processed_messages 테이블
    • backlog 지표: 예) oldest message age, Pub/Sub undelivered messages, Kafka offset lag
    • 실패 경로: 예) 5회 재시도 후 DLQ, 운영자 알림, 수동 replay
  4. 마지막으로 “워커를 10개로 늘리면 어떤 downstream이 먼저 터질까?”를 한 문장으로 적는다.

이 10분 액션의 목적은 Cloud Run worker pool을 당장 쓰는 것이 아니다. 목적은 HTTP 요청, 큐, 워커, 재시도, 비용 상한을 한 장의 운영 모델로 분리해서 보는 것이다.

7. 더 볼 자료

  • Cloud Run worker pools 문서: worker pool의 배포 방식, revision 불변성, IAM 요구사항을 확인하기 좋다.
  • Pub/Sub queue volume 기반 autoscaling 튜토리얼: queue depth를 scale signal로 바꾸는 예시다.
  • Kafka autoscaler 글: offset lag가 왜 Kafka consumer의 핵심 신호인지 설명한다.
  • Cloud Run release notes: worker pool GA, GPU, compose deployment 등 Cloud Run의 운영 primitive 변화를 추적할 수 있다.

중복 회피 메모

최근 로컬 및 Supabase의 infra_dev 글은 Kubernetes Job suspend와 리소스 재조정, 그리고 OIDC·signed URL을 통한 장기 secret 축소를 다뤘다. 이번 글은 Kubernetes 배치나 배포 secret을 반복하지 않고, Cloud Run worker pools GA와 Pub/Sub/Kafka autoscaling 자료를 바탕으로 pull 기반 background worker, backpressure, queue depth/offset lag, 멱등성, 비용 상한을 연결해 설명한다.

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)