단일 .NET 솔루션으로 멀티 테넌트 (Multi-tenant) AI 에이전트 플랫폼 구축하기: 아키텍처 결정 사항 및 값진 교훈
요약
.NET을 사용하여 멀티 테넌트 AI 에이전트 플랫폼을 구축할 때 필요한 아키텍처 설계 원칙을 다룹니다. 테넌트 격리, 비용 관리, 권한 제어를 위한 단일 강제 집행 지점 구축의 중요성을 강조합니다.
핵심 포인트
- 단일 핸들러를 통한 에이전트 생성 경로의 강제 집행 및 유효성 검사
- 모델 호출 전 지출 상한선(Spend ceilings)을 체크하는 경계 설정
- 단일 규칙 테이블을 이용한 플랜 권한(Plan entitlements) 관리
- EF Core 전역 쿼리 필터를 활용한 안전한 테넌트 격리 구현
엔터프라이즈 아키텍트 (Enterprise Architect)로서 20년 이상 근무한 후, AI를 제대로 배우기 위해 지난 8개월 동안 밤과 주말을 반납하며 AI 에이전트 플랫폼을 구축했습니다. 이 제품은 문서화된 절차(SOP, 정책, 런북 (Runbooks))를 API 엔드포인트로 노출되는 작동 가능한 에이전트로 변환하며, 테넌트별 격리 (Per-tenant isolation), 지출 한도 (Spend caps), 범위 제한 키 (Scoped keys), 플랜 기반 제한 (Plan-based limits), 그리고 전체 실행 감사 (Full run auditing) 기능을 제공합니다. 단일 .NET 솔루션으로 구성되었으며, EF Core, 핸들러 레이어 (Handler layer), 그리고 워크스페이스가 Anthropic, Gemini 또는 자체 키로 교체할 수 있도록 프로바이더 추상화 (Provider abstraction) 뒤에 배치된 Azure OpenAI를 사용합니다.
AI는 전체 작업의 약 20%에 불과했습니다. 이 포스트는 나머지 80%, 즉 낯선 사람들에게 안전하게 넘겨줄 수 있게 만든 아키텍처 결정 사항들과 이를 배우기 위해 치러야 했던 비용에 관한 것입니다.
-
여러 개의 게이트 (Gates)보다 하나의 강제 집행 초크 포인트 (Enforcement choke point)가 낫습니다. REST API, 템플릿 갤러리, 가이드형 빌더, 그리고 "평범한 영어로 설명하기" 모드와 같은 모든 에이전트 생성 경로가 단일 핸들러를 통과합니다. 플랜 제한 (계층별 에이전트 수) 및 유효성 검사 (Validation)는 정확히 한 곳에 존재합니다. 구축 후반부에 다섯 번째 생성 경로를 추가했을 때, 별도의 작업 없이도 제한 사항을 그대로 상속받았습니다. 만약 강제 집행이 엔드포인트별로 구현되어 있었다면, 저도 모르게 우회 경로를 출시했을 것입니다.
-
모든 실행이 통과하는 경계(Boundary)에서, 즉 첫 번째 모델 호출 전에 강제 집행하십시오. 지출 상한선 (Spend ceilings)은 처음에 퍼블릭 API에만 적용되었습니다. 이로 인해 대화형 콘솔과 예약된 실행은 제한이 없었습니다. 즉, 무료 사용자가 워크벤치에 앉아 저의 Azure 비용을 소진할 수 있었던 것입니다. 해결책은 모든 실행 유형이 통과하는 단 하나의 메서드로 체크 로직을 옮기는 것이었으며, 이는 비용이 발생하는 플래닝 호출 (Planning call)을 포함하여 어떤 모델 호출이 일어나기 전에도 평가되도록 했습니다. 단 한 번의 커밋으로 "왜 이게 차단되지 않았지"라는 유형의 버그들이 통째로 사라졌습니다.
-
플랜 권한 (Plan entitlements)을 하나의 규칙 테이블로 관리하십시오. 에이전트 수, API 액세스, 스케줄링, 커넥터 (Connectors), 시트 (Seats) 등은 모두 단일 정적
PlanEntitlements타입에서 결정됩니다. 가격 페이지, API의 402 응답, 그리고 UI 배너는 특정 계층(Tier)에 무엇이 포함되어 있는지에 대해 결코 서로 다른 말을 할 수 없습니다. 물어볼 곳이 단 한 곳뿐이기 때문입니다.
제가 공개적으로 배울 뻔했던 당연한 결론(Corollary) 하나는, 여러분의 가격 페이지는 코드가 반드시 지켜야 하는 약속이라는 점입니다. 저는 아무런 강제성도 없는 "15개의 에이전트"라는 불렛 포인트를 달고 출시할 뻔했습니다.
-
감사 가능한 예외를 포함한 전역 쿼리 필터 (Global query filters)를 통한 테넌트 격리(Tenant isolation) — EF Core의 전역 쿼리 필터는 기본적으로 모든 쿼리의 범위를 현재 테넌트로 제한합니다. 의도적인 탈출구인
IgnoreQueryFilters()는 테넌트 전체를 가로질러 볼 수 있도록 설계된 플랫폼 관리자(Platform-admin) 뷰에서만 나타나도록 하여, 코드 리뷰 시 검색(greppable)이 가능하게 만들었습니다. 즉, 모든 발생 사례는 그 자체로 정당성을 입증해야 합니다. -
테스트 대상(Test surface)은 반드시 프로덕션 경로(Production path)를 실행해야 합니다. 제가 만든 워크벤치(Workbench)는 원래 에이전트의 단계를 실행하는 대신 대화 형식으로 재계획하도록 설계되었습니다. 이로 인해 테스트 동작이 실제 환경과 달라졌고, 이를 정확히 파악하기 위해 세 번의 별도 디버깅 세션이 필요했습니다. 해결책은 워크벤치가 프로덕션 API 호출과 동일한 실행 경로를 거치도록 라우팅하는 것이었습니다. 만약 여러분의 테스트 대상이 병렬 경로를 실행한다면, 여러분은 다른 제품을 테스트하고 있는 것입니다.
-
에러는 생각보다 클라이언트에서 더 자주 소멸합니다. 저의 SSE 채팅 인터페이스는 서버 에러를 올바르게 렌더링했지만, 스트림 종료(Stream-close) 파이널라이저(Finalizer)가 이를 빈 텍스트로 덮어씌워 버렸습니다. 모든 서버 측 실패가 빈 응답처럼 보였고, 저는 클라이언트를 의심하기 전에 서버를 두 번이나 다시 디버깅했습니다. 만약 UI가 "아무것도 보여주지 않는다면", 백엔드를 건드리기 전에 파이널라이제이션(Finalization) 경로를 먼저 확인하십시오.
결과를 확인하고 싶으시다면 무료 계층(Free tier)과 익명 데모가 포함된 플랫폼이 현재 라이브 상태로 운영 중입니다. 하지만 저는 주로 다른 분들이 .NET에서 멀티 테넌트 제한 강제(Multi-tenant limit enforcement)와 프로바이더 추상화(Provider abstraction)를 어떻게 구조화하는지, 그리고 어떤 패턴을 사용하고 계신지 궁금합니다.
Platform: https://studio.tramenterprise.com
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기