본문으로 건너뛰기

"Architecture" 태그 — 6개 게시물

시스템 아키텍처, 설계 패턴 관련 글

모든 태그 보기

LLM Architecture Gallery

· 약 6분
최재훈
LEAD (AI Research Engineer), Brain Crew

TL;DR

Sebastian Raschka가 운영하는 LLM Architecture Gallery는 GPT-2부터 최신 Frontier 모델까지 주요 LLM들의 아키텍처를 시각화하여 비교할 수 있는 참고 자료입니다. 각 모델의 파라미터 규모, 컨텍스트 길이, 어텐션 메커니즘, 디코더 타입 등 핵심 사양을 한눈에 파악할 수 있으며, GPT-2의 기본 Dense 구조부터 DeepSeek V3의 MoE, xLSTM 등 다양한 아키텍처 진화를 추적할 수 있습니다. AI Research Engineer가 문제 상황에 맞는 적절한 모델 선택과 아키텍처 설계 인사이트를 얻을 수 있는 실무 레퍼런스입니다.

Key Takeaways

  • 아키텍처 진화 추적: GPT-2(1.5B, MHA)부터 최신 Frontier 모델(DeepSeek V3 671B, Llama 4 400B 등)까지 디코더 구조, 어텐션 메커니즘(MHA → GQA → MoE), 정규화 기법의 변화를 체계적으로 비교 가능
  • 스케일별 설계 패턴: 소형(1B-8B), 중형(24B-32B), 대형(120B-400B), 초대형(671B-1T) 파라미터 범위별로 서로 다른 아키텍처 선택(Dense vs MoE, Attention 전략)을 확인할 수 있어 프로젝트 요구사항에 맞는 모델 선택 기준 제공
  • 기술적 디테일 확인: 각 모델의 config.json, 라이선스, 컨텍스트 길이, 포지셔널 임베딩 방식(Absolute → RoPE), Key detail 등 실무 구현에 필요한 정보를 팩트시트로 제공
  • 다양한 혁신 사례: xLSTM(7B)처럼 Transformer 외 아키텍처, Linear Attention을 활용한 Kimi 시리즈, MoE 최적화를 보여주는 Qwen3/DeepSeek 계열 등 실험적 접근법 학습 가능
  • 지속적 업데이트: 2026년 3월까지 업데이트되며(최신 Mistral Large 3 673B, GLM-5 744B 등 포함) 물리적 포스터로도 제공되어 팀 학습 및 레퍼런스용으로 활용 가능

상세 내용

Sebastian Raschka 박사가 운영하는 LLM Architecture Gallery는 현대 대규모 언어모델들의 아키텍처를 체계적으로 정리한 시각적 참고 자료입니다. 이 갤러리는 그의 주요 아티클인 "The Big LLM Architecture Comparison", "From GPT-2 to gpt-oss", "From DeepSeek V3 to V3.2", "A Dream of Spring for Open-Weight LLMs" 등에서 다룬 아키텍처 다이어그램과 팩트시트를 한 곳에 모아놓은 것입니다.

Provider LLM(Frontier 급 모델)을 주로 사용하는 실무 환경에서도 각 모델의 내부 아키텍처를 이해하면 LLM 기반 문제에 더 유연하고 전략적으로 접근할 수 있습니다. 예를 들어 레이턴시가 중요한 상황에서는 Dense 모델을, 대규모 처리에는 MoE 구조를 선택하는 등의 의사결정이 가능해집니다.

베이스라인: GPT-2부터 시작하기

갤러리는 GPT-2 XL (1.5B) 을 Late-2019 dense baseline으로 포함하여, Transformer 디코더 스택이 GPT-2 이후 얼마나 변화했는지 비교할 수 있는 기준점을 제공합니다.

GPT-2 XL 주요 사양:

  • Scale: 1.5B 파라미터
  • Context: 1,024 토큰
  • Decoder type: Dense
  • Attention: MHA(Multi-Head Attention) with learned absolute positional embeddings
  • Key detail: Dropout, GELU, LayerNorm을 사용한 클래식 GPT-2 레시피

이 기본 구조를 이해하면 이후 모델들이 어떤 방향으로 최적화되었는지(GQA 도입, RoPE 사용, Pre-norm 전환 등) 명확히 파악할 수 있습니다.

주요 모델 아키텍처 비교

Llama 계열의 진화

**Llama 3 (8B)**는 GPT-2 대비 진화된 Reference dense stack을 보여줍니다:

  • Attention: GQA(Grouped Query Attention) with RoPE
  • Context: 8,192 토큰 (GPT-2의 8배)
  • Key detail: Pre-norm 구조로 학습 안정성 향상
  • License: Llama 3 Community License

**Llama 3.2 (1B)**는 소형 모델 카테고리에서 Qwen 등과 비교되는 벤치마크를 제공하며, **Llama 4 Maverick (400B)**는 초대규모 모델의 최신 사례를 보여줍니다.

MoE 아키텍처: DeepSeek & Qwen

DeepSeek V3 (671B)V3.2는 Mixture-of-Experts 구조를 활용한 효율적인 초대규모 모델의 대표 사례입니다:

  • 전체 671B 파라미터를 가지면서도 실제 활성화되는 파라미터는 일부만 사용
  • DeepSeek R1 (671B)는 Reasoning 능력을 강화한 변형

Qwen3 계열은 다양한 스케일에서 MoE를 적용:

  • Qwen3 (235B-A22B): 235B 총 파라미터, 22B 활성 파라미터
  • Qwen3 Next (80B-A3B): 더욱 aggressive한 sparsity
  • Qwen3 (32B), (8B), (4B): Dense 구조로 다양한 규모 지원

극소형 모델: SmolLM & Nanbeige

SmolLM3 (3B)Gemma 3 (270M) 같은 소형 모델들은 Edge 디바이스나 리소스 제약 환경에서 중요합니다. Nanbeige 4.1 (3B)Tiny Aya (3.35B) 는 특정 언어나 도메인에 특화된 경량 옵션을 제공합니다.

실험적 아키텍처: xLSTM

xLSTM (7B) 은 Transformer가 아닌 LSTM 기반 접근법으로, 장기 의존성 처리와 메모리 효율성에서 다른 관점을 제시합니다. 이는 Attention 메커니즘의 대안을 탐구하는 연구자들에게 중요한 레퍼런스가 됩니다.

초대규모 모델들

1T(1조) 파라미터급 모델들도 포함되어 있습니다:

  • Kimi K2 (1T), K2.5 (1T): Linear Attention 활용
  • Ling 2.5 (1T): 중국어 특화
  • GLM-5 (744B): 최신 초대규모 모델

이들은 주로 MoE 구조를 통해 실제 inference 비용을 관리하며, 각기 다른 최적화 전략을 보여줍니다.

핵심 기술 요소 비교

Attention 메커니즘 진화

  1. MHA (Multi-Head Attention): GPT-2 시대 표준
  2. GQA (Grouped Query Attention): Llama 3, OLMo 등에서 KV cache 효율화
  3. MoE: DeepSeek, Qwen, Mistral Large 등에서 조건부 계산
  4. Linear Attention: Kimi 시리즈에서 긴 컨텍스트 처리 최적화

포지셔널 임베딩

  • Learned Absolute: GPT-2
  • RoPE (Rotary Position Embedding): 대부분의 현대 모델 표준

정규화 전략

  • Post-norm: 초기 Transformer
  • Pre-norm: Llama, OLMo 등 현대 모델의 표준 (학습 안정성)

실무 활용 방법

  1. 모델 선택 기준 수립: 프로젝트의 레이턴시, 처리량, 메모리 제약에 따라 Dense(작은 규모, 예측 가능한 성능) vs MoE(큰 규모, 효율적 처리) 선택
  2. 아키텍처 벤치마킹: 유사 규모 모델들(예: 7B-8B Dense 그룹)의 설계 차이점 비교로 최적화 아이디어 도출
  3. 라이선스 확인: 각 팩트시트의 License 정보로 상업적 사용 가능 여부 즉시 파악
  4. 구현 레퍼런스: config.json 링크와 Tech report로 재현 가능한 구현 세부사항 확인
  5. 팀 교육 자료: Redbubble 포스터(Medium 사이즈: 26.9 x 23.4 inches 권장)를 활용한 오프라인 학습 환경 구축

지속적인 업데이트

갤러리는 2026년 3월 20일까지 업데이트되었으며, 새로운 모델이 출시될 때마다 지속적으로 추가됩니다. 부정확한 팩트시트나 링크 오류는 Architecture Gallery issue tracker를 통해 제보할 수 있습니다.

최근 추가된 모델 예시:

  • Mistral Large 3 (673B)
  • GLM-4.7 (355B)
  • Nemotron 3 Super (120B-A12B)
  • Arcee AI Trinity Large (400B)
  • Sarvam (30B, 105B)

학습 커뮤니티와 지속적 성장

Sebastian Raschka는 이 갤러리 외에도 "LLMs From Scratch" 코스, AI Newsletter, Reasoning Models 분석 등을 통해 LLM 생태계의 최신 지식을 공유하고 있습니다. LLM 아키텍처는 계속 진화하고 있으며, Frontier 모델을 사용하는 엔지니어도 내부 동작 원리를 이해함으로써 더 나은 프롬프트 엔지니어링, 파인튜닝 전략, 배포 최적화를 수행할 수 있습니다.

"다같이 평생 공부합시다"라는 원본 문서의 메시지처럼, 이 갤러리는 AI Research Engineer가 지속적으로 최신 아키텍처 트렌드를 따라가고 더 넓은 시각에서 LLM 기반 문제에 유연하게 대응할 수 있도록 돕는 살아있는 레퍼런스입니다.

References

파일시스템이 주목받는 이유?

· 약 5분
최재훈
LEAD (AI Research Engineer), Brain Crew

TL;DR

AI 에이전트 생태계에서 파일시스템이 '영속적 기억 장치'로 재조명받고 있습니다. LLM의 컨텍스트 윈도우는 일시적인 화이트보드에 불과하지만, CLAUDE.md 같은 파일 기반 접근은 에이전트에게 장기 기억과 정체성을 제공합니다. Anthropic의 Agent Skills(SKILL.md) 포맷이 Microsoft, OpenAI, GitHub 등에 채택되며 사실상 표준으로 자리잡았고, 파일 포맷이 곧 API가 되는 '조율 없는 상호운용성' 시대가 열리고 있습니다. 다만 ETH Zürich 연구에 따르면 과도한 컨텍스트 파일은 오히려 성능을 저하시키므로, 최소한의 필수 요구사항만 담아야 합니다.

Key Takeaways

  • 파일시스템은 LLM의 일시적 컨텍스트 윈도우를 보완하는 가장 단순한 영속적 저장소입니다. Claude Code, Cursor 등 주요 AI 코딩 도구들이 파일 기반 컨텍스트 관리를 핵심 기능으로 채택했습니다.
  • SKILL.md 포맷이 AI 에이전트 간 사실상 표준으로 부상하며, Microsoft, OpenAI, GitHub, Cursor가 Anthropic의 Agent Skills 포맷을 채택해 도구 간 컨텍스트 이식성을 확보했습니다.
  • 과도한 컨텍스트는 독이 됩니다. ETH Zürich 연구 결과, 장황한 컨텍스트 파일은 태스크 성공률을 낮추고 추론 비용을 20% 이상 증가시킵니다. 최소한의 핵심 요구사항만 기술해야 합니다.
  • 파일 포맷이 곧 API가 되는 시대: 마크다운 기반 스킬 파일은 특정 앱에 종속되지 않고 이동·조합·감사가 가능하며, MCP 서버나 플러그인 마켓플레이스 없이도 '조율 없는 상호운용성'을 달성합니다.
  • LlamaIndex의 Jerry Liu가 제안한 원칙: 수백 개 도구를 가진 에이전트 하나보다, 파일시스템과 5~10개 핵심 도구만으로 구성된 에이전트가 더 범용적이고 효과적일 수 있습니다.

상세 내용

왜 지금 파일시스템인가?

AI 에이전트 생태계에서 파일시스템이 다시 주목받고 있습니다. LlamaIndex는 "Files Are All You Need"를 발표했고, LangChain은 에이전트의 파일시스템 기반 컨텍스트 엔지니어링을 다뤘으며, Oracle조차 에이전트 메모리 관리에서 파일시스템과 데이터베이스를 비교하는 글을 게시했습니다.

이 움직임의 핵심은 데이터베이스와는 다른 지속적 맥락 관리 수단으로서 파일시스템의 재발견입니다. Andrej Karpathy는 Claude Code가 성공한 이유를 "사용자의 컴퓨터·환경·데이터·컨텍스트 위에서 직접 실행되기 때문"이라고 지적하며, OpenAI의 클라우드 컨테이너 중심 접근이 잘못된 방향이었다고 평가했습니다.

실제로 Anthropic은 CLI 도구인 Claude Code가 수익의 상당 부분을 견인하면서 흑자에 근접하고 있으며, 현재 코딩 에이전트가 실질적 AI 활용 사례의 대부분을 차지하고 있습니다.

컨텍스트 윈도우의 한계: 화이트보드 vs 영속적 기억

LLM의 컨텍스트 윈도우는 흔히 '기억'으로 오해되지만, 실제로는 계속 지워지는 화이트보드에 가깝습니다. 인간의 기억은 장기 저장, 선택적 회상, 불필요한 정보 망각 기능을 포함하지만, LLM은 이런 기능이 없습니다.

Claude Code를 사용하다 보면 "context left until auto-compact" 알림을 마주하게 됩니다. 이때 에이전트가 축적한 코드베이스, 선호도, 결정 사항 등의 컨텍스트가 압축되거나 소실됩니다. 파일시스템은 이를 가장 단순한 방식으로 해결합니다: 기록을 파일에 쓰고, 필요할 때 다시 읽는 것입니다.

실무에서 활용되는 예시:

  • CLAUDE.md: 프로젝트에 대한 영속적 컨텍스트 제공
  • Cursor의 채팅 히스토리: 과거 대화를 검색 가능한 파일로 저장
  • aboutme.md: 개발자의 선호도, 기술 스택, 작업 스타일을 담은 이동 가능한 신원 기술자. API 조율 없이 앱 간 이동 가능

ETH Zürich 연구: 컨텍스트 파일의 역설

ETH Zürich의 최근 논문은 리포지토리 수준의 컨텍스트 파일이 실제로 코딩 에이전트의 태스크 완수에 도움이 되는지 평가했습니다. 결과는 반직관적이었습니다.

주요 발견:

  • 여러 에이전트와 모델에 걸쳐 컨텍스트 파일이 태스크 성공률을 오히려 낮춤
  • 추론 비용은 20% 이상 증가
  • 컨텍스트 파일을 받은 에이전트는 더 넓게 탐색하고, 더 많은 테스트를 실행하고, 더 많은 파일을 순회했지만, 정작 수정이 필요한 코드에 도달하는 것은 지연

이 현상의 원인은 파일이 에이전트가 지나치게 진지하게 따르는 체크리스트처럼 작동했기 때문입니다. 논문의 결론은 "컨텍스트 파일을 쓰지 말라"가 아니라, **"불필요한 요구사항이 태스크를 어렵게 만들며, 컨텍스트 파일은 최소 요구사항만 기술해야 한다"**는 것입니다.

문제는 파일시스템의 영속 계층 자체가 아니라, CLAUDE.md를 2,000단어짜리 온보딩 문서처럼 작성하는 관행입니다.

파일 포맷이 곧 API: 표준화의 여정

현재 CLAUDE.md, AGENTS.md, copilot-instructions.md, .cursorrules 등이 공존하며, 에이전트에 영속적 파일시스템 기반 컨텍스트가 필요하다는 점은 합의되었으나 파일 이름과 내용 형식은 아직 미합의 상태입니다.

Anthropic의 Agent Skills: 사실상 표준의 등장

Anthropic은 Agent Skills를 오픈 표준으로 발표하며 SKILL.md 포맷을 제안했습니다. 이는 빠르게 채택되어:

  • Microsoft, OpenAI, Atlassian, GitHub, Cursor가 공식 채택
  • Claude Code용으로 작성한 스킬이 Codex, Copilot에서도 작동
  • 파일 포맷이 곧 API가 되는 패러다임 실현

Dan Abramov의 소셜 파일시스템 제안

Dan Abramov는 AT Protocol 기반 소셜 파일시스템을 제안하며 핵심 설계 원칙을 제시했습니다:

  • 사용자 데이터를 개인 리포지토리 내 파일로 취급
  • 앱들이 "포스트가 무엇인지" 합의할 필요 없이 도메인 네임 기반 네임스페이스로 충돌 방지
  • 모든 앱의 데이터베이스는 파생 데이터, 즉 모든 사용자 폴더의 캐시된 구체화 뷰

실용적 사례: NanoClaw와 스킬 기반 아키텍처

NanoClaw는 경량 개인 AI 어시스턴트 프레임워크로, 기능 대신 스킬 모델을 채택했습니다:

  • Telegram 지원이 필요하면 Telegram 모듈이 아닌 /add-telegram 스킬(마크다운 파일)이 Claude Code에 통합 방법을 가르침
  • 스킬은 파일이므로 이동 가능하고, 감사 가능하며, 조합 가능
  • MCP 서버나 플러그인 마켓플레이스 불필요

이것이 조율 없는 상호운용성(interoperability without coordination)입니다. 두 앱이 마크다운을 읽을 수 있으면 컨텍스트를 공유할 수 있습니다.

LlamaIndex의 미니멀리즘: 도구보다 파일시스템

LlamaIndex의 Jerry Liu는 흥미로운 주장을 펼쳤습니다:

"수백 개 도구를 가진 에이전트 하나 대신, 파일시스템과 5~10개 도구만으로 100개 이상의 MCP 도구를 가진 에이전트보다 더 범용적일 수 있다."

이는 에이전트 설계에서 복잡도를 줄이고 기본 인터페이스에 집중하라는 메시지입니다. 파일시스템은:

  • 특정 앱에 종속되지 않음
  • AI 에이전트 시대에 도구 간 전환, 워크플로 결합, 연속성 유지를 가능하게 하는 개방형 인터페이스

Archil과 POSIX 파일시스템

Archil은 "에이전트가 POSIX 파일시스템을 원하기 때문에" 클라우드 볼륨을 구축 중이라고 밝혔습니다. 이는 에이전트가 표준 파일시스템 API를 통해 작동할 때 가장 효율적이며, 클라우드 환경에서도 이런 접근이 필요함을 시사합니다.

References

LangGraph Multi-Tenant PostgreSQL 설계 가이드

· 약 8분
김성연
AI Research Engineer, Brain Crew

TL;DR

LangGraph 기반 Multi-Tenant 시스템을 PostgreSQL로 구축할 때 사용할 수 있는 5가지 격리 전략을 비교 분석합니다. Thread ID + Namespace 방식은 낮은 복잡도로 빠르게 시작 가능하며, Row Level Security(RLS)는 데이터베이스 레벨에서 강력한 격리를 제공합니다. Schema 분리와 Database 분리는 더 높은 격리 수준이 필요한 금융/의료 등의 규제 환경에 적합합니다. 실무에서는 요구사항에 따라 전략을 선택하되, JWT 기반 인증과 테넌트 컨텍스트 관리를 통해 안전한 격리를 구현해야 합니다.

Key Takeaways

  • Thread ID Prefix 전략: tenant-{tenant_id}:user-{user_id}:session-{session_id} 형식으로 애플리케이션 레벨에서 간단하게 Multi-Tenant를 구현 가능. MVP나 일반 SaaS에 권장.
  • PostgreSQL RLS 활용: SET LOCAL app.tenant_id + Policy 기반으로 데이터베이스 레벨의 강력한 격리 제공. 애플리케이션 버그에도 데이터 누출 방지 가능.
  • Namespace 계층 구조: Checkpoint는 tenant-{tenant_id}, Store는 (tenant_id, user_id, "memories") 튜플로 구성해 cross-thread 상태 관리와 Long-term Memory 격리 구현.
  • Connection Pooling 고려: Schema/Database 분리 시 테넌트별 커넥션 풀 관리가 필수. 동적 스키마 라우팅과 캐싱 전략 필요.
  • 보안 체크리스트: JWT 검증, SQL Injection 방지, 감사 로깅, 정기적인 테넌트 격리 테스트를 통해 Multi-Tenant 환경의 보안 강화 필요.

상세 내용

Multi-Tenant 격리 전략 선택 가이드

LangGraph 기반의 Agent 시스템을 Multi-Tenant 환경에 배포할 때, 테넌트 간 데이터 격리는 핵심적인 아키텍처 결정입니다. 각 전략은 격리 수준, 구현 복잡도, 확장성, 그리고 사용 케이스에 따라 뚜렷한 트레이드오프를 가집니다.

전략격리 수준복잡도확장성사용 케이스
Application-level낮음낮음높음빠른 MVP
Thread ID Prefix중간낮음높음일반적인 SaaS
Schema 분리높음중간중간규제 요구사항
Row Level Security높음높음높음엔터프라이즈
Database 분리최고최고낮음금융/의료

전략 선택의 핵심은 요구되는 격리 수준운영 복잡도 간의 균형입니다. 대부분의 경우 Thread ID + Namespace 방식으로 시작하여, 보안 요구사항이 증가하면 RLS나 Schema 분리로 마이그레이션하는 것을 권장합니다.

전략 1: Thread ID + Namespace 기반 격리

가장 실용적인 시작점으로, LangGraph의 Thread와 Namespace 개념을 활용한 애플리케이션 레벨 격리 방식입니다.

핵심 설계 원칙:

  • Thread ID: tenant-{tenant_id}:user-{user_id}:session-{session_id} 형식으로 각 실행을 고유하게 식별
  • Checkpoint Namespace: tenant-{tenant_id}로 테넌트 레벨에서 그룹핑
  • Store Namespace: (tenant_id, user_id, "memories") 튜플로 Long-term Memory 계층 구조 구성

구현 예시

from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from langgraph.store.postgres.aio import AsyncPostgresStore
from pydantic import BaseModel
import jwt

# JWT 기반 테넌트 인증
security = HTTPBearer()

class TenantContext(BaseModel):
tenant_id: str
user_id: str
org_id: Optional[str] = None

def get_tenant_context(
credentials: HTTPAuthorizationCredentials = Depends(security)
) -> TenantContext:
"""JWT에서 테넌트 정보 추출"""
try:
payload = jwt.decode(
credentials.credentials,
JWT_SECRET,
algorithms=["HS256"]
)
return TenantContext(
tenant_id=payload["tenant_id"],
user_id=payload["user_id"],
org_id=payload.get("org_id")
)
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")

# Thread ID 생성 전략
def generate_thread_id(
tenant_id: str, user_id: str, session_id: str
) -> str:
return f"tenant-{tenant_id}:user-{user_id}:session-{session_id}"

def generate_checkpoint_ns(tenant_id: str) -> str:
return f"tenant-{tenant_id}"

def generate_store_namespace(tenant_id: str, user_id: str) -> tuple:
return (tenant_id, user_id, "memories")

장점:

  • 구현 복잡도가 낮고 빠르게 프로토타입 가능
  • PostgreSQL 특별 설정 불필요
  • 수평 확장성 우수 (단일 데이터베이스에서 수천 개 테넌트 지원)

제약사항:

  • 애플리케이션 코드 버그 시 데이터 누출 위험
  • 데이터베이스 레벨의 강제 격리 없음

전략 2: PostgreSQL Row Level Security (RLS)

데이터베이스 레벨에서 행 단위 접근 제어를 구현하여, 애플리케이션 로직과 무관하게 테넌트 격리를 보장합니다.

RLS 설정 예시

-- 1. tenant_id 컬럼 추가 (기존 테이블 수정)
ALTER TABLE checkpoints ADD COLUMN tenant_id TEXT;
ALTER TABLE checkpoint_writes ADD COLUMN tenant_id TEXT;

-- 2. 인덱스 생성 (성능 최적화)
CREATE INDEX idx_checkpoints_tenant
ON checkpoints(tenant_id, thread_id);

CREATE INDEX idx_checkpoint_writes_tenant
ON checkpoint_writes(tenant_id, thread_id);

-- 3. RLS 정책 활성화
ALTER TABLE checkpoints ENABLE ROW LEVEL SECURITY;
ALTER TABLE checkpoint_writes ENABLE ROW LEVEL SECURITY;

-- 4. 테넌트별 격리 정책
CREATE POLICY tenant_isolation ON checkpoints
FOR ALL
USING (tenant_id = current_setting('app.tenant_id', TRUE))
WITH CHECK (tenant_id = current_setting('app.tenant_id', TRUE));

CREATE POLICY tenant_isolation ON checkpoint_writes
FOR ALL
USING (tenant_id = current_setting('app.tenant_id', TRUE))
WITH CHECK (tenant_id = current_setting('app.tenant_id', TRUE));

LangGraph와 RLS 통합

class TenantAwarePostgresSaver(AsyncPostgresSaver):
"""RLS 지원 커스텀 Checkpointer"""

def __init__(self, conn: Connection, tenant_id: str):
super().__init__(conn)
self.tenant_id = tenant_id

async def _set_tenant_context(self):
"""세션 시작 시 tenant_id 설정"""
await self.conn.execute(
f"SET LOCAL app.tenant_id = '{self.tenant_id}'"
)

@app.post("/chat")
async def chat_endpoint(
request: ChatRequest,
tenant: TenantContext = Depends(get_tenant_context)
):
async with pool.connection() as conn:
# RLS 컨텍스트 설정
await conn.execute(
f"SET LOCAL app.tenant_id = '{tenant.tenant_id}'"
)

checkpointer = AsyncPostgresSaver(conn)
graph = create_graph().compile(checkpointer=checkpointer)

config = {
"configurable": {
"thread_id": generate_thread_id(
tenant.tenant_id,
tenant.user_id,
request.session_id
)
}
}

response = await graph.ainvoke(
{"messages": request.messages},
config
)
return response

RLS의 핵심 이점:

  • 데이터베이스가 격리를 강제하므로 애플리케이션 버그에도 안전
  • 기존 LangGraph 코드 수정 최소화
  • 감사 로깅과 결합 가능

성능 고려사항:

  • current_setting() 함수 호출 오버헤드 (일반적으로 무시 가능)
  • tenant_id 인덱스 필수 (쿼리 성능 유지)

전략 3: Schema 기반 격리

각 테넌트를 별도 PostgreSQL Schema로 격리하는 방식으로, 물리적 분리에 가까운 격리를 제공합니다.

class SchemaBasedCheckpointer:
"""테넌트별 Schema 라우팅"""

def __init__(self, pool: AsyncConnectionPool):
self.pool = pool
self.schema_cache = {}

async def get_checkpointer(
self, tenant_id: str
) -> AsyncPostgresSaver:
schema_name = f"tenant_{tenant_id}"

# Schema 자동 생성
if schema_name not in self.schema_cache:
async with self.pool.connection() as conn:
await conn.execute(
f"CREATE SCHEMA IF NOT EXISTS {schema_name}"
)
await conn.execute(
f"SET search_path TO {schema_name}"
)
# LangGraph 테이블 초기화
checkpointer = AsyncPostgresSaver(conn)
await checkpointer.setup()

self.schema_cache[schema_name] = True

# Schema 전환 후 Checkpointer 반환
async with self.pool.connection() as conn:
await conn.execute(
f"SET search_path TO {schema_name}"
)
return AsyncPostgresSaver(conn)

적용 시나리오:

  • 규제 요구사항 (GDPR, HIPAA 등)
  • 테넌트별 백업/복구 필요
  • 데이터 마이그레이션 용이성

운영 복잡도:

  • Schema 생성/삭제 자동화 필요
  • Connection Pool 관리 복잡성 증가
  • 테넌트 수가 수백 개 이상일 때 스키마 관리 부담

Long-Term Memory (Store) Multi-Tenant 격리

LangGraph Store는 Checkpoint와 별도로 Long-term Memory를 관리합니다. Namespace 튜플 구조를 활용한 계층적 격리가 핵심입니다.

# Store Namespace 전략
def generate_store_namespace(
tenant_id: str,
user_id: str
) -> tuple:
"""
계층 구조: (tenant_id, user_id, "memories")
- 레벨 1: 테넌트 격리
- 레벨 2: 사용자별 분리
- 레벨 3: 메모리 타입
"""
return (tenant_id, user_id, "memories")

# 사용 예시
async def save_user_preference(
tenant_id: str,
user_id: str,
preference: dict
):
namespace = generate_store_namespace(tenant_id, user_id)

await store.aput(
namespace=namespace,
key="preferences",
value=preference
)

# 검색 시 테넌트 자동 필터링
async def search_memories(
tenant_id: str,
user_id: str,
query: str
):
namespace = generate_store_namespace(tenant_id, user_id)

# Store는 namespace prefix로 자동 격리
results = await store.asearch(
namespace_prefix=(tenant_id,), # 테넌트 레벨 필터
query=query
)
return results

Store RLS 적용 (추가 격리층):

-- Store 테이블에도 RLS 적용
ALTER TABLE store ADD COLUMN tenant_id TEXT;

CREATE POLICY store_tenant_isolation ON store
FOR ALL
USING (
namespace[1] = current_setting('app.tenant_id', TRUE)
)
WITH CHECK (
namespace[1] = current_setting('app.tenant_id', TRUE)
);

보안 체크리스트

Multi-Tenant 시스템 배포 전 반드시 확인해야 할 보안 항목:

1. 인증/인가

  • JWT 토큰 서명 검증 구현
  • 토큰 만료 시간 적절히 설정 (권장: 1시간)
  • Refresh Token 순환 메커니즘

2. 격리 검증

  • Cross-tenant 쿼리 시도 시 접근 거부 확인
  • Thread ID에 테넌트 정보 포함 여부 검증
  • RLS 정책 우회 시도 테스트

3. SQL Injection 방지

# ❌ 위험: 문자열 포맷팅
await conn.execute(
f"SET LOCAL app.tenant_id = '{tenant_id}'"
)

# ✅ 안전: 파라미터화된 쿼리
await conn.execute(
"SELECT set_config('app.tenant_id', $1, true)",
[tenant_id]
)

4. 감사 로깅

async def audit_log(
tenant_id: str,
user_id: str,
action: str,
resource: str
):
await conn.execute("""
INSERT INTO audit_logs
(tenant_id, user_id, action, resource, timestamp)
VALUES ($1, $2, $3, $4, NOW())
""", [tenant_id, user_id, action, resource])

5. 정기 검증

  • 월간 테넌트 격리 침투 테스트
  • 분기별 권한 감사
  • 데이터 접근 로그 분석 자동화

성능 최적화 전략

Connection Pool 설정:

# Schema 분리 시 동적 풀 관리
class TenantAwarePool:
def __init__(self, base_uri: str, max_pools: int = 50):
self.pools: Dict[str, AsyncConnectionPool] = {}
self.base_uri = base_uri
self.max_pools = max_pools

async def get_pool(
self, tenant_id: str
) -> AsyncConnectionPool:
if tenant_id not in self.pools:
if len(self.pools) >= self.max_pools:
# LRU 제거
oldest = min(
self.pools.items(),
key=lambda x: x[1].last_used
)
await oldest[1].close()
del self.pools[oldest[0]]

self.pools[tenant_id] = AsyncConnectionPool(
f"{self.base_uri}?options=-c search_path=tenant_{tenant_id}",
min_size=2,
max_size=10
)

return self.pools[tenant_id]

인덱싱 전략:

-- Composite 인덱스로 테넌트 쿼리 최적화
CREATE INDEX idx_checkpoints_tenant_thread_ts
ON checkpoints(tenant_id, thread_id, checkpoint_id DESC);

-- Partial 인덱스로 활성 테넌트만 최적화
CREATE INDEX idx_active_tenants
ON checkpoints(tenant_id, thread_id)
WHERE created_at > NOW() - INTERVAL '30 days';

References

The Big LLM Architecture Comparison

· 약 5분
최재훈
LEAD (AI Research Engineer), Brain Crew

TL;DR

DeepSeek V3는 2024년 말 등장한 이래 LLM 아키텍처의 새로운 방향을 제시했습니다. 7년 전 원조 GPT 이후 구조적으로는 크게 변하지 않았지만, Multi-Head Latent Attention(MLA)과 Mixture-of-Experts(MoE) 같은 효율성 중심의 혁신이 주목받고 있습니다. 단순한 성능 향상보다는 계산 효율성과 확장성을 개선하는 아키텍처 설계가 2025년 LLM 개발의 핵심 트렌드입니다. 이 글은 최신 오픈 모델들의 구조적 발전을 비교 분석하여, AI Research Engineer가 알아야 할 아키텍처 변화의 실체를 살펴봅니다.

Key Takeaways

  • 점진적 개선이 주류: GPT-2(2019)부터 Llama 4, DeepSeek V3(2024-2025)까지 기본 트랜스포머 구조는 유사하며, 주요 변화는 RoPE, Grouped-Query Attention, SwiGLU 등 효율성 개선에 집중
  • 계산 효율성이 핵심 차별화 요소: DeepSeek V3의 Multi-Head Latent Attention(MLA)과 MoE 구조는 추론 시 KV 캐시 메모리를 줄이고, 활성화되는 파라미터를 제한하여 효율성 극대화
  • 벤치마크보다 구조 이해가 중요: 데이터셋, 학습 기법, 하이퍼파라미터가 공개되지 않는 경우가 많아 성능 비교는 어렵지만, 아키텍처 자체의 설계 철학을 이해하는 것이 실무 적용에 더 유용
  • 멀티모달은 별도 논의 필요: 최신 모델들의 멀티모달 기능은 텍스트 능력과 분리하여 평가해야 하며, 이 글은 텍스트 아키텍처에 집중
  • 오픈 모델 중심의 생태계: 2025년 현재 DeepSeek, Llama, GLM 등 주요 오픈 모델들의 아키텍처 공개가 활발하며, 이들의 설계 선택을 비교하는 것이 실무 인사이트 획득에 유리

상세 내용

2025년 LLM 아키텍처의 현주소

원조 GPT 아키텍처가 개발된 지 7년이 지났습니다. GPT-2(2019)를 돌아보고 DeepSeek V3, Llama 4(2024-2025)를 전망하면, 이들이 구조적으로 여전히 매우 유사하다는 점에 놀랄 수 있습니다.

물론 변화는 있었습니다:

  • Positional Embeddings: 절대 위치 인코딩에서 Rotational Positional Embedding(RoPE)로 진화
  • Attention 메커니즘: Multi-Head Attention이 Grouped-Query Attention으로 대체
  • 활성화 함수: GELU 대신 더 효율적인 SwiGLU 채택

하지만 이러한 개선들은 근본적인 혁신일까요, 아니면 동일한 아키텍처 기반을 세련되게 다듬은 것일까요?

LLM 간 비교는 본질적으로 어렵습니다. 데이터셋, 학습 기법, 하이퍼파라미터가 크게 다르고 제대로 문서화되지 않는 경우가 많기 때문입니다. 그럼에도 불구하고 아키텍처 구조 자체의 변화를 살펴보는 것은 2025년 LLM 개발자들이 어떤 방향으로 나아가고 있는지 이해하는 데 큰 가치가 있습니다.

이 글에서는 벤치마크 성능이나 학습 알고리즘보다는, 현재 주요 오픈 모델들을 정의하는 아키텍처 발전에 집중합니다. (참고로 멀티모달 LLM은 별도로 다룬 바 있으며, 이번 글에서는 최신 모델들의 텍스트 능력에 초점을 맞추고 멀티모달 논의는 다음 기회로 미룹니다.)

DeepSeek V3/R1: 효율성 중심의 아키텍처 혁신

2025년 1월 출시된 DeepSeek R1은 큰 반향을 일으켰습니다. DeepSeek R1은 2024년 12월 소개된 DeepSeek V3 아키텍처를 기반으로 한 추론(reasoning) 모델입니다. 비록 2024년에 출시되었지만, DeepSeek V3가 널리 주목받고 채택된 것은 2025년 DeepSeek R1 출시 이후이므로 포함하는 것이 합리적입니다.

DeepSeek V3에서 도입된 두 가지 핵심 아키텍처 기법은 계산 효율성을 크게 개선했으며, 다른 많은 LLM과 차별화됩니다:

1.1 Multi-Head Latent Attention (MLA)

Multi-Head Latent Attention은 기존 Multi-Head Attention의 메모리 효율성 문제를 해결하기 위해 설계되었습니다. 전통적인 어텐션 메커니즘에서는 각 헤드가 독립적인 Key와 Value를 유지해야 하므로, 긴 시퀀스 처리 시 KV 캐시가 메모리 병목이 됩니다.

MLA는 저차원 latent 표현을 활용하여 이 문제를 완화합니다:

  • 각 헤드의 Key/Value를 공유 가능한 압축된 표현으로 변환
  • 추론 시 KV 캐시 메모리 요구량 대폭 감소
  • 긴 컨텍스트 처리 능력 향상

이는 특히 실시간 추론이나 리소스 제약 환경에서 중요한 개선입니다.

1.2 Mixture-of-Experts (MoE)

DeepSeek V3는 MoE 구조를 활용하여 모델 파라미터를 확장하면서도 실제 연산량은 제한합니다:

  • 각 토큰은 전체 전문가(experts) 중 일부만 활성화
  • 전체 파라미터는 크지만 활성 파라미터는 상대적으로 작음
  • 학습과 추론 모두에서 효율성 향상

MoE는 2025년 현재 대규모 LLM의 표준 기법으로 자리잡았으며, 특히 오픈 소스 생태계에서 널리 채택되고 있습니다.

다른 주요 아키텍처 동향

이 글에서는 DeepSeek V3 외에도 GLM-5, Llama 4를 포함한 여러 최신 아키텍처들을 비교 분석합니다. 각 모델은 다음과 같은 특징을 가집니다:

  • 공통점: 트랜스포머 기본 구조 유지, RoPE 채택, 효율적 어텐션 메커니즘 사용
  • 차이점: MoE 구현 방식, 레이어 정규화 위치, FFN 설계, 특정 최적화 기법

모든 모델이 근본적으로 다른 패러다임을 제시하기보다는, 검증된 구조 위에서 특정 측면(효율성, 확장성, 특정 태스크 성능)을 개선하는 방향으로 발전하고 있습니다.

실무 적용을 위한 고려사항

AI Research Engineer로서 이러한 아키텍처 비교에서 얻어야 할 인사이트는:

  1. 벤치마크만으로는 부족: 공개된 성능 수치보다 아키텍처 설계 철학을 이해하고, 자신의 유스케이스에 맞는 트레이드오프 판단이 중요
  2. 효율성이 새로운 경쟁력: 단순 모델 크기보다 메모리 효율성, 추론 속도, 활성 파라미터 비율 같은 지표가 실무 배포의 핵심
  3. 점진적 개선의 누적 효과: 각각의 기법(RoPE, GQA, SwiGLU, MLA 등)은 작은 개선처럼 보이지만, 결합하면 상당한 성능 향상
  4. 오픈 소스 생태계 활용: 주요 아키텍처들이 오픈되면서 실험과 커스터마이징이 용이해졌으며, 이를 활용한 도메인 특화 모델 개발 가능

결론

2025년 LLM 아키텍처는 혁명보다는 진화의 시기입니다. 트랜스포머라는 강력한 기반 위에서, 계산 효율성과 확장성을 극대화하는 세밀한 엔지니어링이 핵심입니다. DeepSeek V3의 MLA와 MoE는 이러한 트렌드를 대표하며, 앞으로도 유사한 방향의 개선이 계속될 것으로 예상됩니다.

실무에서는 최신 아키텍처의 벤치마크 순위보다, 각 설계 선택이 가져오는 실제 효과를 이해하고 자신의 문제에 적용하는 능력이 더욱 중요해질 것입니다.

References

GPU Programming 101

· 약 5분
최재훈
LEAD (AI Research Engineer), Brain Crew

TL;DR

GPU 프로그래밍은 CPU와 근본적으로 다른 병렬 처리 아키텍처를 활용하는 기술입니다. CPU가 순차적 처리에 최적화된 반면, GPU는 수천 개의 코어로 대규모 병렬 연산을 수행합니다. AI Research Engineer라면 딥러닝 모델 학습 최적화를 위해 GPU의 메모리 계층 구조(글로벌/공유/레지스터 메모리), 스레드 조직화(그리드/블록/워프), 그리고 메모리 접근 패턴(coalescing)을 이해해야 합니다. CUDA나 OpenCL 같은 프레임워크를 통해 GPU를 프로그래밍하며, 효율적인 커널 설계가 성능의 핵심입니다.

Key Takeaways

  • 대규모 병렬성 활용: GPU는 수천 개의 경량 코어를 통해 단순한 연산을 대량으로 처리하는데 특화되어 있어, 행렬 연산이 많은 딥러닝에 최적입니다.
  • 메모리 계층 최적화: 글로벌 메모리(느림, 대용량) → 공유 메모리(빠름, 블록 공유) → 레지스터(매우 빠름, 스레드 전용) 순으로 활용하면 성능을 극대화할 수 있습니다.
  • 메모리 접근 패턴: 연속된 스레드가 연속된 메모리를 접근하는 coalesced access 패턴을 유지해야 메모리 대역폭을 효율적으로 사용할 수 있습니다.
  • 스레드 조직화 이해: 워프(32개 스레드 단위) 단위로 동작하므로, 분기문(if-else)은 워프 내 발산(divergence)을 일으켜 성능을 저하시킬 수 있습니다.
  • 프레임워크 선택: CUDA(NVIDIA 전용, 성숙한 생태계)와 OpenCL(크로스 플랫폼) 중 프로젝트 요구사항에 맞게 선택하고, PyTorch/TensorFlow 같은 고수준 라이브러리가 내부적으로 어떻게 GPU를 활용하는지 이해하면 디버깅과 최적화에 유리합니다.

상세 내용

GPU vs CPU: 아키텍처의 근본적 차이

GPU Programming 101

CPU와 GPU는 서로 다른 목적으로 설계된 프로세서입니다. CPU는 복잡한 제어 로직과 큰 캐시를 가진 소수의 강력한 코어로 구성되어, 순차적 처리와 분기 예측에 최적화되어 있습니다. 반면 GPU는 간단한 제어 유닛을 가진 수천 개의 작은 코어로 구성되어, 동일한 연산을 대량의 데이터에 병렬로 적용하는 SIMT(Single Instruction, Multiple Threads) 아키텍처를 채택합니다.

AI/ML 워크로드에서 GPU가 압도적인 이유는 명확합니다. 신경망 학습의 핵심인 행렬 곱셈, 컨볼루션 연산, 활성화 함수 적용 등은 모두 수백만 개의 독립적인 연산으로 분해될 수 있으며, 이는 GPU의 대규모 병렬 처리 능력과 완벽하게 부합합니다.

GPU 메모리 계층 구조

GPU 프로그래밍에서 성능 최적화의 핵심은 메모리 계층을 이해하고 활용하는 것입니다:

글로벌 메모리 (Global Memory)

  • 가장 크지만 가장 느린 메모리 (수백 사이클 지연)
  • 모든 스레드에서 접근 가능
  • 일반적으로 수 GB ~ 수십 GB 용량
  • CPU의 메인 메모리와 유사한 역할

공유 메모리 (Shared Memory)

  • 블록 내 스레드들이 공유하는 고속 메모리
  • 레지스터보다 느리지만 글로벌 메모리보다 100배 이상 빠름
  • 스레드 간 데이터 교환 및 재사용에 활용
  • 일반적으로 블록당 48~96 KB

레지스터 (Registers)

  • 각 스레드 전용의 가장 빠른 메모리
  • 지연 시간이 거의 없음
  • 로컬 변수가 저장되는 공간
  • 제한적이므로 과도한 사용 시 occupancy 감소

효율적인 메모리 사용 패턴은 다음과 같습니다:

  1. 글로벌 메모리에서 데이터를 공유 메모리로 로드
  2. 공유 메모리에서 여러 번 재사용하며 연산 수행
  3. 결과를 다시 글로벌 메모리에 저장

이 패턴은 느린 글로벌 메모리 접근을 최소화하고, 빠른 공유 메모리의 지역성(locality)을 활용합니다.

스레드 조직화: 그리드, 블록, 워프

GPU 프로그래밍에서는 스레드를 계층적으로 조직합니다:

그리드 (Grid)

  • 전체 커널 실행 단위
  • 여러 블록으로 구성
  • 1D, 2D, 3D 구조 가능

블록 (Block)

  • 스레드의 그룹
  • 같은 블록 내 스레드는 공유 메모리 사용 가능
  • 동기화 가능 (__syncthreads())
  • 일반적으로 128~1024 스레드로 구성

워프 (Warp)

  • 32개 스레드의 실행 단위 (NVIDIA 기준)
  • 동일한 명령어를 동시에 실행
  • 워프 내 분기(branch divergence) 발생 시 직렬화되어 성능 저하

최적화 팁:

  • 블록 크기는 워프 크기(32)의 배수로 설정
  • 워프 내 조건 분기 최소화
  • 메모리 접근은 coalesced pattern 유지 (연속된 스레드가 연속된 메모리 접근)

프로그래밍 모델과 프레임워크

CUDA (Compute Unified Device Architecture)

  • NVIDIA GPU 전용
  • 가장 성숙한 생태계와 도구
  • C/C++ 확장 형태
  • cuDNN, cuBLAS 등 최적화된 라이브러리 제공
__global__ void vectorAdd(float *A, float *B, float *C, int N) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N) {
C[idx] = A[idx] + B[idx];
}
}

OpenCL (Open Computing Language)

  • 크로스 플랫폼 (NVIDIA, AMD, Intel 등)
  • 이식성이 높지만 CUDA 대비 복잡한 API
  • 다양한 하드웨어 지원 필요 시 선택

고수준 프레임워크

  • PyTorch, TensorFlow: 자동 GPU 가속
  • Numba, CuPy: Python에서 GPU 커널 작성
  • Triton: OpenAI의 GPU 프로그래밍 언어

AI Research Engineer를 위한 실전 가이드

프로파일링과 디버깅

  • NVIDIA Nsight Systems/Compute로 병목 지점 분석
  • 커널 실행 시간, 메모리 전송 시간 측정
  • Occupancy 확인 (이론적 최대 대비 실제 활용률)

일반적인 최적화 전략

  1. 메모리 대역폭 최적화: Coalesced access, 불필요한 전송 제거
  2. 연산 강도 증가: 메모리 접근 대비 연산 비율 높이기
  3. Occupancy 최적화: 블록 크기, 레지스터 사용량 조절
  4. 텐서 코어 활용: Mixed precision training (FP16/BF16)

딥러닝 특화 최적화

  • Fused kernels: 여러 연산을 하나의 커널로 결합
  • Memory pooling: 반복적 할당/해제 오버헤드 제거
  • Gradient accumulation: 큰 배치를 여러 작은 배치로 분할
  • Flash Attention: 메모리 효율적인 attention 구현

실무에서의 고려사항

GPU 프로그래밍은 추상화 수준에 따라 접근 방식이 달라집니다:

  • 고수준 (대부분의 경우): PyTorch/TensorFlow 사용, 프레임워크 최적화 기능 활용
  • 중간 수준: 커스텀 CUDA 커널 작성 (PyTorch custom ops)
  • 저수준: 전체 시스템 최적화, 새로운 아키텍처 구현

대부분의 AI Research Engineer는 고수준 프레임워크를 주로 사용하지만, GPU 동작 원리를 이해하면:

  • 예상치 못한 성능 저하 원인 파악 가능
  • 메모리 부족(OOM) 문제 해결 전략 수립
  • 모델 아키�처 설계 시 하드웨어 친화적 선택
  • 프로파일링 결과를 올바르게 해석

References

  • 원본 문서: GPU Programming 101 (Backend-Engineering, Architecture)

You don’t need Elasticsearch : BM25 is now in Postgres

· 약 8분
최재훈
LEAD (AI Research Engineer), Brain Crew

TL;DR

Postgres의 기본 전문 검색(Full-Text Search)은 키워드 반복 남용, 문서 길이 편향, 희귀 단어 처리 실패 등의 문제로 실무에서 한계가 있었습니다. 이를 해결하기 위해 Elasticsearch를 추가하는 것이 일반적이었지만, 이제 pg_textsearch 확장을 통해 BM25 알고리즘을 Postgres에서 직접 사용할 수 있습니다. BM25는 TF 포화(Term Frequency Saturation), IDF(Inverse Document Frequency), 문서 길이 정규화를 통해 검색 품질을 대폭 개선하며, pgvector와 결합한 하이브리드 검색으로 RAG 파이프라인과 AI 에이전트의 검색 성능을 향상시킬 수 있습니다.

Key Takeaways

  • 인프라 단순화: BM25를 Postgres에서 직접 사용하면 Elasticsearch 같은 별도 검색 클러스터, 데이터 동기화 파이프라인, 추가 운영 부담 없이 검색 품질을 크게 개선할 수 있습니다.
  • BM25의 핵심 개선사항: TF Saturation(키워드 반복 스팸 방지), IDF(희귀 단어 가중치 증가), Length Normalization(짧고 집중된 문서 우대)으로 Postgres 기본 검색의 4가지 주요 문제를 해결합니다.
  • 하이브리드 검색의 필요성: RAG와 AI 에이전트는 정확한 키워드 매칭(BM25)과 의미적 유사성(Vector Search)을 모두 필요로 하며, Reciprocal Rank Fusion으로 두 방식을 결합할 수 있습니다.
  • 실무 적용 간편성: CREATE EXTENSION pg_textsearchCREATE INDEX USING bm25 만으로 즉시 적용 가능하며, 기존 pgvector와 함께 사용하면 한 쿼리로 하이브리드 검색을 구현할 수 있습니다.
  • 99%의 사용 사례에 충분: 페타바이트급 로그 수집이 아닌 일반적인 애플리케이션 검색(문서, 제품 카탈로그, 지원 티켓 등)에는 Postgres + BM25 + Vector 조합이 충분하며 운영 복잡도를 크게 낮춥니다.

상세 내용

Postgres 검색의 현실과 복잡성의 덫

Postgres는 Stripe, Instagram, Spotify를 비롯한 수백만 개발자의 표준 데이터베이스입니다. 검색 기능 역시 모든 앱에 필수적입니다—제품 카탈로그, 문서, 사용자 콘텐츠, 지원 티켓, 그리고 최근에는 답변 생성 전에 관련 문서를 찾아야 하는 AI 에이전트와 RAG 파이프라인까지.

개발자들은 자연스럽게 Postgres로 검색을 구현하려 하지만 곧 한계에 부딪힙니다. 그러면 다음 단계는? Elasticsearch, Algolia, Typesense 같은 전문 검색 시스템 도입입니다.

그 순간부터:

  • 별도 클러스터를 운영하고 24/7 가동 상태를 유지해야 합니다
  • Postgres와 검색 시스템 간 데이터 동기화 파이프라인을 구축해야 합니다
  • 검색 결과가 오래되거나 누락된 이유를 디버깅해야 합니다
  • On-call 대응 시스템 목록에 또 하나가 추가됩니다
  • 매달 수천 달러를 매니지드 서비스에 지불하거나, 운영 전문가를 고용해야 합니다

물론 1%는 이런 복잡성이 필요합니다. 페타바이트급 로그 수집을 위한 Elasticsearch, 실시간 분석을 위한 Clickhouse, Google과 OpenAI의 맞춤형 인프라. 하지만 나머지 99%는 이런 복잡성이 필요 없습니다. 이미 사용 중인 데이터베이스에서 더 나은 검색만 있으면 됩니다.

데모 앱에서 Native Search, BM25, Vector Search, Hybrid Search를 직접 비교해볼 수 있습니다.

Postgres Native Search의 4가지 핵심 문제

검색의 주요 목표는 주어진 쿼리에 대해 가장 관련성 높고 유용한 결과를 반환하는 것입니다. 간단해 보이지만, 실제로는 전혀 그렇지 않습니다.

구체적인 예시를 위해 다음과 같은 문서들이 있다고 가정해봅시다:

📄 Database Connection Pooling Guide
"Database connection pooling improves application performance.
A pool maintains reusable connections. Configure pool size based on workload."

📄 PostgreSQL Authentication Setup
"Set up PostgreSQL database authentication methods.
Configure pg_hba.conf for password, certificate, and LDAP authentication."

📄 Generic Blog Post (스팸)
"Database database database. Learn about database. Database is important.
Database database database. More database info."

📄 EXPLAIN ANALYZE Quick Tip (15 단어)
"Use EXPLAIN ANALYZE to find slow PostgreSQL queries.
Shows execution plan and actual timing."

📄 Complete PostgreSQL Query Tuning Guide (80 단어)
"This comprehensive PostgreSQL guide covers query tuning. PostgreSQL query
performance depends on proper use of EXPLAIN and EXPLAIN ANALYZE..."

Problem 1: 키워드 스터핑이 승리한다

"database"를 검색하면, Native Postgres는 키워드 개수로 순위를 매깁니다. "database"를 12번 반복한 스팸 문서가 1위를 차지하고, 실제 유용한 가이드는 하위에 랭크됩니다.

Problem 1

Problem 2: 흔한 단어가 지배한다

"database authentication"을 검색하면, "database"는 10개 이상의 문서에 등장하고, "authentication"은 단 1개에만 등장합니다. 어떤 단어가 실제로 찾고자 하는 내용을 식별할까요?

Native Postgres는 두 단어를 동등하게 취급합니다. BM25는 "authentication"이 진짜 시그널임을 압니다.

Problem 2

Problem 3: 긴 문서가 승리한다

"EXPLAIN ANALYZE"를 검색하면, 80단어 가이드는 8번 언급하고, 15단어 팁은 2번 언급합니다. Native는 긴 문서를 더 높게 랭크합니다.

하지만 짧은 팁은 전체가 EXPLAIN ANALYZE에 관한 것입니다. 이것이 최고의 결과입니다.

Problem 3

Problem 4: All-or-Nothing 매칭

"database connection pooling"을 검색하면, Native는 Boolean AND를 사용합니다. 세 단어가 모두 있는 문서만 매치됩니다. 15개 중 2개만 결과로 나옵니다.

OR로 바꾸면? 13개 결과가 나오지만, 많은 문서가 동일한 점수를 갖습니다. 어떤 것이 실제로 관련 있는지 알 방법이 없습니다.

Problem 4

해결책: BM25 알고리즘

좋은 소식은 검색 업계가 이미 1990년대에 이 문제를 해결했다는 것입니다. 단지 Postgres에 추가되지 않았을 뿐입니다. 그것이 바로 BM25(Best Matching 25)입니다.

BM25는 Elasticsearch, Solr, Lucene 등 거의 모든 프로덕션 검색 시스템을 구동하며, 위의 문제들을 정확히 해결합니다:

Term Frequency Saturation (TF 포화) - 단어를 12번 언급한다고 해서 문서가 12배 더 관련성 있는 것은 아닙니다. 몇 번 언급 후에는 추가 반복이 거의 도움이 되지 않습니다. 스팸이 패배합니다.

Inverse Document Frequency (IDF) - 희귀한 단어가 더 중요합니다. "Database"는 어디에나 있으므로 노이즈입니다. "Authentication"은 한 번만 등장하므로 시그널입니다. BM25는 이에 따라 가중치를 부여합니다.

Length Normalization (길이 정규화) - 쿼리에 집중된 15단어 팁이 지나가듯 언급한 80단어 문서를 이깁니다. BM25는 문서 길이를 조정합니다.

Ranked Retrieval (순위 기반 검색) - 모든 문서가 의미 있는 관련성 점수를 받으며, 단순히 "매치" 또는 "매치 안 됨"이 아닙니다. 부분 매치도 나타나지만 낮은 순위로 표시됩니다.

BM25 Venn Diagram

이것이 Google이 처음부터 작동한 방식입니다. 검색의 기본 요건입니다.

Postgres에서 BM25 사용하기

pg_textsearch는 BM25를 Postgres에 도입합니다:

CREATE EXTENSION pg_textsearch;
CREATE INDEX ON articles USING bm25(content);

SELECT * FROM articles
ORDER BY content <@> to_bm25query('database performance')
LIMIT 10;

BM25 수식의 이해

BM25 점수는 다음과 같이 계산됩니다:

score(D,Q) = Σ IDF(qi) · [f(qi,D) · (k1+1)] / [f(qi,D) + k1·(1-b + b·|D|/avgdl)]

여기서:

  • f(qi,D): 문서 D에서 키워드 qi가 등장하는 횟수
  • |D|: 문서 D의 길이(단어 수)
  • avgdl: 컬렉션의 평균 문서 길이
  • k1: TF 포화를 제어하는 파라미터 (일반적으로 1.2~2.0)
  • b: 길이 정규화를 제어하는 파라미터 (0~1, 일반적으로 0.75)

AI 에이전트와 RAG를 위한 하이브리드 검색

AI 에이전트와 RAG 파이프라인도 검색이 필요합니다. 그리고 BM25만으로는 해결할 수 없는 문제가 있습니다.

사용자가 "why is my database slow?"라고 물으면, "query optimization"이나 "index tuning"과 직접적인 키워드 매치가 없습니다. BM25는 아무것도 찾지 못합니다. 에이전트가 실패합니다.

Vector Search는 의미를 이해합니다. "slow database"가 "performance optimization"과 관련되어 있다는 것을 압니다. 하지만 벡터는 반대 문제가 있습니다: 너무 퍼지(fuzzy)합니다. 에러 코드 PG-1234를 검색하면 벡터는 일반적인 에러 문서를 반환하지, 정확한 에러 코드가 있는 문서를 반환하지 않습니다.

해결책: 둘 다 사용하는 것입니다.

Query: error PG-1234

  • BM25 finds: 정확한 코드가 있는 문서
  • Vectors find: 일반적인 에러 문서
  • Hybrid finds: 정확한 코드 문서 ✓

Query: why is my database slow

  • BM25 finds: 없음 (키워드 매치 없음)
  • Vectors find: 성능 최적화 문서
  • Hybrid finds: 성능 문서 ✓

Query: fix connection timeout

  • BM25 finds: 타임아웃 설정 문서
  • Vectors find: 트러블슈팅 가이드
  • Hybrid finds: 둘 다, 관련성 순으로 정렬 ✓

이것이 모든 주요 AI 검색 시스템이 하이브리드 검색을 사용하는 이유입니다.

  • LangChain의 EnsembleRetriever는 Reciprocal Rank Fusion을 사용하여 BM25와 벡터를 결합합니다
  • Cohere Rerank는 BM25를 첫 번째 단계 리트리버로 권장합니다
  • Pinecone은 sparse와 dense 벡터를 결합하는 하이브리드 검색을 추가했습니다

Postgres에서 하이브리드 검색 구현하기

pgvector와 함께 Postgres에서도 가능합니다:

-- Reciprocal Rank Fusion을 사용한 하이브리드 검색
WITH bm25 AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY content <@> to_bm25query($1)) as rank
FROM docs LIMIT 20
),
vector AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $2) as rank
FROM docs LIMIT 20
)
SELECT id, 1.0/(60+bm25.rank) + 1.0/(60+vector.rank) as score
FROM bm25 FULL JOIN vector USING (id)
ORDER BY score DESC LIMIT 10;

키워드 + 의미. 하나의 데이터베이스에서.

Hybrid Search

핵심 요점

대부분의 애플리케이션은 별도의 검색 인프라가 필요하지 않습니다. BM25와 벡터 검색을 Postgres에서 직접 사용하면:

  • 운영 복잡도 감소: 별도 클러스터, 동기화 파이프라인, 추가 모니터링 없음
  • 일관성 보장: 단일 트랜잭션 내에서 데이터와 검색 인덱스 업데이트
  • 비용 절감: 수천 달러의 매니지드 검색 서비스 비용 제거
  • 개발 속도 향상: 이미 익숙한 Postgres SQL로 검색 구현
  • RAG 최적화: BM25의 정확성과 벡터의 의미 이해를 결합

물론 페타바이트급 로그 검색이나 밀리초 단위 레이턴시가 필요한 대규모 검색 서비스는 전문 솔루션이 필요합니다. 하지만 99%의 사용 사례—일반적인 앱의 문서 검색, 제품 카탈로그, 지원 티켓, RAG 파이프라인—에는 Postgres + pg_textsearch + pgvector가 충분하며, 훨씬 단순합니다.

References