Skip to main content

16장. 백엔드 구조 설계: 동기 호출, 비동기 작업, 큐

LLM 기능을 데모로 붙일 때는 모든 것이 간단해 보인다. 웹 요청이 들어오면 서버가 모델을 한 번 호출하고, 응답을 받아서 그대로 돌려주면 끝이다. 처음 며칠은 꽤 잘 된다. 그런데 사용자가 늘고, 입력이 길어지고, 파일이 붙고, 재시도가 섞이고, 외부 API까지 연결되기 시작하면 분위기가 갑자기 달라진다. 응답은 들쑥날쑥해지고, 워커는 오래 붙잡히고, 타임아웃은 자주 터지며, 비용은 조용히 올라간다.

기존 CRUD 백엔드에서는 요청 하나가 비교적 짧고 예측 가능하게 끝나는 경우가 많다. 데이터베이스를 읽고 쓰고, 검증하고, 응답하면 된다. 하지만 LLM 호출은 다르다. 느릴 수 있고, 느린 정도도 일정하지 않다. 출력 길이에 따라 시간이 달라지고, 모델 상태나 공급자 상황에 따라 지연이 튀기도 한다. 같은 API라도 어떤 요청은 2초, 어떤 요청은 25초가 걸린다. 여기에 파일 파싱, 벡터화, 검색, 후처리, 다른 시스템 연동까지 붙으면 한 번의 사용자 요청이 사실상 작은 워크플로가 된다.

그래서 이 장의 핵심은 단순하다. LLM 호출을 일반 CRUD 요청처럼 다루지 말고, 실시간 응답이 필요한 일과 오래 걸리는 일을 분리해서 설계하자는 것이다. 그리고 그 분리의 중심에 큐가 있다.

4부. 프로덕션 아키텍처로 확장하기 / 16장. 백엔드 구조 설계: 동기 호출, 비동기 작업, 큐

CRUD 감각으로 만들면 왜 금방 무너질까

문제는 모델이 느리다는 사실 자체가 아니다. 더 큰 문제는 느린 데다가 변동성이 크고, 비용이 있으며, 실패 양상도 기존 API와 다르다는 점이다.

사용자가 채팅창에서 질문을 보낼 때는 3초도 길게 느껴진다. 그런데 같은 시스템에서 긴 보고서를 생성하거나 수십 개 문서를 분류하는 작업은 몇십 초에서 몇 분이 걸릴 수 있다. 둘을 같은 HTTP 요청-응답 모델로 처리하면 어떤 일이 생길까. 웹 서버 연결은 오래 유지되고, 프록시 타임아웃은 점점 가까워지고, 서버 스레드나 이벤트 루프 자원은 오래 점유된다. 트래픽이 몰리는 순간, 짧게 끝나야 할 요청까지 함께 밀리기 시작한다.

여기서 흔히 생기는 오해가 있다. "서버 성능을 올리면 되지 않나?" 물론 어느 정도는 맞다. 하지만 서버를 더 키우는 것만으로는 구조 문제를 해결할 수 없다. 오래 걸리는 작업이 짧은 작업과 같은 통로를 사용하면, 비싼 자동차 전용도로에 트럭과 굴착기와 이삿짐 차량을 한꺼번에 올려놓는 셈이다. 결국 막힌다.

LLM 백엔드가 무너지는 전형적인 신호는 다음과 같다.

  • 사용자가 간단한 질문을 해도 응답 속도가 갑자기 불안정해진다.

  • 파일 처리나 긴 생성 작업이 들어오면 API 서버의 리소스가 급격히 점유된다.

  • 재시도가 겹치면서 같은 작업이 중복 실행되고 비용이 새어 나간다.

  • 외부 모델 API의 일시적 실패가 전체 사용자 경험을 흔든다.

  • 특정 테넌트나 특정 대형 작업이 전체 시스템의 처리 순서를 망가뜨린다.

이런 증상은 대개 "모든 것을 동기 호출로 처리했다"는 구조적 선택에서 출발한다.

먼저 작업을 두 종류로 나눠라

LLM 백엔드 설계의 첫 번째 질문은 모델을 무엇으로 부를지가 아니다. 그보다 먼저, 이 작업이 동기 처리인지 비동기 처리인지부터 구분해야 한다.

동기 호출은 사용자가 화면에서 기다리는 동안 바로 결과를 받는 방식이다. 요청을 보내고 같은 연결 안에서 응답을 받는다. 채팅, 짧은 요약, 제목 생성, 짧은 분류 같은 기능이 여기에 잘 맞는다.

비동기 작업은 요청을 접수한 뒤 나중에 결과를 돌려주는 방식이다. 서버는 즉시 "작업을 받았다"는 사실만 응답하고, 실제 처리는 뒤에서 따로 진행한다. 긴 문서 분석, 대량 데이터 처리, 첨부파일 파이프라인, 보고서 생성, 배치 작업이 여기에 속한다.

이 구분은 기술 선택이 아니라 제품 선택이다. 사용자에게 어떤 기다림을 요구할 것인지, 어느 순간 결과를 보여줄 것인지, 실패하면 어떻게 설명할 것인지가 함께 결정된다.

작업 분류를 할 때는 다음 질문이 유용하다.

  • 사용자가 화면 앞에서 지금 바로 결과를 기다리는가?

  • 평균 시간보다 최악의 시간이 훨씬 길어질 가능성이 있는가?

  • 같은 요청 안에서 파일 처리, 검색, 여러 번의 모델 호출, 외부 시스템 연동이 이어지는가?

  • 실패 시 자동 재시도가 필요한가?

  • 처리량이 한순간에 몰릴 수 있는가?

  • 결과를 이메일, 알림, 폴링, 웹훅 등 다른 방식으로 전달해도 되는가?

앞의 질문 중 여러 개에 "그렇다"가 붙는다면, 대개 비동기 작업으로 빼는 편이 안전하다.

동기 호출이 잘 맞는 경우

동기 호출은 여전히 중요하다. 사용자는 대화를 하고 싶지, 작업 접수증만 보고 싶어 하지 않기 때문이다. 문제는 동기 호출을 없애는 것이 아니라, 동기 호출이 잘 맞는 범위를 정확히 지키는 데 있다.

동기 처리에 어울리는 기능은 대체로 짧고, 단일 단계이며, 결과가 즉시 가치가 있는 것들이다. 예를 들어 고객 문의를 한 문장으로 분류하거나, 짧은 텍스트를 매끄럽게 고쳐주거나, 채팅 답변을 스트리밍으로 보여주는 경우다. 이런 기능은 모델 응답이 다소 느리더라도 사용자가 "기다리는 과정" 자체를 받아들일 수 있다.

동기 호출을 설계할 때는 몇 가지 원칙이 있다.

첫째, 시간 예산을 정해야 한다. 무한히 기다리게 두지 말고, 이 기능은 예를 들어 8초 안에 끝나야 한다는 기준이 필요하다. 그 안에 결과가 나오지 않으면 부분 결과를 보여주거나, 비동기 전환을 제안하거나, 나중에 확인할 수 있게 해야 한다.

둘째, 가능하면 스트리밍을 활용한다. 모델이 긴 답변을 만들더라도 첫 토큰을 빨리 보여주면 체감 속도가 크게 좋아진다. 사용자 입장에서는 6초 뒤에 한 번에 텍스트가 나타나는 것보다, 1초 뒤에 말문을 여는 쪽이 훨씬 자연스럽다.

셋째, 동기 경로에는 복잡한 파이프라인을 너무 많이 넣지 않는 편이 좋다. 간단한 질문 하나에 검색, 재랭킹, 툴 호출, 후처리, 외부 시스템 저장, 감사 로그, 알림 발송을 모두 엮기 시작하면, 겉으로는 실시간 기능처럼 보여도 속은 이미 비동기 워크플로에 가까워진다.

넷째, 취소 가능성을 고려해야 한다. 사용자가 브라우저를 닫거나 다음 질문을 보냈다면 이전 작업을 끝까지 붙잡고 있을 이유가 없는 경우가 많다. 동기 처리에서는 특히 "사용자가 더 이상 기다리지 않는 작업"을 빨리 정리할 수 있어야 한다.

비동기 작업은 '나중에 해도 되는 일'이 아니라 '나중에 해야 안전한 일'이다

많은 팀이 비동기 작업을 부가 기능처럼 생각한다. 당장 급하지 않은 일을 뒤로 보내는 용도라고 보는 것이다. 하지만 LLM 서비스에서는 조금 다르다. 비동기는 편의 기능이 아니라 안정성을 위한 기본 구조에 가깝다.

예를 들어 사용자가 200페이지 분량의 PDF를 업로드해 요약 보고서를 요청했다고 해보자. 여기에는 파일 저장, 텍스트 추출, 청크 분할, 품질 검사, 여러 번의 모델 호출, 결과 병합, 최종 포맷 생성 같은 단계가 들어갈 수 있다. 이걸 하나의 HTTP 요청 안에서 끝내겠다는 생각 자체가 위험하다. 시간이 오래 걸릴 뿐 아니라 중간 실패 가능성도 높고, 재시도 전략도 필요하다.

이럴 때 올바른 흐름은 대개 이렇다. API 서버는 요청을 받자마자 작업 레코드를 만들고, 큐에 메시지를 넣고, 사용자에게 job_id를 돌려준다. 실제 처리는 워커가 가져가서 실행한다. 결과는 데이터베이스나 객체 저장소에 보관하고, 사용자는 폴링이나 웹소켓, 서버 전송 이벤트(SSE), 웹훅, 이메일 알림 등으로 상태를 확인한다.

이렇게 하면 얻는 이점이 크다. 웹 서버는 빠르게 응답하고, 작업 부하는 큐가 흡수하며, 워커 수를 따로 조절할 수 있다. 그리고 무엇보다 실패를 더 침착하게 다룰 수 있다. 한 단계에서 실패하면 재시도하면 되고, 여러 번 실패하면 사람에게 보이거나 별도 대기열로 옮기면 된다. 사용자도 "서버가 멈췄다"가 아니라 "작업 처리 중"이라는 상태를 이해할 수 있다.

큐는 왜 중요한가

큐는 작업을 그냥 줄 세우는 도구가 아니다. LLM 백엔드에서 큐는 속도 차이를 흡수하고, 폭주를 완화하고, 실패를 다룰 수 있게 만드는 완충 장치다.

사용자 요청은 보통 들쭉날쭉하게 들어온다. 반면 LLM 공급자 API는 초당 요청 수 제한이 있을 수 있고, 내부 워커 수도 제한적이다. 이 둘을 직접 맞부딪치게 하면 급격한 트래픽 변화가 그대로 장애로 이어진다. 큐는 그 사이에서 "잠깐 여기 모여 있어"라고 말해주는 대기 공간이다.

큐가 제공하는 핵심 가치는 세 가지다.

  • 버퍼링: 갑작스러운 요청 급증을 즉시 처리하지 못하더라도 시스템이 무너지지 않게 한다.

  • 분리: API 서버와 실제 작업 수행자를 느슨하게 연결해 각각 독립적으로 확장할 수 있게 한다.

  • 제어: 우선순위, 재시도, 지연 실행, 실패 격리 같은 운영 정책을 적용할 수 있게 한다.

특히 LLM 서비스에서는 비용 제어와 큐가 깊게 연결된다. 어떤 시점에 무제한으로 작업을 병렬 실행하는 것은 성능 문제가 아니라 곧바로 비용 문제이기도 하다. 큐를 두면 지금 처리할 양과 나중에 처리할 양을 나눌 수 있고, 작업 종류별로 동시성 제한도 걸 수 있다.

추천하는 기본 구조

실무에서 시작하기 좋은 구조는 생각보다 복잡하지 않다. 중요한 것은 역할 분리다. 각 부품이 무엇을 책임지는지 명확해야 한다.

기본 구조는 보통 다음 요소로 구성된다.

  • API 서버: 사용자 요청을 받고 검증하며, 동기 처리 또는 비동기 등록 여부를 결정한다.

  • 작업 저장소: job_id, 상태, 요청자, 입력 참조, 결과 참조, 에러, 시도 횟수 같은 메타데이터를 저장한다.

  • 큐: 실행 대기 중인 작업을 관리한다.

  • 워커: 큐에서 작업을 가져와 실제 모델 호출, 파일 처리, 외부 API 연동을 수행한다.

  • 결과 저장소: 긴 결과물, 생성 파일, 중간 산출물을 보관한다.

  • 상태 알림 채널: 폴링 API, 웹소켓, SSE, 웹훅, 이메일 등으로 사용자에게 진행 상태를 전달한다.

  • 관측 시스템: 로그, 메트릭, 트레이스를 수집해 병목과 실패 지점을 파악한다.

이 구조의 장점은 각 부품을 따로 키울 수 있다는 점이다. 채팅 요청이 많아지면 API 서버와 실시간 워커를 늘리면 되고, 문서 처리 요청이 많아지면 비동기 워커를 더 늘리면 된다. 둘이 서로 발목을 잡지 않게 만드는 것이 핵심이다.

작업 상태 모델을 먼저 설계하라

비동기 아키텍처에서 정말 중요한 것은 큐 자체보다도 작업 상태 모델이다. 상태 모델이 허술하면 사용자는 지금 무슨 일이 벌어지는지 알 수 없고, 운영자는 어디서 실패했는지 추적하기 어렵다.

최소한 다음 정도의 상태는 갖추는 것이 좋다.

  • queued: 작업이 등록되었고 아직 실행 전이다.

  • running: 워커가 가져가 처리 중이다.

  • retrying: 일시적 실패로 재시도 대기 중이다.

  • succeeded: 결과가 정상적으로 생성되었다.

  • failed: 더 이상 자동 복구하지 않고 실패로 확정되었다.

  • cancelled: 사용자가 취소했거나 시스템 정책상 중단되었다.

상태만 있는 것으로는 부족하다. 왜 그 상태가 되었는지도 남겨야 한다. 예를 들어 started_at, finished_at, attempt_count, last_error_code, last_error_message, tenant_id, priority, idempotency_key, input_location, output_location 같은 필드는 나중에 큰 차이를 만든다.

특히 idempotency_key는 꼭 기억할 만하다. 이 키는 같은 요청이 중복 접수되더라도 결과를 한 번만 만들게 해주는 장치다. 네트워크가 불안정하면 클라이언트는 같은 요청을 다시 보낼 수 있다. 결제 시스템에서 중복 결제가 무섭듯, LLM 서비스에서는 중복 생성과 중복 비용이 무섭다. 같은 작업을 두 번 하지 않게 만드는 설계는 생각보다 중요하다.

워커는 똑똑하기보다 단순해야 한다

처음에는 워커 하나에 모든 일을 몰아넣고 싶어진다. 파일도 읽고, 문서도 자르고, 검색도 하고, 모델도 부르고, 결과도 저장하고, 알림도 보내고 싶다. 하지만 그렇게 만들수록 문제는 복잡해진다. 실패 지점이 늘고, 재시도 기준이 흐려지며, 어느 단계가 병목인지도 보이지 않게 된다.

좋은 워커는 만능이 아니라 단순한 책임을 가진다. 예를 들어 문서 추출 워커, 임베딩 워커, 생성 워커, 후처리 워커처럼 성격이 다른 작업을 나누면 운영이 훨씬 쉬워진다. 물론 무조건 잘게 쪼개라는 뜻은 아니다. 중요한 것은 같은 성질의 일을 같은 종류의 워커가 맡게 하는 것이다.

LLM 호출 워커는 특히 다음 원칙을 지키는 편이 좋다.

  • 입력과 출력을 명확히 정의한다.

  • 한 번 실패했다고 바로 포기하지 않되, 무한 재시도는 하지 않는다.

  • 타임아웃을 작업 종류별로 다르게 둔다.

  • 외부 API 호출 수와 토큰 사용량을 기록한다.

  • 중간 상태를 남겨서 재개 가능성을 만든다.

  • 같은 작업을 두 번 실행해도 부작용이 최소화되게 설계한다.

워커는 영화 속 천재 해결사가 아니라, 묵묵하게 같은 규칙을 지키는 공장 라인에 가깝다. 실무에서는 이런 쪽이 훨씬 강하다.

동기와 비동기를 섞는 하이브리드 패턴

현실의 서비스는 보통 둘 중 하나만 쓰지 않는다. 사용자는 빠른 반응을 원하지만, 시스템은 안정적으로 돌아야 한다. 그래서 하이브리드 패턴이 자주 쓰인다.

대표적인 방식은 "우선 짧게 시도하고, 길어지면 비동기로 넘기는" 구조다. 예를 들어 5초 안에 답변이 완성되면 즉시 반환하고, 그렇지 않으면 작업을 백그라운드로 전환해 나중에 결과를 보여준다. 사용자 입장에서는 가능한 한 빠르게 응답받고, 시스템 입장에서는 긴 꼬리를 동기 경로 밖으로 밀어낼 수 있다.

또 다른 방식은 "즉시 초안, 나중에 정제본"이다. 먼저 빠른 모델이나 짧은 프롬프트로 초안을 만들고, 필요하면 뒤에서 더 정교한 처리로 최종본을 생성한다. 예를 들어 회의록 초안은 곧바로 보여주고, 액션 아이템 정리나 태깅, CRM 반영은 비동기 작업으로 돌릴 수 있다.

이 패턴은 특히 제품 경험을 좋게 만든다. 사용자는 바로 뭔가를 받고, 시스템은 조용히 나머지를 정리한다. 사람도 일을 이렇게 한다. 일단 초안을 보내고, 정리는 저녁에 한다. 백엔드도 가끔은 인간적인 방식이 통한다.

큐를 도입하면 꼭 함께 설계해야 할 것들

큐를 넣는다고 모든 문제가 자동으로 사라지지는 않는다. 오히려 큐가 생기면 새롭게 신경 써야 할 운영 포인트가 생긴다.

첫째는 우선순위다. 모든 작업을 하나의 큐에 넣으면 긴 작업 몇 개가 짧은 작업 수백 개를 막을 수 있다. "5초 내 응답이 필요한 작업"과 "10분 걸려도 되는 작업"은 같은 줄에 세우지 않는 편이 좋다. 실시간성에 가까운 작업, 일반 작업, 배치 작업을 분리하는 것만으로도 체감 품질이 크게 좋아진다.

둘째는 공정성이다. 특정 고객 한 명이 대량 작업을 던졌다고 해서 전체 시스템이 그 고객의 일을 대신해 주는 구조가 되면 곤란하다. 테넌트별 동시 실행 수를 제한하거나, 고객별 큐를 분리하거나, 스케줄링 가중치를 두는 방식이 필요하다.

셋째는 백프레셔다. 이 말은 시스템이 감당할 수 있는 속도보다 더 많은 작업이 들어올 때, 어딘가에서 "지금은 천천히"라고 말하는 능력이다. 큐 길이가 일정 수준을 넘으면 새 작업 접수를 제한하거나, 더 가벼운 모델로 전환하거나, 무료 플랜의 처리 속도를 늦추는 등의 정책을 둘 수 있다.

넷째는 가시성이다. 큐 길이, 평균 대기 시간, 작업 성공률, 재시도 횟수, 실패율, 테넌트별 사용량이 보이지 않으면 운영은 거의 감에 의존하게 된다. 특히 "지금 느린 이유가 모델 때문인지, 큐 적체 때문인지, 특정 작업 종류 때문인지"를 바로 알 수 있어야 한다.

실패를 예외가 아니라 상태로 다뤄라

LLM 백엔드에서 실패는 드문 사건이 아니다. 외부 모델 API는 일시적으로 느려질 수 있고, 레이트 리밋이 걸릴 수 있고, 입력 데이터가 깨져 있을 수도 있다. 중요한 것은 실패를 없애는 것이 아니라, 실패가 시스템 전체를 흔들지 못하게 하는 것이다.

실패를 다룰 때는 먼저 유형을 나눠야 한다. 네트워크 오류나 일시적 공급자 문제는 재시도가 가능하다. 반면 입력 형식 오류, 권한 부족, 지원하지 않는 파일 등은 같은 방식으로 다시 시도해도 소용이 없다. 이 둘을 구분하지 않으면 재시도는 복구 수단이 아니라 비용 증폭기가 된다.

실전에서 챙겨야 할 포인트는 다음과 같다.

  • 재시도 가능 오류와 불가능 오류를 구분한다.

  • 재시도에는 간격을 둔다. 즉시 연속 재시도는 대개 상황을 악화시킨다.

  • 일정 횟수 이상 실패한 작업은 별도 실패 대기열로 보낸다.

  • 중복 실행이 발생해도 같은 결과가 유지되도록 만든다.

  • 사용자에게는 기술적 에러보다 행동 가능한 상태를 보여준다.

예를 들어 사용자에게 "502 upstream error"를 보여주는 것보다 "문서 분석이 지연되고 있습니다. 잠시 후 자동으로 다시 시도합니다."가 훨씬 낫다. 운영자에게는 정확한 에러 코드가 필요하지만, 사용자에게는 이해 가능한 현재 상태가 필요하다.

데이터 저장 전략도 함께 생각해야 한다

동기 호출만 하던 시절에는 결과를 바로 응답하고 끝낼 수 있었다. 하지만 비동기 작업이 생기면 입력과 출력, 중간 결과를 어디에 어떻게 둘지 결정해야 한다.

특히 긴 문서나 생성 결과를 모두 작업 테이블 한 칸에 넣으려 하면 금방 불편해진다. 보통은 메타데이터는 데이터베이스에, 큰 입력 파일과 결과 파일은 객체 저장소에 두고, 작업 레코드에는 그 위치만 기록하는 방식이 깔끔하다.

이 접근은 몇 가지 장점이 있다. 큰 데이터 때문에 트랜잭션이 무거워지지 않고, 결과를 다시 다운로드하거나 다른 시스템으로 넘기기 쉬우며, 만료 정책도 따로 관리할 수 있다. 나중에 비용 절감을 위해 오래된 산출물을 아카이브하거나 삭제하는 것도 편해진다.

또 하나 중요한 점은 입력 스냅샷이다. 비동기 작업은 나중에 실행되므로, 작업 시점의 입력을 안정적으로 보존해야 한다. 사용자가 원문을 수정해버렸는데도 예전 요청을 재현해야 하는 경우가 있기 때문이다. "당시 어떤 프롬프트와 어떤 입력으로 이 결과가 나왔는가"를 복원할 수 있어야 운영이 편해진다.

사용자 경험은 백엔드 구조에서 결정된다

동기와 비동기를 나누는 일은 단지 서버 구조를 정하는 것이 아니다. 사용자 경험을 설계하는 일이기도 하다.

동기 처리에서는 사용자가 기다리는 시간이 핵심이다. 그래서 스트리밍, 부분 응답, 로딩 메시지, 취소 버튼이 중요해진다. 반대로 비동기 처리에서는 기다리는 방식이 핵심이다. 작업이 얼마나 남았는지, 끝나면 어디서 볼 수 있는지, 실패하면 다시 시도할 수 있는지, 그 과정이 사용자에게 분명해야 한다.

좋은 비동기 UX는 상태가 명확하다. 예를 들어 "업로드 완료 → 텍스트 추출 중 → 분석 중 → 결과 정리 중 → 완료"처럼 사람이 이해할 수 있는 단계가 보이면, 기다림이 훨씬 덜 답답하다. 반대로 3분 동안 스피너만 돌고 있으면 사용자는 페이지를 새로고침하고, 다시 눌러보고, 결국 같은 작업을 두 번 생성하게 된다.

즉, 중복 작업은 때로 사용자의 실수가 아니라 상태 전달 실패의 결과다.

작게 시작하되, 갈아엎지 않게 시작하라

처음부터 거대한 분산 시스템을 만들 필요는 없다. 하지만 나중에 반드시 분리해야 할 것을 초반부터 한 덩어리로 고정해두면, 성장 단계에서 큰 비용을 치르게 된다.

초기 단계에서는 다음과 같은 순서가 현실적이다.

  • 1단계: 짧은 기능만 동기 처리로 제공한다. 시간 예산과 타임아웃을 명확히 둔다.

  • 2단계: 파일 처리나 긴 생성 작업을 job 기반 비동기로 분리한다.

  • 3단계: 큐를 작업 성격별로 나누고, 워커를 별도 확장 가능하게 만든다.

  • 4단계: 우선순위, 테넌트별 제한, 재시도 정책, 알림 체계를 운영 수준으로 다듬는다.

핵심은 "지금은 작으니까 아무렇게나"가 아니라 "지금은 작으니 구조를 단순하게, 하지만 경계는 분명하게"다. API 서버, 작업 저장, 큐, 워커의 경계만 명확해도 이후 확장은 훨씬 쉬워진다.

실전 의사결정 기준

끝으로, 어떤 기능을 어디에 둘지 헷갈릴 때 쓸 수 있는 간단한 판단 기준을 정리해보자.

사용자가 화면 앞에서 바로 결과를 기다리고, 평균적으로 짧게 끝나며, 실패 시 다시 눌러도 큰 문제가 없다면 동기 호출이 맞다. 반대로 처리 시간이 길거나 들쭉날쭉하고, 재시도와 상태 관리가 필요하며, 비용과 병렬 처리량을 통제해야 한다면 비동기 작업으로 보내야 한다.

그리고 다음 셋 중 하나라도 해당되면 큐를 진지하게 고려하는 편이 좋다.

  • 요청이 순간적으로 몰릴 수 있다.

  • 외부 LLM API의 속도와 제한이 일정하지 않다.

  • 같은 종류의 작업을 대량으로 처리해야 한다.

LLM 서비스 백엔드는 빠른 답변을 만드는 기술이면서 동시에, 느린 일을 안전하게 미루는 기술이다. 둘 중 하나만 잘해서는 오래 버티기 어렵다.

마무리

프로덕션 단계의 LLM 서비스는 더 이상 "모델을 어떻게 부를까"의 문제가 아니다. 어떤 요청은 즉시 처리하고, 어떤 작업은 뒤로 보내며, 그 둘이 서로를 방해하지 않게 만드는 시스템 설계의 문제다. 동기 호출은 사용자와의 대화 창을 지키고, 비동기 작업은 긴 일을 시스템 밖으로 넘기지 않고 안에서 질서 있게 처리하게 만든다. 큐는 그 사이를 버티게 하는 완충재다.

정리하면 이 장의 메시지는 하나다. LLM 호출은 느리고 비싸며 흔들릴 수 있다. 그러니 기존 CRUD 백엔드처럼 한 줄 요청-응답으로만 다루지 말자. 실시간성과 장기 작업을 분리하고, 큐를 중심으로 흐름을 설계해야 서비스가 오래 간다.

다음 장에서는 이렇게 분리된 구조 위에서, 사용자별 대화 흐름과 문맥을 어떻게 유지할지 살펴본다. LLM 서비스가 실제 서비스답게 보이기 시작하는 지점은 바로 그곳이다.

책 목록

실전 LLM 서비스 개발: ChatGPT·Claude·Gemini API로 만드는 AI 웹 서비스의 설계부터 운영까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기