본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 26. 21:12

내가 AI 모델 호출을 서버로 옮기는 이유 — 보안, 성능, 그리고 그 외의 모든 것

요약

AI 기반 도구 Logicvisor를 구축하며 경험한 AI 모델 호출 위치(클라이언트 vs 서버)에 대한 아키텍처 결정 과정을 다룹니다. 보안, 성능, 인증, 캐싱 등 프로덕션 환경에서 서버 사이드 호출이 필수적인 이유를 상세히 분석합니다.

핵심 포인트

  • 클라이언트 사이드 호출은 초기 프로토타입 개발 시 속도가 빠름
  • 보안 및 API 키 노출 방지를 위해 서버 사이드 호출이 권장됨
  • 서버 사이드 구현 시 인증, 캐싱, 프로바이더 추상화가 용이함
  • 프로덕션 환경에서는 안정적인 응답 처리를 위해 서버 아키텍처가 필수적임

내가 알고리즘 코드를 검토하고, 시간 및 공간 복잡도(time and space complexity)를 분석하며, 기술 면접 전에 받고 싶은 피드백을 제공하는 AI 기반 도구인 Logicvisor를 구축할 때, 초기에 근본적인 아키텍처(architectural) 결정을 내려야 했습니다.

AI 호출은 실제로 어디에 위치해야 할까요?

단순해 보이지만, 사실 그렇지 않습니다. 그리고 많은 개발자가 보통 무언가를 가장 빠르게 실행할 수 있는 기본 설정으로 선택하며 너무 성급하게 내리는 결정이라고 생각합니다. 그래서 저는 제가 이 문제를 어떻게 생각했는지, 실제 상황에서 트레이드오프(tradeoffs)가 구체적으로 어떤 모습인지, 그리고 왜 Logicvisor — 그리고 솔직히 말해 제가 작업하는 대부분의 프로덕션(production) 프로젝트 — 에서 그 답이 결코 논쟁의 여지가 없었는지에 대해 이야기해 보고자 합니다.

목차

  • 클라이언트 사이드 (Client-Side) AI 호출의 경우
  • 프로덕션에서 클라이언트 사이드가 무너지는 이유
  • 서버 사이드 (Server-Side) AI 호출의 경우
  • Logicvisor에서 이 결정이 어떻게 적용되었는가
    • 성능 (Performance)
    • 보안 (Security)
    • 인증 (Authentication)
    • 캐싱 (Caching)
    • 프로바이더 추상화 (Provider Abstraction)
    • AI 응답 처리 (AI Response Processing)
  • 그래서 이것이 정말 논쟁의 대상이었을까?

먼저, 우리가 무엇 사이에서 선택하고 있는지부터 정립해 봅시다

앱이 Gemini, Claude, GPT 등 어떤 AI 모델과 통신해야 할 때, 모델 제공업체(provider)로 향하는 해당 HTTP 요청은 반드시 _어딘가_에서 시작되어야 합니다. 당신에게는 두 가지 옵션이 있습니다:

클라이언트 사이드 (Client-side): 브라우저가 AI 제공업체의 API로 직접 호출을 보냅니다.

서버 사이드 (Server-side): 브라우저가 당신의 서버를 호출하고, 당신의 서버가 AI 제공업체를 호출하며, 응답은 당신의 인프라(infrastructure)를 통해 돌아옵니다.

이것이 결정의 전부입니다. 하지만 각 경로의 결과는 매우 깊고 광범위합니다.

클라이언트 사이드 AI 호출의 경우

먼저 반대편의 입장도 공정하게 살펴보겠습니다. 클라이언트 사이드 AI 호출은 단순히 게으름 때문이 아닙니다. 그것을 선택하는 데에는 정당한 이유가 있습니다.

백엔드 오버헤드(Backend overhead) 제로. 프로토타입을 만들거나, MVP(Minimum Viable Product)를 구축하거나, 주말 프로젝트를 위해 무언가를 급히 만들고 있다면, 단순히 AI 호출을 프록시(Proxy)하기 위해 서버를 구축하는 것은 아직 필요하지 않은 마찰을 초래할 수 있습니다. 클라이언트가 API를 호출하고 응답을 받으면 끝입니다.

네트워크 홉(Network hop) 감소. 클라이언트 → AI 제공업체는 직선 경로입니다. 클라이언트 → 귀하의 서버 → AI 제공업체 또한 직선이지만, 더 긴 경로입니다. 추가되는 모든 홉은 잠재적인 지연 시간(Latency)의 원인이 되며, 귀하의 서버가 AI 제공업체와 지리적으로 가깝지 않다면 그 격차는 더욱 커집니다.

개발 중 빠른 반복(Iteration). 프롬프트(Prompt)를 수정하고, 페이지를 새로고침하면 결과를 바로 확인할 수 있습니다. 재배포 주기나 서버 재시작이 필요 없습니다. AI를 활용한 구축의 초기 탐색 단계에서 이러한 피드백 루프는 진정으로 가치가 있습니다.

순수 클라이언트용 도구에는 적합함. 귀하의 데이터베이스를 건드리지 않고, 사용자 세션(User session)이 필요 없으며, 민감한 비즈니스 로직이 없는 경우 — 개인 생산성 도구, 브라우저 확장 프로그램, 내부 유틸리티 등 — 클라이언트 사이드 호출은 완벽하게 적절할 수 있습니다.

이것이 솔직한 장점입니다. 이제 이것이 왜 무너지는지에 대해 말씀드리겠습니다.

프로덕션 환경에서 클라이언트 사이드 AI 호출이 실패하는 이유

API 키 문제

이것이 가장 명백한 문제이지만, 이것이 그토록 심각한지 정확히 짚고 넘어갈 가치가 있습니다.

브라우저에서 API 호출을 할 때, 귀하의 API 키는 반드시 해당 요청에 포함되어야 합니다. 이를 피할 방법은 없습니다. 제공업체가 귀하를 인증해야 하기 때문입니다. 그리고 해당 요청은 브라우저에서 이루어지기 때문에, 개발자 도구(DevTools)를 열거나, 트래픽을 가로채거나, 번들링된 자바스크립트(JavaScript)에서 추출하는 누구에게나 키가 노출될 수 있습니다.

그 결과는 단순히 누군가가 귀하의 키를 _볼 수 있다_는 것에 그치지 않습니다. 그들이 귀하의 비용으로 키를 _사용할 수 있다_는 것이 문제입니다. 귀하가 모르는 사이에 말이죠. AI API 과금은 사용량 기반(Usage-based)이므로, 귀하의 키를 가진 단 한 명의 악의적인 사용자가 모니터링 알림이 울리기도 전에(만약 모니터링 시스템이 있다면 말이죠) 귀하의 계정을 바닥낼 정도의 청구 금액을 발생시킬 수 있습니다.

키 로테이션 (Key rotation)이 도움이 될 수는 있지만, 이는 사후 대응적입니다. 피해는 이미 발생한 뒤인 경우가 많습니다.

귀하의 프롬프트 엔지니어링 (Prompt Engineering)은 공개되어 있습니다

이 부분은 주의를 덜 받는 편이지만, 사람들이 생각하는 것보다 훨씬 더 중요합니다.

귀하가 작성한 프롬프트는 종로종종 실제 제품 가치가 담겨 있는 곳입니다. 만약 귀하가 알고리즘 코드에 대해 AI 리뷰어가 구조화되고 일관되며 고품질의 피드백을 제공하도록 만드는 시스템 프롬프트 (System prompt)를 만드는 데 시간을 들였다면, 그 프롬프트가 바로 제품입니다. 클라이언트 측 (Client-side) 호출은 이를 완전히 노출시킵니다. 경쟁자가 개발자 도구 (DevTools)를 열어 귀하의 시스템 프롬프트를 읽고, 단 몇 시간 만에 귀하의 핵심 기능을 복제할 수 있습니다.

서버에서는 프롬프트가 귀하의 인프라를 절대 벗어나지 않습니다. 클라이언트는 입력을 보내고, 서버는 그 입력을 어떻게 처리할지 결정합니다.

남용에 대한 통제권이 없습니다

클라이언트 측에서는 사용자가 귀하의 AI 엔드포인트 (Endpoint)를 루프(loop)로 계속 때리는 스크립트를 작성하는 것을 막을 방법이 없습니다. 이러한 요청 하나하나가 AI 제공업체에 도달하며 귀하에게 토큰 (Token) 비용을 발생시킵니다. 귀하에게는 속도 제한 (Rate limiting), 요청 검증 (Request validation), 사용자당 할당량 (Quota)을 강제할 방법이 없습니다.

단순히 악의적인 행위자에게 취약한 것뿐만이 아닙니다. 의도치 않은 재요청 (Re-fetching)을 유발하는 귀하의 프론트엔드 (Frontend) 코드 내의 버그가 조용히 API 예산을 소진할 수도 있습니다.

캐싱 (Caching) 불가

AI API 호출은 토큰당 비용이 발생합니다. 만약 여러 사용자가 귀하의 도구에 기능적으로 동일한 코드를 리뷰해 달라고 요청한다면, 왜 동일한 추론 (Inference)에 대해 300번이나 비용을 지불해야 할까요?

클라이언트 측에서는 API 레벨에서 캐싱을 할 수 없습니다. 모든 동일한 요청이 제공업체로 전송되어 지연 시간 (Latency)을 유발하고 토큰 비용을 발생시킵니다. 서버에서는 응답을 지능적으로 캐싱할 수 있습니다. 입력을 해시 (Hash)하고, 캐시 레이어 (Cache layer)를 확인한 뒤, 캐시된 결과를 반환하면 됩니다. 비용은 한 번만 지불하면 됩니다.

운영 환경에서 눈을 가리고 비행하는 격입니다

서버 측 인프라가 없다면, 귀하의 AI 레이어가 실제로 어떻게 사용되고 있는지에 대한 중앙 집중식 뷰 (Centralized view)를 가질 수 없습니다. 어떤 프롬프트가 성능이 좋은가요? 어떤 입력이 쓰레기 같은 응답을 생성하고 있나요? 어떤 사용자가 속도 제한에 걸리고 있나요? 귀하의 토큰 지출은 어디로 흘러가고 있나요?

클라이언트 측 (Client-side) AI 호출은 이 모든 것을 추측에 의존하고 있다는 것을 의미합니다. 로그 (Logs), 모니터링 (Monitoring), 그리고 관측성 (Observability) — 즉, 프로덕션 시스템 (Production system)의 기본적인 계측 (Instrumentation) — 을 위해서는 서버가 루프 안에 포함되어야 합니다.

서버 측 AI 호출을 선택해야 하는 이유

이러한 맥락을 바탕으로, AI 호출이 서버에서 실행될 때 실제로 얻을 수 있는 이점은 다음과 같습니다.

API 키가 클라이언트에 절대 노출되지 않음

귀하의 키는 서버의 환경 변수 (Environment variable)에 저장됩니다. 클라이언트는 이에 대해 전혀 알지 못하며, 접근할 수도 없고, 추출할 수도 없습니다. 이는 실제 사용자를 대상으로 하는 모든 애플리케이션이 갖춰야 할 최소한의 수용 가능한 보안 태세 (Security posture)입니다.

속도 제한 (Rate Limiting) 제어 가능

특정 사용자가 주어진 시간 범위 내에서 얼마나 많은 요청을 보낼 수 있는지 귀하가 결정합니다. 계정별, IP별, 세션별 등 귀하의 위협 모델 (Threat model)이 요구하는 방식에 따라 이를 강제할 수 있습니다. 남용 (Abuse)은 귀하에게 발생하는 통제 불능의 사건이 아니라, 귀하가 관리할 수 있는 대상이 됩니다.

export async function enforceAIRateLimit(
    userId: string,
    request?: NextRequest
...

응답 캐싱 (Response Caching) 가능

동일하거나 거의 동일한 입력에 대해 캐싱된 결과를 반환할 수 있어, 지연 시간 (Latency)과 비용을 모두 절감할 수 있습니다. 여러 사용자가 유사한 정렬 알고리즘 구현을 제출할 수 있는 Logicvisor와 같은 도구의 경우, 반복되는 추론 (Inference)에 대한 비용 절감 효과는 빠르게 누적됩니다.

// 해싱하기 전에 코드를 정형화된 형태 (Canonical form)로 정규화하여
// 서식 차이로 인해 캐시 미스 (Cache miss)가 발생하지 않도록 합니다.
const canonicalCode = await canonicalizeCodeAST(solution, preferred_language);
...

프롬프트에 서버 측 컨텍스트 (Context) 주입 가능

이 부분은 아키텍처 측면에서 매우 흥미로운 지점입니다. 클라이언트는 코드, 질문, 요청과 같은 가공되지 않은 입력 (Raw input)을 보냅니다. 하지만 귀하의 서버는 클라이언트가 모르는 정보들을 알고 있습니다: 사용자가 누구인지, 사용자의 이력이 어떠한지, 어떤 티어 (Tier)를 사용 중인지, 어떤 언어를 선택했는지, 그리고 이미 어떤 결과들을 받았는지 말입니다. 이 모든 컨텍스트는 인프라 (Infrastructure)를 벗어나기
_전(before)_에 프롬프트에 주입될 수 있습니다.

클라이언트는 해당 컨텍스트에 전혀 접근할 수 없기 때문에, 이를 조작하거나 속일 수 없습니다.

완전한 관측 가능성 (Full Observability)

모든 요청은 로그로 기록됩니다. 모든 응답은 추적 가능합니다. 토큰 사용량을 모니터링하고, 비정상적인 동작을 식별하며, 어떤 프롬프트가 최상의 결과를 생성하는지 추적하고, 실제 데이터를 통해 운영 환경 (Production)의 문제를 디버깅할 수 있습니다. 이것이 바로 운영 환경에서 소프트웨어를 실행한다는 것의 의미입니다.

await updateAPIUsageAnalytics("/api/internal", true, responseTime, aiTokensUsed, estimatedCost, user.id, {
  modelName: modelUsed,
  modelProvider,
...

BFF 논거 — 여러 번 대신 한 번의 라운드 트립 (Round Trip)

여기에는 단순히 AI 호출을 넘어선 더 넓은 아키텍처 측면의 이점이 있습니다. 중간에 서버가 없다면 클라이언트가 모든 것을 직접 오케스트레이션 (Orchestrate)해야 합니다. AI를 호출하고, 응답을 기다리고, 그다음 데이터베이스를 호출하고, 다시 기다린 다음, UI를 업데이트해야 합니다. 이 과정 하나하나가 사용자에게는 눈에 보이는 지연 시간으로 나타납니다.

BFF (Backend for Frontend) 패턴을 사용하면 클라이언트는 단 한 번의 요청만 보냅니다. 서버가 AI 호출을 처리하고, 응답을 가공하며, 필요한 경우 데이터베이스를 쿼리하고, 비즈니스 로직을 적용한 뒤, 최종적으로 해결된 단일 페이로드 (Payload)를 반환합니다. 사용자는 폭포수처럼 이어지는 여러 번의 통신 대신, 단 한 번의 네트워크 라운드 트립 (Round Trip)을 경험하게 됩니다.

아키텍처 다이어그램

구현 세부 사항으로 들어가기 전에, 아키텍처의 차이를 시각화하면 다음과 같습니다.

클라이언트 측 AI 호출 — 문제점

Client-side architecture diagram

브라우저 (Browser) → AI 제공업체 (AI Provider) 직접 호출 · 전송 과정에서 API 키 노출

서버 측 AI 호출 — 해결책

Server-side architecture diagram

브라우저 (Browser) → API 라우트 (API Route) → [캐시 (Cache) · 속도 제한기 (Rate Limiter) · AI 제공업체 (AI Provider) · DB] → 브라우저 (Browser)

Logicvisor에서 적용된 방식

구체적인 사례를 들어보겠습니다. AI 레이어를 서버로 옮기는 것이 실제로 어떻게 구현되었는지 보여드리겠습니다.

성능(Performance) — 워터폴(Waterfall) 현상의 붕괴

Logicvisor는 백엔드에 Supabase를 사용합니다. 앱의 여러 지점에서 저는 여러 테이블로부터 데이터를 가져오고, AI 리뷰를 실행한 다음, 페이지에 필요한 모든 것을 한 번에 반환해야 합니다.

만약 이 모든 과정이 클라이언트(Client)에서 일어난다면 다음과 같은 상황을 마주하게 될 것입니다: 사용자 컨텍스트를 위해 Supabase 호출 → 대기 → AI 제공업체(AI Provider) 호출 → 대기 → 과거 리뷰를 위해 다시 Supabase 호출 → 대기 → 렌더링. 이러한 각각의 대기 시간은 사용자에게 그대로 노출되며, 체인 중간에 무언가 실패할 수 있는 기회가 됩니다.

서버에서는 이러한 호출들이 서로, 그리고 데이터와 매우 가까운 곳에서 발생합니다. AI 호출, 데이터베이스 쿼리, 그리고 필요한 모든 변환 작업이 서버 측(Server-side)에서 모두 해결되며, 클라이언트는 깔끔한 응답 하나를 받게 됩니다. 사용자는 일련의 UI 깜빡임이 아니라 단 한 번의 로딩 상태만을 경험하게 됩니다.

연산(Compute) 문제도 있습니다. 대규모 AI 응답을 파싱(Parsing)하고 처리하는 작업 — 즉, JSON 펜스(JSON fences)를 제거하고, 구조를 검증하며, 출력을 UI가 기대하는 형식으로 변환하는 작업 — 은 브라우저(Browser)에 적합하지 않은 작업입니다. 브라우저는 설계 단계부터 메모리와 CPU 사용이 제한되어 있으며, DOM, 다른 탭, 그리고 사용자가 열어놓은 모든 것과 자원을 경쟁해야 합니다. 서버는 그러한 제약이 없습니다.

보안(Security) — 기능으로서의 API 난독화

호출을 서버로 옮긴다는 것은 클라이언트의 모든 상호작용이 여러분의 자체 API 범위 내로 한정됨을 의미합니다. 클라이언트는 여러분의 엔드포인트(Endpoint)를 호출하고 응답을 받으면 끝입니다. 서버가 해당 요청을 내부적으로 어떻게 처리하는지 — 어떤 외부 API를 호출하는지, 그 호출에 어떤 키(Key)를 실어 보내는지, 혹은 응답이 어떻게 구성되었는지 — 에 대해서는 전혀 알 수 없습니다.

이것은 은닉을 통한 보안(Security through obscurity)이 아닙니다. 이 난독화는 아키텍처의 구조적 특성입니다. 이론적으로 OS 수준의 네트워크 도구가 이 중 일부를 노출할 수는 있겠지만, 공격자가 여러분의 스택(Stack)을 침해하기 위해 수행해야 할 작업의 난이도를 극적으로 높여 놓은 것입니다.

여러분의 시스템 프롬프트(System prompts) — 즉, 제품의 도메인 지식(Domain knowledge)과 검토 방법론(Review methodology)을 실제로 인코딩하는 부분 — 은 절대 서버를 떠나지 않습니다. 이것은 결코 작은 일이 아닙니다.

인증 (Authentication) — 상태 비저장(Stateless) 및 원활한(Seamless) 방식

루프 내에 서버가 존재하면 적절한 인증 아키텍처(Auth architecture)를 극적으로 더 깔끔하게 만들 수 있습니다. 저는 HttpOnly 쿠키를 설정하고, 서명된 JWT 토큰을 첨부하며, 클라이언트가 직접 제어하지 않고도 참여할 수 있는 상태 비저장(Stateless) 인증 및 인가(Authorization) 시스템을 구축할 수 있었습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0