
스스로 비워지는 DLQ: AWS 기반의 자가 치유형 이벤트 기반 아키텍처
요약
API 스키마 변경으로 인해 발생하는 DLQ(Dead Letter Queue) 문제를 해결하기 위해 LLM을 데이터 변환기가 아닌 코드 생성기로 활용하는 자가 치유형 아키텍처를 제안합니다. 모든 데이터를 LLM에 통과시키는 대신, 샘플을 통해 변환 로직을 생성하고 이를 결정론적으로 실행하여 비용과 정확도를 모두 잡는 방식입니다.
핵심 포인트
- API 스키마 변경 시 발생하는 대량의 DLQ 메시지 처리 문제 해결
- LLM을 직접적인 데이터 변환 도구가 아닌 코드 생성기로 활용
- 토큰 비용 절감 및 비결정론적 출력 문제 방지
- AWS Bedrock을 활용한 보안 및 데이터 경계 관리
벤더가 API 버전을 올리지 않고 필드 타입을 변경합니다. 아무도 당신에게 알려주지 않습니다. 당신의 스키마 검증 (schema validation)은 모든 유입 주문을 거부하기 시작하고, 20분 이내에 5,000개의 웹훅 (webhooks)이 SQS 데드 레터 큐 (DLQ)에 쌓입니다. 기본 보관 기간은 4일이며, 최대 14일까지 지속됩니다.
구체적인 사례는 다음과 같습니다: Shopify의 주문 페이로드 (payload)에는 숫자형 id (820982911946154500)와 사용자가 보는 name (#1001)이 모두 포함되어 있습니다. 미들웨어 (middleware) 계층이 name을 당신의 서비스가 order_id로 읽는 필드에 매핑하기 시작하거나, 상위 클라이언트가 Shopify의 GraphQL 글로벌 ID (gid://shopify/Order/820982911946154500)로 전환합니다. 어느 쪽이든 정수 파싱 (integer parse)은 실패하며, 5,000건의 재고 차감은 이루어지지 않습니다.
표준적인 해결 방법은 시니어 엔지니어의 오후 시간을 사용하는 것입니다. 호출을 받고, 큐를 내보내고(export), 차이점이 명확해질 때까지 세 개의 샘플 페이로드를 살펴본 뒤, 일회성 Python 스크립트를 작성하고, 복사본으로 테스트한 후, 다시 드라이브 (redrive)합니다. 작동은 합니다. 하지만 이는 당신의 가장 값비싼 엔지니어를 사용하는 가장 흥미롭지 않은 방식이며, 금요일에 이런 일이 발생하는 빈도는 매우 높습니다.
사람 없이 실행되는 버전도 있습니다. 모델이 데이터를 수정할 만큼 똑똑해서가 아니라, _차이점 (diff)_을 찾아내는 것이 어려운 부분이며, 그 차이점은 단 하나의 샘플 페이로드 너비에 불과하기 때문입니다.
모델을 적용하는 데 쓰지 말고, 수정 사항을 작성하는 데 사용하세요
유혹적인 설계는 5,000개의 깨진 페이로드를 모두 LLM에 전달하고 깨끗한 페이로드를 돌려달라고 요청하는 것입니다. 그러지 마십시오.
수치를 계산해 봅시다: 약 1,000개의 입력 토큰과 1,000개의 출력 토큰을 가진 5,000개의 페이로드는 500만 개의 입력 토큰과 500만 개의 출력 토큰입니다. Bedrock을 통한 Claude Sonnet 4.6 (백만 토큰당 $3/$15) 기준, 사고당 약 $90가 소요되며, 이는 큐 깊이에 따라 선형적으로 증가합니다. 50,000개의 메시지 백로그 (backlog)는 $900의 비용이 듭니다. 또한 바이트 단위로 정확해야 하는 데이터에 대해 비결정론적 (non-deterministic)인 출력을 얻게 되며, 변환 과정을 검토할 방법도 없습니다. 검토할 수 있는 과정 자체가 없기 때문입니다. 5,000개의 독립적인 추측만 존재할 뿐입니다.
대안은 모델을 코드 생성기 (code generator)로 취급하는 것입니다. 비식별화된 샘플 페이로드 (sample payload) 하나와 검증 오류 (validation error)를 모델에 보내고, 결정론적인 변환 (deterministic transformation) 결과를 받은 다음, 일반적인 컴퓨팅 자원에서 해당 변환을 실행하는 방식입니다. 500만 개의 토큰 대신 2,000개의 입력 토큰만 사용하면 됩니다. 사람이 15초 안에 읽을 수 있는 단 하나의 결과물(artifact)이 생성됩니다.
데이터 경계 (data-boundary) 문제에 대해서는: 이 지점에서 Bedrock 추론 (inference)은 제3자 API보다 진정으로 더 낫습니다. AWS는 프롬프트 (prompts)와 완성된 결과물 (completions)이 저장되지 않으며, 모델 제공자와 공유되지 않고, 모델 학습에 사용되지 않는다고 명시하고 있습니다. 또한 트래픽은 AWS 네트워크 내부(또는 구성 시 PrivateLink 엔드포인트)에 머뭅니다. 하지만 이것이 방심해도 된다는 뜻은 아닙니다. Bedrock 호출에 대한 자체 CloudWatch 로깅은 당신이 보낸 내용을 그대로 유지할 것이며, 교차 리전 추론 (cross-region inference)은 페이로드를 귀하의 DPA(데이터 처리 합의)가 적용되는 리전 외부로 이동시킬 수 있습니다. 또한 샘플 페이로드 하나를 다루는 것이 5,000개를 다루는 것보다 컴플라이언스 (compliance) 논의 측면에서 훨씬 수월합니다. 샘플이 VPC를 떠나기 전에 비식별화 (redact)를 수행한다면, 해당 문제는 대부분 해결됩니다.
아키텍처
1. 함정 (The trap). 메시지가 소스 큐 (source queue)의 maxReceiveCount를 초과하면 SQS가 이를 DLQ (Dead Letter Queue)로 이동시킵니다. 특별히 영리한 방식은 아닙니다. 이는 단지 SQS가 제 역할을 수행하는 것뿐입니다.
2. 신호 (The signal). DLQ의 ApproximateNumberOfMessagesVisible에 대한 CloudWatch 알람 (alarm)이 임계값 (threshold)을 초과합니다. 참고할 점은, 알람이 Step Functions를 직접 호출할 수는 없다는 것입니다. 알람 작업 (alarm actions)은 SNS, EC2 작업, Auto Scaling, Systems Manager, 그리고 2023년 12월부터는 Lambda로 제한됩니다. 스테이트 머신 (state machine)에 도달하려면 CloudWatch Alarm State Change 이벤트를 EventBridge 규칙과 매칭시킨 다음, 거기서 Step Functions를 대상으로 지정해야 합니다. 수많은 아키텍처 다이어그램이 알람에서 스테이트 머신으로 직선을 긋고 있지만, 실제로는 그런 경로가 존재하지 않습니다.
3. 오케스트레이터 (The orchestrator). Step Functions는 안전 장치 (safety harness) 역할을 합니다. 이는 재시도 (retries), 타임아웃 (timeouts), 모든 상태 전이 (state transition)에 대한 감사 로그 (audit log), 그리고 결정적으로 승인 게이트 (approval gate)를 관리합니다. AI는 여러분이 제어하는 워크플로 (workflow) 내부의 단일 작업일 뿐이며, IAM 역할 (IAM role)을 가지고 독자적인 의견을 가진 존재가 아닙니다.
4. 생성기 (The generator). Step Functions는 하나의 익명화된 샘플 (redacted sample)과 캡처된 검증 오류 (type mismatch: expected integer at $.order_id, received string)를 Bedrock에 전달하며, 다음과 같은 취지의 프롬프트 (prompt)를 함께 보냅니다: 이 페이로드 (payload)와 이 오류가 주어졌을 때, 페이로드를 예상된 스키마 (schema)에 매핑하는 변환 (transformation)을 생성하라. 이처럼 범위가 좁은 작업에는 보통 Claude Haiku 4.5 ($1/$5 per million)로도 충분합니다. 스키마가 정말 까다로운 경우에는 Sonnet 4.6을 사용하십시오. Claude 3.5 Sonnet은 건너뛰십시오. 이 모델은 2025년 12월에 Bedrock의 Public Extended Access 티어로 이동했으며, 현재 비용은 $6/$30로, 더 낮은 성능임에도 불구하고 현세대 모델 요금의 두 배를 청구합니다.
5. 변이 도구 (The mutator). Lambda 함수가 변환 결과물을 수신하여 DLQ 전체에 대해 10개씩 배치 (batches) 단위로 적용합니다. 이 정도 규모의 백로그 (backlog)라면 15분 제한 시간 내에 충분히 실행됩니다.
SQS의 기본 DLQ 재드라이브 (redrive, StartMessageMoveTask) 기능은 변환 단계에서 도움을 줄 수 없다는 점에 유의하십시오. 이는 메시지를 있는 그대로 (verbatim) 이동시키며, SendMessage를 통해 DLQ에 직접 기록된 메시지가 아닌 재드라이브 정책 (redrive policy)을 통해 도착한 메시지에 대해서만 작동합니다. 검증기 (validator)가 이미 거부한 페이로드를 그대로 다시 재생 (replay)하는 것은 DLQ를 다시 채우는 것뿐입니다. 이것이 바로 변이 도구가 존재하는 이유입니다.
6. 재생 (The replay). 정제된 페이로드는 소스 큐 (source queue)로 돌아가거나, 팬아웃 (fan-out)을 원하는 경우 EventBridge 버스 (bus)로 이동합니다. 어느 쪽이든 상관없습니다. 소스 큐가 더 단순하며 장애 영향 범위 (blast radius) 내의 서비스 수를 하나 줄일 수 있습니다.
7. 영수증 (The receipt). Step Functions는 Amazon Q Developer를 통해 Slack에 게시합니다. 여기서 Amazon Q Developer는 이전의 AWS Chatbot을 의미하며, 2025년 2월에 이름이 변경되었습니다. 내부 API와 IAM 권한은 동일합니다:
5,000개의 Shopify 웹훅 (webhooks) 검증 실패. 근본 원인:
order_id타입 변경. 변환 로직(transformation)을 생성하고, 페이로드 (payloads)를 정제하여 5,000개 중 5,000개를 재처리 (replayed)했습니다. [생성된 코드 보기]
| 구성 요소 (Component) | 비용 (Cost) |
|---|---|
| Bedrock — Sonnet 4.6, ~2K in / ~600 out | $0.015 |
| ... |
EventBridge를 제외하고 소스 큐 (source queue)로 직접 재처리하면 약 $0.018에 가깝습니다. 어느 쪽이든, 월 $0.10인 상시 CloudWatch 알람 비용이 5번의 복구 비용보다 더 많이 듭니다.
인간과 비교하면: 시간당 $100의 총 보상 비용 (loaded rate)을 적용한 시니어 엔지니어의 2~3시간 작업은 $200–$300이며, 여기에 해당 엔지니어가 그날 실제로 출시했어야 할 작업의 가치가 추가됩니다. 따라서 대략 4자릿수(four orders of magnitude)의 차이가 나며, 모든 것을 모델에 보내는 단순한 (naive) 접근 방식은 그 사이인 $90 정도에 위치합니다.
하지만 경제성이 핵심은 아닙니다. 20센트와 300달러의 차이는 다른 작업에 대한 Bedrock 청구서에 비하면 반올림 오차 수준에 불과합니다. 핵심은 4일간의 보존 기간 (retention window)이며, 그 기간은 온콜 (on-call) 엔지니어가 잠을 자고 있는지 여부를 신경 쓰지 않는다는 점입니다.
이 방식이 깨지는 지점
프로덕션 Lambda에서 모델이 생성한 코드를 실행하는 것은 추가 단계가 포함된 원격 코드 실행 (remote code execution)입니다. 만약 이 시스템을 구축한다면, 다음 사항들도 함께 구축해야 합니다.
가능하다면 임의의 Python을 exec()로 실행하지 마세요. 더 강력한 설계는 모델이 제약된 변환 사양 (transformation spec)을 출력하게 하는 것입니다. 즉, JSONata 표현식, JMESPath 매핑, 또는 직접 작성하여 검증할 수 있는 작은 선언적 DSL (declarative DSL)을 출력하고 이를 Lambda가 해석하도록 하는 방식입니다. 이렇게 하면 코드를 평가 (evaluating)하는 것이 아니라 데이터를 파싱 (parsing)하는 것이 되며, 실패 모드 또한 셸 (shell)이 아닌 거부된 사양 (rejected spec)이 됩니다. 만약 실제 Python이 반드시 필요하다면, 실행하기 전에 AST 허용 목록 (allowlist)을 통과하게 하고 전체 프로세스를 별도의 AWS 계정에 배치하십시오.
샌드박스가 실제로 무엇을 보장하는지 이해하십시오. 변이 코드(mutator)의 실행 역할(execution role)을 두 개의 특정 큐 ARN에 대해 sqs:ReceiveMessage, sqs:DeleteMessage, sqs:GetQueueAttributes, sqs:SendMessage로 제한하는 것은 올바르고 필수적인 조치입니다. 하지만 이것이 격리(containment)를 의미하지는 않습니다. sqs:DeleteMessage 권한 하나만으로도 악성 코드는 5,000개의 고객 이벤트를 파괴할 수 있으며, events:PutEvents와 같이 외부로 데이터를 보낼 수 있는 권한을 부여하는 것은 코드가 읽을 수 있는 무엇이든 유출할 수 있는 경로(exfiltration path)를 제공하는 것과 같습니다. 보안 경계(security boundary)는 역할(role)이지, Lambda가 아닙니다.
또한, 인터넷 접속을 차단하려면 함수를 NAT 게이트웨이가 없는 프라이빗 VPC 서브넷(private VPC subnets)에 연결해야 하며, SQS 및 Bedrock을 위한 VPC 엔드포인트(VPC endpoints)를 추가해야 합니다. VPC 설정이 없는 Lambda는 기본적으로 완전한 아웃바운드 인터넷 접속 권한을 가집니다. 단순히 "NAT 게이트웨이가 없다"는 것만으로는 아무런 의미가 없습니다.
돈이 움직이는 모든 작업에 대해서는 사람이 재실행(replay)을 승인하도록 게이트를 설치하십시오. Step Functions의 .waitForTaskToken 콜백 패턴이 바로 이를 위해 만들어졌습니다. 변환된 내용을 생성하여 승인(approve) 및 거부(deny) 버튼과 함께 Slack에 게시하고, 누군가 클릭할 때까지 실행을 대기 상태로 유지하십시오. 생성된 30줄의 Python 코드를 검토하는 데는 15초면 충분합니다. 그 대안은 결제 이벤트에 쓰기 권한을 가진 자율 시스템을 방치하는 것입니다. 재고 수량의 경우 자동 승인하십시오. 하지만 Stripe 지급(payouts)의 경우에는 절대 그렇게 하지 마십시오.
멱등성(Idempotency)은 선택 사항이 아니며, 예전에도 그랬습니다. SQS 표준 큐는 최소 한 번 전달(at-least-once delivery) 방식을 사용하므로, 이러한 아키텍처가 존재하기 전부터 컨슈머(consumer)는 이미 중복 처리를 처리할 수 있어야 했습니다. 재실행(replay)은 멱등성을 건너뛰었을 때 발생하는 결과가 정해진 일정에 맞춰 발생하도록 만들 뿐입니다. 만약 이중 처리된 재고 업데이트가 값을 두 번 차감한다면, 이 아키텍처가 최악의 순간에 그 버그를 찾아내기 전에 먼저 그 문제부터 해결하십시오.
간직할 만한 부분
데드 레터 큐(Dead letter queues)는 좋은 데이터가 죽으러 가는 곳이며, 대개 누군가의 오후 시간을 함께 앗아갑니다. 실패 원인은 거의 항상 사소합니다. 타입 강제 변환(type coercion), 필드 이름 변경, 혹은 빈 문자열이었던 값이 null로 변한 경우 등입니다. 이 작업이 비용이 많이 드는 이유는, 이를 알아차리기 위해 사람이 깨어 있어야 하고, 호출(paged)을 받아야 하며, 맥락(context)을 파악하고 있어야 하기 때문입니다.
diff를 읽고 변환 로직을 작성하는 인지적 작업(cognitive work)을 수천 개의 레코드에 이를 적용하는 기계적 작업(mechanical work)과 분리하는 것이 자동화를 실행 가능하게(tractable) 만드는 핵심입니다. 모델은 단 하나의 샘플에 대해, 단 한 번의 작은 작업만을 수행합니다. 그 이후의 모든 하위 단계(downstream)는 여러분이 작성한 IAM 역할(IAM role)을 사용하는 지루할 정도로 결정론적인 연산(deterministic compute)일 뿐입니다.
이것이 바로 LLM을 프로덕션(production) 환경에 근접하게 배치하기 위한 대략적으로 올바른 형태입니다.
현재 여러분의 팀은 스키마 드리프트(schema drift)를 어떻게 처리하고 있습니까? 엣지에서의 검증(validation at the edge), 관대한 리더(tolerant readers), 아니면 금요일 오후에 돌아가는 스크립트인가요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기