Articlestack.convex.dev·2026년 7월 5일·0

How to Build Async AI Apps with Convex and TypeScript

Quick Summary

이 글은 LLM 기반 TypeScript 앱에서 작업을 단일 HTTP 요청이나 서버 메모리에 묶어 두면 실패하기 쉬우며, Convex의 반응형 데이터베이스·스케줄링·타입 안전성을 통해 비동기 AI 워크플로를 내구성 있게 구성하는 방식을 설명한다.

How to Build Async AI Apps with Convex and TypeScript 관련 대표 이미지

🖼️ 인포그래픽

How to Build Async AI Apps with Convex and TypeScript 내용을 설명하는 본문 이미지

🖼️ 4컷 인포그래픽

How to Build Async AI Apps with Convex and TypeScript 내용을 설명하는 본문 이미지

💡 한 줄 요약

이 글은 LLM 기반 TypeScript 앱에서 작업을 단일 HTTP 요청이나 서버 메모리에 묶어 두면 실패하기 쉬우며, Convex의 반응형 데이터베이스·스케줄링·타입 안전성을 통해 비동기 AI 워크플로를 내구성 있게 구성하는 방식을 설명한다.

📌 핵심 요약

  • LLM 작업은 응답까지 수십 초 이상 걸리고 도구 호출·재시도·요약 같은 후속 단계가 이어질 수 있어, 사용자의 브라우저 탭이나 하나의 HTTP 연결 수명에 맞추기 어렵다.
  • 일반적인 Promise 기반 비동기는 한 프로세스가 살아 있는 동안 완료되는 작업에는 적합하지만, 클라이언트 연결 해제·서버 재시작·배포·장시간 대기까지 견뎌야 하는 장기 비동기 작업에는 부족하다.
  • 스트리밍 HTTP 요청만으로 토큰을 브라우저에 흘려보내는 방식은 사용자 이탈, 네트워크 단절, 로드밸런서 타임아웃, 서버 재활용, 모델 API 오류가 발생하면 작업 자체가 사라지는 구조적 약점을 가진다.
  • Convex는 데이터베이스에 상태를 먼저 기록하고, 액션이 모델 호출을 수행한 뒤, mutation으로 결과를 지속적으로 저장하며, query 구독을 통해 클라이언트가 자동으로 최신 상태를 받는 반응형 백엔드 모델을 제시한다.
  • 예시 워크플로는 사용자의 프롬프트와 빈 assistant 메시지를 트랜잭션으로 저장한 뒤 모델 생성 액션을 예약하고, 문장이나 절 단위로 결과를 데이터베이스에 반영하여 UI에는 스트리밍처럼 보이지만 저장소 중심으로 재개 가능한 흐름을 만든다.

🧩 주요 포인트

  1. LLM 작업은 응답까지 수십 초 이상 걸리고 도구 호출·재시도·요약 같은 후속 단계가 이어질 수 있어, 사용자의 브라우저 탭이나 하나의 HTTP 연결 수명에 맞추기 어렵다.
  2. 일반적인 Promise 기반 비동기는 한 프로세스가 살아 있는 동안 완료되는 작업에는 적합하지만, 클라이언트 연결 해제·서버 재시작·배포·장시간 대기까지 견뎌야 하는 장기 비동기 작업에는 부족하다.
  3. 스트리밍 HTTP 요청만으로 토큰을 브라우저에 흘려보내는 방식은 사용자 이탈, 네트워크 단절, 로드밸런서 타임아웃, 서버 재활용, 모델 API 오류가 발생하면 작업 자체가 사라지는 구조적 약점을 가진다.
  4. Convex는 데이터베이스에 상태를 먼저 기록하고, 액션이 모델 호출을 수행한 뒤, mutation으로 결과를 지속적으로 저장하며, query 구독을 통해 클라이언트가 자동으로 최신 상태를 받는 반응형 백엔드 모델을 제시한다.
  5. 예시 워크플로는 사용자의 프롬프트와 빈 assistant 메시지를 트랜잭션으로 저장한 뒤 모델 생성 액션을 예약하고, 문장이나 절 단위로 결과를 데이터베이스에 반영하여 UI에는 스트리밍처럼 보이지만 저장소 중심으로 재개 가능한 흐름을 만든다.

🧠 상세 정리

1. LLM 작업은 요청보다 오래 산다

글의 출발점은 대부분의 TypeScript 백엔드가 전통적인 request-response 모델을 기준으로 설계되었지만, LLM 워크로드는 그 모델에 잘 맞지 않는다는 문제의식이다. 사용자가 프롬프트를 보내고 모델이 응답하는 데 30초가 걸리는 동안 사용자가 탭을 닫을 수 있고, 그 뒤에도 에이전트가 도구 호출이나 요약 단계를 계속 수행해야 할 수 있다. 이 작업이 하나의 HTTP 요청 안에만 있으면 연결이 끊기는 순간 사라지고, 한 서버의 메모리에만 있으면 서버가 재시작될 때 사라진다. 따라서 요청은 작업을 시작하는 트리거로는 적절하지만, 작업을 담아두는 컨테이너로 삼기에는 취약하다는 점이 핵심이다.

2. Promise 기반 비동기와 장기 비동기는 다르다

글은 TypeScript 개발자에게 익숙한 Promise 기반 비동기와 AI 앱에 필요한 장기 비동기를 구분한다. Promise와 await는 하나의 프로세스 안에서 이벤트 루프를 양보하고, 런타임이 나중에 이어서 실행해 주는 방식이다. 데이터베이스 조회나 짧은 fetch처럼 단일 함수 호출 안에 들어오는 작업에는 이 모델이 충분하다. 그러나 장기 실행 작업은 클라이언트 연결 해제, 서버 재시작, 배포, 경우에 따라 사람이나 외부 이벤트를 기다리는 수 주 단위의 중단까지 견뎌야 한다. 이런 경우에는 신뢰할 수 있는 단일 프로세스 메모리가 없으므로 계산 상태를 내구성 있는 저장소에 두고, 스케줄러가 나중에 다시 진행할 수 있어야 한다.

3. 스트리밍 HTTP 요청만으로는 실패 지점이 너무 많다

많은 AI 튜토리얼은 서버에서 브라우저로 스트리밍 응답을 열고 모델 토큰을 그대로 흘려보내는 패턴을 기본값처럼 다룬다. 그러나 글은 이 방식이 사용자 브라우저 종료, 이동 중 네트워크 단절, 로드밸런서 타임아웃, 서버 프로세스 재활용, 모델의 rate limit이나 SDK 예외 같은 평범한 운영 상황에서 쉽게 깨진다고 지적한다. 문제는 사용자에게 보이는 작업의 유일한 존재 장소가 요청 핸들러 내부라는 데 있다. 생성할 가치가 있는 작업이라면 대체로 끝낼 가치도 있으므로, 연결이 끊겨도 살아남는 저장 공간과 재개 가능한 구조가 필요하다. 핵심은 연결을 필수 지지대가 아니라 선택적인 관찰 경로로 바꾸는 것이다.

4. 클라우드는 하나의 서버가 아니라 여러 노드의 환경이다

프로덕션 클라우드 인프라는 기본적으로 멀티 노드 환경이기 때문에, 하나의 서버가 모든 상태를 들고 있다고 가정하는 설계는 확장 순간부터 깨지기 쉽다. 재접속 후 이어받기 기능도 시작한 노드와 다시 연결된 노드가 다를 수 있으므로, 상태가 특정 프로세스 메모리에 고정되어 있으면 제대로 동작하지 않는다. 스티키 세션은 잠시 문제를 가려 줄 수 있지만 인스턴스가 교체되는 순간 큰 실패로 드러날 수 있다. 그래서 글은 네트워크 호출보다 persistence가 먼저 와야 한다고 말한다. 프롬프트, 부분 출력, 중간 도구 결과가 생성되는 즉시 데이터베이스에 저장되면 어떤 노드든 작업을 다시 이어받고 어떤 클라이언트든 진행 상황을 다시 구독할 수 있다.

5. 서버는 작업 장소가 아니라 스케줄·저널·재개 지점이 되어야 한다

글은 서버리스 함수 실행 시간 제한, 중간 프록시의 long-poll 종료, 원래 브라우저 탭이 사라진 뒤 websocket 메시지를 전달할 곳이 없어지는 상황을 예외가 아니라 배포된 앱의 정상적인 운영 조건으로 본다. 이런 실패가 겹치면 단순히 연결만 유지하려는 접근은 점점 복잡해지고 불안정해진다. 따라서 서버를 작업이 계속 머무는 장소로 보는 대신, 작업을 예약하고 기록하고 다시 시작하게 만드는 장소로 바라봐야 한다. 실제 진행 상태는 모든 노드가 읽고 쓸 수 있는 저장소에 있어야 한다. 이 관점에서는 어떤 노드가 계산을 진전시켜도 되고, 어떤 클라이언트가 붙어도 같은 상태를 관찰할 수 있다.

6. Dropbox의 비디오 트랜스코딩 비유

글은 Dropbox의 비디오 트랜스코딩을 AI 작업 구조를 이해하기 위한 비유로 사용한다. 전체 비디오를 하나의 요청 안에서 변환하려 하면 사용자가 연결을 끊는 순간 작업이 위태로워지지만, HLS처럼 청크 단위로 나누면 각 청크를 독립적으로 변환하고 저장하고 재개할 수 있다. AI 작업도 같은 형태를 따른다. 긴 작업을 여러 단계로 쪼개고, 각 단계의 산출물을 저장하며, 재개 가능성을 연결이 아니라 저장소의 속성으로 만들어야 한다. 이 비유는 LLM 호출을 단순한 긴 API 요청으로 보기보다, 중간 상태가 계속 기록되는 워크플로로 설계해야 한다는 글의 주장을 뒷받침한다.

7. Convex의 반응형 백엔드 모델

Convex가 제시하는 모델은 데이터베이스 query를 실시간 구독으로 바꾸는 반응형 백엔드다. Convex query가 특정 행 범위를 읽고, 이후 mutation이 그 범위 안의 행을 수정하면 해당 query를 구독 중인 모든 클라이언트가 websocket을 통해 자동으로 갱신된 결과를 받는다. 이 구조에서는 폴링 루프, 수동 캐시 무효화, 별도 webhook 연결 같은 접착 코드가 줄어든다. LLM worker는 부분 출력을 데이터베이스에 쓰기만 하면 되고, 대화를 보고 있는 클라이언트는 같은 query 구독을 통해 업데이트를 받는다. worker는 누가 구독 중인지 알 필요가 없고, 클라이언트도 변경 여부를 따로 물어볼 필요가 없다.

8. mutation, query, action과 타입 안전성의 역할

Convex는 backend function을 mutation과 query로 나누며, 각각 다른 보장을 제공한다. mutation은 읽기와 쓰기를 수행하는 직렬화 가능한 트랜잭션이므로 같은 행을 다루는 여러 mutation이 일관된 순서의 상태를 본다. query는 순수 읽기라서 시스템이 안전하게 다시 실행하고 캐시할 수 있으며, 이 특성 때문에 반응형 query subscription으로 클라이언트에 업데이트를 밀어줄 수 있다. LLM 호출 같은 장기 실행 작업과 외부 부작용은 action에 놓이고, action은 모델 제공자에 호출한 뒤 mutation을 예약해 결과를 커밋한다. 여기에 TypeScript 스키마와 런타임 검증, 함수 인자 validator, React hook까지 이어지는 타입 흐름이 더해져 메시지, 도구 호출, run 상태처럼 계속 변화하는 구조가 서버와 클라이언트 사이에서 어긋나는 일을 줄인다.

9. 프롬프트를 먼저 저장하고 모델을 나중에 호출하는 워크플로

글의 구현 패턴은 프롬프트를 먼저 저장하고 모델 호출을 그다음에 수행하는 것이다. 클라이언트는 mutation을 호출해 사용자의 메시지를 messages 테이블에 쓰고, 동시에 빈 assistant 메시지를 placeholder로 만든 뒤 generation을 처리할 action을 예약한다. 이 mutation은 짧고 동기적이며 직렬화 가능한 트랜잭션이므로 사용자 메시지와 assistant placeholder가 원자적으로 함께 저장된다. 클라이언트가 mutation 반환 직후 연결을 끊더라도 예약된 action은 계속 실행되고 assistant 메시지는 나중에 채워진다. 따라서 다음 페이지 로드 때 고아 상태를 정리하는 별도 reconciliation 단계가 필요하지 않다.

10. 데이터베이스에 쓰는 스트리밍과 다중 에이전트 조정

모델 생성 action은 대화 history를 읽고 모델에서 나온 chunk를 buffer에 모은 뒤, 적절한 시점마다 appendChunk mutation으로 assistant row에 반영한다. 글은 토큰마다 쓰는 방식이 직관적으로 보일 수 있지만 대부분 옳지 않으며, 문장이나 절 단위로 flush하면 사용자에게는 문자 단위 스트리밍처럼 보이면서도 쓰기 양을 합리적으로 유지할 수 있다고 설명한다. 같은 모델은 multi-agent 구성에도 적용된다. 두 action이 같은 runs 테이블에 쓰거나 planner agent가 queue 테이블에 task를 만들고 worker agent가 가져가는 식으로 조정할 수 있다. 별도 message bus 없이 데이터베이스가 조정 지점이 되고, 반응형 계층 덕분에 UI를 포함한 모든 참여자가 같은 공유 상태를 보게 된다.

🧾 핵심 주장 / 시사점

  • AI 앱에서 중요한 경계는 HTTP 연결이 아니라 저장된 상태다. 요청은 작업을 시작할 수 있지만, 작업의 생존성과 재개 가능성은 데이터베이스와 스케줄러가 책임져야 한다.
  • 실시간 UI를 만들기 위해 반드시 HTTP streaming에 작업의 수명을 묶을 필요는 없다. 데이터베이스에 중간 결과를 저장하고 query 구독으로 갱신을 전달하면 사용자 경험은 스트리밍처럼 보이면서도 시스템은 더 견고해진다.
  • 장기 실행 LLM 워크플로에서는 타입 안전성이 단순한 개발 편의가 아니라 상태 드리프트를 막는 운영 안정성 장치가 된다. 메시지, 도구 호출, 실행 상태가 계속 진화할수록 스키마부터 클라이언트까지 이어지는 타입 보장이 중요해진다.

✅ 액션 아이템

  • LLM 호출을 단일 HTTP 응답 흐름에서 분리해 요청과 탭 종료와 무관한 상태 중심 파이프라인으로 전환한다.
  • Convex의 상태 기록→액션 호출→mutation 저장→query 구독 순환을 적용해 장기 비동기 작업을 재개 가능한 구조로 정한다.
  • 도구 호출·재시도·요약까지 포함한 후속 단계 결과를 문장 또는 절 단위로 지속 저장해 UI 반응을 중단 없이 복원한다.

❓ 열린 질문

  • 사용자 이탈·네트워크 단절·로드밸런서 타임아웃 발생 시 작업 상태를 어느 시점부터 재개할지 어떤 기준이 필요한가?
  • 문장 단위와 절 단위 저장 중 어느 단위가 복구 신뢰성과 저장소 부하를 함께 만족시키는가?
  • 액션 예약 후 배포나 서버 재시작이 일어날 때 동일 작업의 중복 실행을 막을 상태 동기화 규칙은 어떻게 설계할 것인가?

관련 문서

공통 태그와 주제 흐름을 기준으로 같이 보면 좋은 문서를 이어서 제안합니다.