
Pub/Sub 안에서 Gemini가 작동한다? AI Inference SMT로 메시지를 실시간 가공해 보았다
요약
Google Cloud Pub/Sub의 새로운 기능인 AI Inference SMT를 통해 별도의 코드 작성 없이 메시지 흐름 내에서 Gemini 모델을 직접 호출하고 추론 결과를 병합하는 방법을 소개합니다. Cloud Run 등을 활용한 중계 서버 구축 없이 YAML 설정만으로 실시간 데이터 가공이 가능합니다.
핵심 포인트
- AI Inference SMT를 통해 Pub/Sub 내에서 직접 모델 추론 가능
- Cloud Run/Functions 없이 Gemini 등 MaaS 모델 호출 및 결과 병합
- Rate Limit 대응 및 재시도 로직을 Pub/Sub이 자동으로 관리
- 토픽 또는 구독 측 설정으로 데이터 변환 범위 유연하게 조절 가능
「모델을 호출하고, 결과를 붙이기만 하면 되는」 Cloud Run / Cloud Run functions를 작성해 본 적이 있나요? 본 기사에서는 그것을 단 한 줄도 쓰지 않고, 리뷰를 publish하면 Gemini가 감정을 분류하여 결과가 포함된 메시지가 하류(downstream)로 전달되는 구성을 만듭니다. 직접 준비하는 것은 구독(subscription)에 등록할 YAML 파일 한 장뿐입니다.
Pub/Sub으로 흘러 들어오는 메시지에 AI로 한 단계의 공정을 더하고 싶다. 예를 들어 리뷰의 감정 분석을 한 뒤 BigQuery에 넣고 싶다, 같은 경우입니다.
지금까지 이런 요구사항이 있으면, Pub/Sub에서 메시지를 받아 Gemini Enterprise Agent Platform (구 Vertex AI)를 호출하고, 결과를 다른 토픽(topic)에 다시 publish하는 중계 역할을 하는 Cloud Run functions나 Cloud Run 등을 하나 끼워 넣는 것이 정석이었습니다. 하는 일은 「모델을 호출하고 결과를 붙이는 것」뿐인데도, Rate Limit(속도 제한)을 고려한 Retry(재시도) 처리를 작성하고, Deploy(배포) 파이프라인을 정비하며, 모니터링도 구축해야 합니다. 본질적인 처리는 몇 줄뿐이고, 나머지 수백 줄은 전부 그 주변 코드입니다. 은근히 번거로우면서도 어떤 프로젝트에나 하나쯤은 있는 구성이라고 생각합니다.
Google Cloud Next '26 타이밍에 GA(General Availability)가 된 Pub/Sub의 「AI Inference SMT」는 이 중계 역할을 Pub/Sub 자체에 흡수시켜 버리는 기능입니다. 메시지가 Pub/Sub을 통과하는 도중에 Gemini 등의 모델이 호출되어, 추론(inference) 결과가 부여된 상태로 하류에 전달됩니다.
SMT (Single Message Transforms)는 Pub/Sub 내에서 메시지를 한 건씩 변환할 수 있는 기능군입니다. 제1탄으로 JavaScript의 UDF (User Defined Function, 사용자 정의 함수)가 먼저 GA되어, 필드 마스킹(masking)이나 포맷 변환 정도의 가벼운 처리라면 외부 서비스 없이 Pub/Sub 안에서 완결할 수 있게 되어 있었습니다.
포인트는 변환을 토픽 측과 구독(subscription) 측 중 어디에든 설정할 수 있다는 점입니다. 토픽 측의 SMT는 메시지가 영속화(persistence)되기 전에 적용되므로 모든 구독자(subscriber)에게 적용되고, 구독 측의 SMT는 전달 직전에 적용되므로 특정 구독자만을 위해 변환할 수 있습니다. 「전체적으로는 원본 데이터를 유지하면서, 외부 연동을 하는 구독만 PII(개인정보)를 마스킹한다」와 같은 구분 사용이 가능한 설계입니다.
AI Inference SMT는 이 SMT 패밀리의 새로운 얼굴로, 변환 처리로서 「모델 추론 (model inference)」을 선택할 수 있게 되었다고 이해하는 것이 빠릅니다.
설정은 토픽 또는 구독에 대해 수행합니다. 사용할 수 있는 모델은 크게 두 계통이 있습니다.
하나는 MaaS (Model-as-a-Service) 계통으로, Gemini나 Claude 등 Model Garden을 통해 제공되는 모델을 배포 관리 없이 그대로 지정할 수 있습니다. 다른 하나는 자체 배포 계통으로, Gemini Enterprise Agent Platform (구 Vertex AI. 이름이 길군요)의 엔드포인트(endpoint)에 배포한 독자 모델이나 오픈 모델을 지정합니다.
메시지가 SMT를 통과하면, Pub/Sub이 백그라운드에서 모델로 요청을 보내고, 돌아온 추론 결과를 원래 메시지에 병합(merge)하여 하류로 흘려보냅니다. 호출의 오케스트레이션(orchestration)이나 흐름 제어 (모델 엔드포인트에 과부하가 걸리지 않도록 하는 Rate Adjustment)는 Pub/Sub 측에서 수행하므로, 애플리케이션 측에 Retry 로직을 작성할 필요가 없습니다.
개요만으로는 감이 잘 안 오실 것 같아, 제가 생각한 유스케이스를 몇 가지 들어보겠습니다.
가장 이해하기 쉬운 정석적인 사례입니다. 고객 지원 문의나 앱 리뷰가 Pub/Sub으로 흘러 들어오는 구성에서, SMT로 「카테고리」, 「감정 점수」, 「긴급도」 등을 부여한 뒤 BigQuery 구독을 통해 직접 테이블에 저장합니다. 지금까지 분류만을 위해 존재했던 중간 단계의 Function과 Retry 처리가 통째로 사라집니다. Export Subscription과 조합하면, publish 이후 노코드(no-code)로 「분류된 데이터가 BigQuery에 쌓이는」 파이프라인이 된다는 점이 매력적입니다.
폼의 자유 기술(free-text), 이메일 본문, 채팅 로그와 같은 비구조화 텍스트(unstructured text)에서 날짜, 금액, 제품명 등을 추출하여 JSON으로 만드는 패턴입니다. 정규 표현식(Regular Expression)으로 해결하려 들면 지옥을 맛보게 됩니다(그리고 반년 뒤, 아무도 읽을 수 없는 정규 표현식만이 남게 되죠). 프롬프트(Prompt)로 "이 스키마에 맞춰 추출해줘"라고 지시해 두면, 하류(downstream)에는 구조화된 메시지가 전달됩니다. 하류의 각 컨슈머(Consumer)가 각각 추출 처리를 가질 필요가 없어지므로, 토픽 측 SMT에 배치할 가치가 특히 커집니다.
문서나 상품 정보의 업데이트 이벤트를 받아 벡터 DB(Vector DB)에 반영하는 구성입니다. 기존에는 "업데이트 이벤트 → 임베딩(Embedding) 생성 Function → 벡터 DB 쓰기" 방식이었으나, 임베딩 생성을 SMT로 옮기면 구독자(Subscriber)는 받은 벡터를 쓰기만 하면 됩니다. 검색 인덱스의 신선도가 중요한 서비스에서 파이프라인을 한 단계 얕게 만들 수 있습니다.
모니터링 시스템이 뱉어내는 방대한 알람 JSON을 사람이 읽을 수 있는 2~3줄의 요약본으로 변환한 뒤, Slack 알림용 구독으로 흘려보내는 패턴입니다. 새벽 2시에 불려 나가 잠결에 수백 줄의 JSON과 씨름해 본 경험이 있는 사람이라면 이 가치를 바로 이해할 것입니다. 원래 메시지는 별도의 구독을 통해 원본 그대로 저장할 수 있으므로, "사람용은 요약, 기계용은 원문"이라는 분기를 SMT 배치만으로 표현할 수 있습니다.
글로벌로 전개되는 서비스에서 각 언어의 사용자 게시물을 일단 영어(또는 일본어)로 번역한 뒤 분석 파이프라인으로 흘려보내는 케이스입니다. 번역이라는 "모든 메시지에 일률적으로 적용하고 싶은 가벼운 변환"은 SMT의 성격과 궁합이 좋습니다.
반대로 적합하지 않은 것은 복잡한 조건 분기를 동반하는 처리, 여러 메시지를 묶어서 처리하고 싶은 처리, 추론 결과에 따라 후속의 무거운 처리를 나누어 호출하고 싶은 케이스입니다. 그런 작업은 순순히 Cloud Run이나 Dataflow 등으로 처리하는 것이 좋습니다. SMT는 어디까지나 "1개 메시지에 1개 변환"을 위한 도구입니다.
그런 이유로, 서두에 언급했던 "리뷰의 감성 분석을 한 뒤 BigQuery에 넣고 싶다"를 실제로 해보겠습니다. 이번에는 우선 구조 확인을 위해, SMT가 포함된 구독에서 pull하여 결과를 확인하는 단계까지 진행합니다. BigQuery 구독으로의 연결은 동일한 방식으로 교체할 수 있습니다.
AI Inference SMT는 모델 호출 시 기본적으로 Pub/Sub의 서비스 에이전트(service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com)를 사용합니다. 이 계정에 Vertex AI Service Agent 역할을 부여해 둡니다.
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com" \
--role="roles/aiplatform.serviceAgent" \
...
변환 정의는 YAML로 작성하며, 토픽 또는 구독 생성 시 전달합니다. 우선은 정석대로 작성한 버전입니다.
# ai-smt.yaml
- aiInference:
endpoint: projects/PROJECT_ID/locations/global/publishers/google/models/gemini-3.5-flash
...
엔드포인트(Endpoint)에는 Model Garden의 모델을 전체 경로(full path)로 지정합니다. 직접 배포한 모델이라면 projects/.../endpoints/ENDPOINT 형식입니다.
사용 가능한 모델은 공식 문서의 동작 확인이 완료된 MaaS 모델 목록에 있는 것들입니다. 이번에는 그중에서 최신 모델인 gemini-3.5-flash를 사용합니다.
gcloud pubsub topics create reviews
gcloud pubsub subscriptions create reviews-sub \
--ack-deadline=600 \
...
구독(Subscription)이 생성되었으므로, 다음은 리뷰의 publish입니다. 이 부분이 첫 번째 난관이었습니다. AI Inference SMT는 메시지 데이터를 모델로 보내는 요청 JSON(Request JSON) 그대로 전송합니다. Pub/Sub은 내용물을 해석해주지 않습니다. 즉, Gemini 기반 모델의 경우, 메시지가 Chat Completions API 형식으로 되어 있어야 합니다.
gcloud pubsub topics publish reviews --message=$'{
"model": "google/gemini-3.5-flash",
"messages": [{
...
"어, 그럼 publisher 측에서 프롬프트 형식으로 가공해야 하나?"라고 생각하실 겁니다. 기존 시스템이 가공되지 않은 리뷰 JSON({"review_id": ..., "text": ...})
를 흘려보내고 있는 경우, publisher 측을 수정하는 것은 본말전도입니다.
여기서 빛을 발하는 것이 SMT는 여러 개의 체인(Chain)을 구성할 수 있다는 사양입니다. AI Inference SMT의 전단에 JavaScript UDF를 배치하여, 가공되지 않은 리뷰 JSON을 프롬프트 형식으로 감싸줍니다.
# review-smt.yaml
- javascriptUdf:
code: |
...
방금 만든 구독에 이 정의를 반영합니다.
gcloud pubsub subscriptions update reviews-sub \
--message-transforms-file=review-smt.yaml
참고로, 이 JavaScript를 위해 별도의 배포처를 준비할 필요는 없습니다. update 명령어를 실행하는 시점에 YAML의 내용이 통째로 구독 설정으로서 Cloud에 반영되며, Pub/Sub의 관리형(Managed) 환경 내에서 실행됩니다. Cloud Functions와 같은 런타임 관리도 필요하지 않습니다.
gcloud pubsub topics publish reviews --message='{"review_id": "r-001", "text": "배송은 빨랐지만, 상자에 구멍이 나 있었다. 내용물도 손상되어 있었다."}'
# pull은 빈 결과가 나올 수 있으므로 몇 번 재시도(Retry)
for i in {1..5}; do gcloud pubsub subscriptions pull reviews-sub --auto-ack --limit=10; sleep 5; done
나왔습니다. 가공하면 이런 형태가 됩니다.
{
"original_message": {
"model": "google/gemini-3.5-flash",
...
"배송은 빠르지만 상자에 구멍, 내용물도 손상"에 대해 negative.
타당한 판정입니다. 중계용 Function은 한 줄도 작성하지 않았습니다. 재시도(Retry)도 429 대응도 모두 Pub/Sub 내부에서 알아서 처리해 줍니다. 나머지는 BigQuery 구독에 연결하기만 하면 "publish하는 것만으로 분류된 리뷰가 테이블에 쌓이는" 파이프라인이 완성됩니다.
여러 메시지를 흘려보내 보니, 이론상으로는 알 수 없었던 것들이 몇 가지 보였습니다.
"JSON 형식으로만 응답해줘"라고 지시해도, 출력이 코드 펜스(```json)로 감싸져서 돌아오는 경우가 있거나 모델이나 요청에 따라 달라지는 등, 출력 형식이 완전히 일치하지는 않습니다. 이대로 BigQuery에 넣으면 하류(Downstream)의 파싱(Parsing) 과정에서 오류가 발생합니다. AI Inference SMT의 후단에 UDF를 하나 더 두어, model_output에서 JSON을 추출하고 검증 및 가공하는 후처리 체인은 "있으면 편리한" 정도가 아니라 실질적으로 필수적이라고 느꼈습니다.
동일한 리뷰 문장을 두 번 흘려보냈더니, 점수가 1.0과 0.99로 돌아왔습니다. 당연한 결과라면 당연하지만, "재시도로 인해 재추론(Re-inference)되면 결과가 달라질 수 있다"는 멱등성(Idempotency)에 대한 주의사항이 실제로 눈앞에서 벌어지니 무게감이 다릅니다.
도착한 메시지의 original_message는 UDF 적용 후의 요청 JSON입니다. 즉, 가공되지 않은 리뷰에 있던 review_id는 UDF가 message.data를 덮어쓰는 시점에 사라집니다. 하류에서 원본 레코드와 대조할 수 없는 것은 실무에서 치명적이므로, UDF 내에서 message.attributes
에 ID를 대피시키는(message.attributes.review_id = review.review_id;를 삽입하는) 것이 정답이었습니다. 데이터를 덮어쓰는 변환에서는, 남기고 싶은 메타데이터를 속성(attributes) 쪽으로 옮겨두는 것이 좋아 보입니다.
UDF 체인 버전으로 업데이트하기 전에 발행(publish)했던 메시지가, 업데이트 후의 배포에서 새로운 체인을 통과하여 나왔습니다 (UDF가 예상하지 못한 JSON이었기 때문에 「리뷰: undefined」라는 프롬프트로 추론되었습니다). 변환이 실행되는 시점은 「배포 시」이며, 구독(subscription)에 쌓여 있는 메시지도 정의 변경 후에는 새로운 정의에 따라 처리된다는 동작의 증거입니다. 운영 환경에서 변환을 교체할 때는, 정체된 메시지의 존재를 염두에 두어야 합니다.
실제로 도입하기 전에 짚고 넘어가야 할 점이 몇 가지 있습니다.
먼저, 메시지 1개당 1번의 추론(inference) 요청이 발생하며, 배칭(batching)은 이루어지지 않습니다. 대량의 메시지가 흐르는 토픽(topic)에 무턱대고 설정하면, 그대로 모델 호출 횟수, 즉 청구 금액이 치솟게 됩니다. 초당 1,000개의 메시지가 흐르는 토픽에 분위기에 휩쓸려 설정하는 것은 금물입니다. 요금은 모델 이용료에 더해, 추론 오케스트레이션(inference orchestration) 인프라 비용이 별도로 발생하는 이중 구조이므로, 유량이 많은 토픽에서는 사전에 시뮬레이션을 해두는 것이 안전합니다.
추론에는 60초의 제한이 있습니다. 이를 초과하면 배포 시도가 타임아웃(timeout)되어 재시도(retry)되며, 재시도를 다 마치지 못하면 (설정되어 있는 경우) 데드 레터 토픽(dead letter topic)으로 전송됩니다. 여기서 주의해야 할 점은 재시도 시의 재추론입니다. 동일한 메시지에 대해 추론이 여러 번 실행될 수 있으므로, LLM의 출력이 매번 같지 않을 수 있다는 전제하에 하류(downstream) 시스템을 멱등(idempotent)하게 설계해야 합니다.
AI Inference SMT는 「추론하여 결과를 붙이기만 하는」 중계 컴포넌트를 Pub/Sub에 내장할 수 있는 기능입니다. 분류, 추출, 요약, 임베딩(embedding) 생성과 같이 메시지 1개로 완결되는 가벼운 추론이라면, 파이프라인을 한 단계 더 단순하게 만들 수 있습니다.
반면, 메시징 계층은 본래 가볍고 동작을 예측하기 쉬워야 하는 곳입니다. 그곳에 LLM이라는 「느리고, 가끔 엉뚱한 소리를 하는」 동료 같은 컴포넌트를 들여오는 것이므로, 레이턴시(latency), 비용, 멱등성 설계는 기존보다 한 단계 더 주의를 기울일 필요가 있습니다. 편리함의 대가로 책임이 메시지 기반(messaging infrastructure)으로 넘어간다는 구도는 의식해 두어야 합니다.
실제로 만져보면서 UDF와의 체인을 통해 전처리(pre-processing) 및 후처리(post-processing)까지 구성할 수 있다는 것을 알게 된 것은 수확이었습니다. 「발행자(publisher)는 그대로 두고, 중간에서 전부 처리한다」는 것이 현실적으로 성립합니다. 앞으로 「추론하여 결과를 붙이기만 하면 되는」 요구사항이 온다면, 함수(Function)를 작성하기 전에 먼저 SMT로 해결할 수 없는지 고민하게 될 것 같습니다.
※ 2026년 7월 시점의 정보입니다. 요금 및 사양은 변경될 가능성이 있으므로, 최신 공식 문서를 확인해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기