수십억 건의 법률 문서에 AI를 연결하는 검색 인프라
메타데이터
- 원문 제목: Connect AI to Billions of Legal Documents — Simon Eskildsen, turbopuffer & Jacob Lauritzen, Legora
- 한국어 제목: 수십억 건의 법률 문서에 AI를 연결하는 검색 인프라
- 발신 채널: AI Engineer (운영 소스명: aiDotEngineer)
- 원본 업로드일: 2026-09-16
- 실행일: 2026-09-17
- 재생 시간: 20분 36초
- 원문 URL: https://www.youtube.com/watch?v=V-isu4eTHgw
- Video ID:
V-isu4eTHgw
📌 핵심 질문 / 법률 검색 인프라의 핵심 논점
==법률 AI의 검색 계층은 문서 수만 늘리는 방식으로 확장되지 않는다. 프로젝트와 관할권을 격리된 네임스페이스(namespace)로 만들고, 뜨거운 데이터는 메모리에, 차가운 데이터는 오브젝트 스토리지(object storage)에 두어야 비용·지연 시간·규제 격리를 함께 달성할 수 있다.==
- Legora는 프로젝트 단위 검색과 법률 리서치 검색이라는 서로 다른 부하를 처리한다.
- Postgres의 공격적인 파티셔닝은 활성 프로젝트와 유휴 프로젝트를 같은 파티션에 섞어 캐시를 고갈시켰고, P99 지연 시간을 100밀리초에서 20초까지 악화시켰다.
- 프로젝트별 네임스페이스는 유휴 데이터를 오브젝트 스토리지에 머물게 하면서 검색 단위·암호화 단위·물리적 격리 단위를 일치시켰다.
- 트리 기반 벡터 인덱스와 최소 왕복(round trip) 설계는 S3 수준의 지연 시간을 전제로도 수십억 벡터를 경제적으로 검색하게 한다.
검색 품질은 법률 추론의 출발점이다. 법률 문서는 일반 문서 검색보다 관할권, 상하위 법원, 시간적 효력, 예외 조항을 함께 고려해야 하므로 질의 하나가 여러 검색으로 팬아웃(fan-out)된다. 따라서 모든 데이터를 항상 빠른 계층에 올려두는 것보다 데이터의 접근 빈도와 규제 경계를 저장 구조에 반영하는 편이 실제 제품의 품질과 운영성을 높인다.
1. Legora가 검색해야 하는 법률 업무
Legora는 로펌과 사내 법무팀이 계약과 법률 자료를 다루도록 돕는 협업형 AI 플랫폼이다.
1.1. 법률 업무를 위한 협업 플랫폼
-
고객과 업무 범위
- 로펌: 로펌이 고객으로 참여해 계약 검토와 법률 조사 업무를 수행한다.
- 사내 법무팀: 기업 내부 법무팀도 같은 플랫폼에서 자료를 검토하고 계약을 만든다.
- 문서 규모: 계약 검토는 문서 몇 개를 읽는 수준을 넘어, 엄청난 양의 계약을 훑고 모두 적절한지 확인하는 작업까지 포함한다.
-
플랫폼에서 수행하는 일
- 계약 검토: 기존 계약을 대량으로 검토하고 문제가 없는지 확인한다.
- 계약 작성: 새로운 계약을 생성한다.
- 법률 리서치: 가능한 법률, 과거 판례, 규정을 찾아 특정 질문에 답하고 소송의 주장과 방어를 뒷받침한다.
- 협업: 여러 사람이 같은 법률 업무를 함께 진행한다.
-
제품 비유
- 복합적인 업무 도구: Legora는 Linear, Figma, Notion, GitHub가 법률 업무를 위해 합쳐진 것처럼 생각할 수 있다.
- 핵심 기반: 이 복합적인 사용자 경험을 지탱하는 공통 기반이 검색이다.
1.2. 두 가지 검색 부하
-
프로젝트 검색(Project Search)
- 프로젝트의 의미: Legora에서 프로젝트는 하나의 법률 업무를 수행하는 작업 단위다.
- 인수합병 사례: SpaceX가 Cursor를 인수하려 한다면, 해당 로펌과 진행하는 인수 업무 전체가 하나의 프로젝트가 된다.
- 자료의 구성: 고용 계약, 공급업체 계약, 인수 대상 기업과 관련된 각종 문서를 프로젝트에 업로드한다.
- 검색 경계: 검색은 해당 프로젝트 안으로 제한된다.
- 규모 범위: 한 프로젝트는 수십 개 문서부터 수백만 개 문서까지 가질 수 있다.
-
법률 리서치(Legal Research)
- 전역 코퍼스: 수많은 법률, 과거 사건, 규정 전체를 가로질러 검색하는 심층 리서치형 부하다.
- 질문 응답: 특정 법률 문제를 어떻게 처리할지 묻는 질문의 답을 찾는다.
- 소송 지원: 누군가를 고소하거나 피소된 상황에서 사건을 뒷받침할 자료를 찾는다.
- 검색 특성: 프로젝트 검색보다 훨씬 넓은 코퍼스를 대상으로 하며, 한 질의를 여러 관할권과 관련 자료로 확장한다.
2. 프로젝트 검색의 확장 과정과 실패
프로젝트 검색은 수십만 개 문서에서 수십억 개 문서로 커지는 동안 단일 검색 클러스터에서 데이터베이스 통합 구조까지 여러 단계를 거쳤다.
2.1. 단일 Elasticsearch 클러스터
-
초기 구조
- 하나의 검색 클러스터: 모든 검색 부하를 하나의 Elasticsearch 클러스터가 처리했다.
- 공용 저장소: 모든 테넌트(고객·사용자)의 원본 문서는 하나의 큰 Blob Storage에 보관했다.
- 검색 흐름: 같은 Elasticsearch에서 모든 테넌트의 인덱싱과 검색을 수행했다.
-
초기 장점과 한계
- 단순성: 시스템 하나만 운영하면 되어 시작하기 쉬웠다.
- 확장 압력: 고객과 문서가 늘면서 한 덩어리 구조가 데이터 거주지와 격리 요구를 충족하지 못했다.
2.2. 지역별 Elasticsearch 분리
-
데이터 거주지 요구
- 미국: 미국 고객은 처리가 미국 안에서만 이뤄지기를 원했다.
- 유럽: 유럽 고객은 EU 안에서만 처리되기를 원했다.
- 호주: 호주 고객은 호주 안에서만 처리되기를 원했다.
-
구조의 변화
- 지역별 복제: 기존 전체 구성을 EU, US, APAC 세트에 반복해 여러 Elasticsearch 클러스터로 늘렸다.
- 운영 오버헤드: 클러스터 수가 세 배 또는 네 배가 되어 관리 부담이 커졌지만, 필요한 데이터 거주지까지는 도달했다.
2.3. 엔터프라이즈의 물리적 격리와 고객 관리 키
-
대형 고객의 요구
- 독립 데이터베이스: 대형 은행과 세계 최대 로펌은 모든 데이터를 물리적으로 완전히 격리해 달라고 요구했다.
- 고객 관리 암호화 키(CMEK): 고객이 키 보관 시스템(key vault)에 암호화 키를 두고, Legora가 그 키를 사용할 권한만 받기를 원했다.
-
키 폐기의 의미
- 저장 데이터 암호화: Legora는 고객 키로 저장 상태(at rest)의 데이터를 암호화하고 복호화한다.
- 접근 철회: 고객이 키 사용 권한을 취소하면 Legora는 더 이상 데이터를 복호화할 수 없다.
- 통제권: 고객이 데이터를 읽을 수 있는 키를 직접 통제하므로, 데이터 접근을 실질적으로 차단할 수 있다.
2.4. Postgres로의 이동과 파티션 캐시 고갈
-
통합을 선택한 이유
- 기존 인프라 활용: 이미 온라인 트랜잭션 처리(OLTP)에 Postgres를 사용하고 있었다.
- 지역별 운영 경험: 여러 Postgres와 여러 Blob Storage를 운영하는 분리가 이미 마련되어 있어 검색도 같은 체계로 옮기기 쉬웠다.
- 시스템 수 축소: 검색까지 Postgres에 넣으면 별도의 검색 시스템을 하나 더 운영하지 않아도 됐다.
-
검색 구성
- 벡터 검색: PGVector의 DiskANN을 사용했다.
- 텍스트 검색:
tsvector를 사용했고 BM25는 사용하지 않았다. - 품질 비용: BM25를 잃으면서 검색 관련성이 조금 떨어졌다.
- 파티셔닝: 문서 청크를 저장한 테이블을 약 4,000개 파티션으로 매우 공격적으로 나눴다.
- 배치 방식: 프로젝트 키를 기준으로 각 프로젝트를 파티션에 빈 패킹(bin-packing)했다.
-
핫·콜드 프로젝트의 충돌
- 핫 프로젝트: 최근에 계속 작업하고 반복해서 질의하는 활성 프로젝트가 존재했다.
- 콜드 프로젝트: 한동안 집중적으로 작업한 뒤 다시는 열지 않는 프로젝트도 많았다.
- 잘못된 혼합: 파티션 배치 때문에 자주 쓰는 프로젝트와 거의 쓰지 않는 프로젝트가 같은 파티션에 들어갔다.
- 파티션 비대화: 파티션이 지나치게 커졌고, 질의할 때마다 Postgres가 파티션 전체를 메모리로 가져와 처리해야 했다.
-
캐시 스래싱(cache thrashing)
- 반복적인 메모리 적재: 한 파티션을 읽어 처리한 뒤 다른 파티션을 읽고, 다시 또 다른 파티션을 읽는 동작이 이어졌다.
- 활성 데이터 퇴출: 큰 콜드 파티션이 메모리에 들어오면서 직전에 사용한 핫 데이터가 캐시에서 밀려났다.
- 사용자 경험 악화: 검색과 수집(인제스트)의 P99 지연 시간이 약 100밀리초에서 20초로 치솟았다.
- 규모의 한계: 수억 개 문서까지 커지자 비용과 검색 성능이 모두 감당하기 어려운 수준으로 무너졌다.
3. turbopuffer와 네임스페이스 단위 설계
약 4억 개 문서에 도달했을 때 프로젝트마다 하나의 네임스페이스를 배정하면서 검색 단위, 저장 단위, 격리 단위가 일치하기 시작했다.
3.1. 프로젝트별 네임스페이스가 해결한 문제
-
구조적 변화
- 네임스페이스 하나: 프로젝트 하나를 turbopuffer의 네임스페이스 하나로 표현했다.
- 유휴 데이터의 위치: 사용하지 않는 프로젝트는 비싼 메모리 파티션이 아니라 Blob Storage에 그대로 머물렀다.
- 혼합 방지: 서로 다른 프로젝트가 거대한 Postgres 파티션에 섞이지 않아 핫·콜드 데이터 충돌이 사라졌다.
-
검색 및 운영 개선
- 진짜 BM25: BM25 기반 텍스트 검색으로 옮겨 검색 관련성이 좋아졌다.
- 더 낮은 지연: 검색 지연 시간이 크게 줄었다.
- 단일 클러스터 운영: Postgres처럼 여러 검색 데이터베이스를 따로 만들 필요 없이 하나의 turbopuffer 클러스터를 운영했다.
- 기존 Blob 활용: 데이터가 Blob 기반으로 동작하므로 이미 보유한 Blob Storage와 자연스럽게 연결됐다.
- 비용 절감: 콜드 프로젝트를 항상 고가 계층에 유지하지 않아 비용이 낮아졌다.
3.2. 오브젝트 스토리지 네이티브 쓰기 경로
-
기본 트레이드오프
- 직접 쓰기: turbopuffer는 디스크 복제나 Paxos 합의를 거치지 않고 오브젝트 스토리지에 직접 쓴다.
- 내구성 계층: 데이터의 기본 저장 위치는 S3 같은 오브젝트 스토리지다.
- 쓰기 지연 허용: 수백 밀리초가 걸리는 쓰기는 Shopify의 실시간 재고 예약처럼 즉각적인 트랜잭션에는 맞지 않지만, 검색 시스템에서는 읽기 성능이 충분히 좋다면 허용할 수 있다.
-
쓰기와 인덱스 생성
- WAL 기록: 쓰기는 Write-Ahead Log에 쌓인다.
- 설명용 예시: 실제 데이터베이스 포맷은 JSON이 아니지만,
1.json,2.json,3.json같은 기록이 이어지는 모습으로 이해할 수 있다. - 백그라운드 인덱싱: 이후 벡터 인덱스, 텍스트 인덱스, 컬럼형(columnar) 인덱스 등을 백그라운드에서 만든다.
- 읽기 최적화: 느린 오브젝트 스토리지 쓰기와 빠른 검색 읽기를 분리해 검색 부하에 맞춘다.
3.3. 네임스페이스의 읽기 경로와 왕복 최소화
-
읽기 경로
- 질의 도착: 질의는 테이블과 유사한 개념인 특정 네임스페이스에 도착한다.
- S3 디렉터리 비유: 네임스페이스는 다른 데이터와 분리된 S3 디렉터리처럼 동작한다.
- 노드 선택: 모든 노드가 읽기 복제본이지만, 캐시에 해당 데이터가 있을 확률이 가장 높은 노드에 친화성(affinity)을 두고 보낸다.
- 계층적 조회: 메모리 캐시를 먼저 확인하고, 그다음 NVMe SSD 캐시, 마지막으로 오브젝트 스토리지를 조회한다.
-
왕복(round trip)의 경제성
- 오브젝트 스토리지 지연: 약 1MB Blob에 대한 S3 P99 지연은 약 200밀리초다.
- 설계 목표: 한 번의 검색에서 오브젝트 스토리지와 주고받는 횟수를 가능한 한 줄여야 한다.
- 이상적인 횟수: 전체 경로를 약 세 번의 왕복으로 끝내는 것이 바람직하다.
- NVMe 활용: 높은 동시성에서 왕복 횟수를 줄이면 최신 NVMe 디스크의 처리 능력을 더 잘 활용할 수 있다.
4. 규제 환경을 위한 격리와 암호화
네임스페이스를 저장과 격리의 원자 단위로 선택하면 고객별 암호화·버킷·클라우드 계정 경계를 같은 설계 안에서 표현할 수 있다.
4.1. 키와 버킷의 네임스페이스 단위 분리
-
암호화 경계
- 키 하나씩 부여: 테이블 또는 네임스페이스마다 다른 암호화 키를 사용할 수 있다.
- 키 회수: 고객이 특정 키를 폐기하면 해당 네임스페이스의 데이터를 읽을 수 없게 된다.
- 재암호화: 필요하면 다른 키로 네임스페이스를 다시 암호화할 수 있다.
-
저장 경계
- 버킷 하나씩 부여: 네임스페이스마다 다른 S3, GCS, Azure Blob 버킷을 지정할 수 있다.
- 고객 클라우드 계정: 고객이 자기 클라우드 계정 안에 버킷을 두도록 구성할 수 있다.
- 대규모 버킷 운영: 고객에 따라 수천 개의 버킷에 네임스페이스를 배치할 수 있다.
- 구성 유연성: 버킷을 공유하거나 네임스페이스를 다른 위치로 옮기는 선택도 가능하다.
4.2. SSD 캐시를 끈 실험
-
고객의 물리적 격리 해석
- 휘발성이라는 내부 관점: turbopuffer는 NVMe SSD 캐시를 메모리처럼 휘발성 계층으로 간주했다.
- 고객의 관점: 규제 고객은 SSD 캐시도 데이터가 남는 물리적 저장 계층으로 봤다.
- 필요한 조치: SSD 캐시까지 암호화해야 완전한 저장 상태 격리를 충족할 수 있었다.
-
구현 대신 실험을 선택한 이유
- 예정된 해법: 처음에는 디스크 캐시에 암호화를 구현하려 했다.
- 단순한 검증: 실제 구현에 앞서 SSD 캐시를 아예 비활성화하고 성능을 측정했다.
- 결과: 메모리 캐시만 사용해도 Legora의 해당 부하에서 성능이 충분히 좋았다.
- 운영 결정: 복잡한 SSD 암호화를 즉시 넣는 대신 캐시를 끈 구성을 유지했다.
- 확장 방향: 향후 멀티테넌시 환경에서 SSD 캐시 암호화를 지원할 계획이지만, 네임스페이스 중심 구조는 이미 저장·암호화·격리를 멀티테넌트 방식으로 자연스럽게 맞춘다.
5. 법률 리서치와 수십억 벡터
법률 리서치는 프로젝트 검색보다 훨씬 큰 코퍼스와 높은 팬아웃을 요구하며, 관할권과 시간 관계를 보존하는 강한 필터링이 핵심이다.
5.1. 코퍼스 규모와 질의 폭발
-
성장하는 데이터
- 벡터 수: 법률 리서치 코퍼스는 100억 벡터를 향해 빠르게 증가하고 있다.
- 읽기 중심 부하: 쓰기보다 읽기 부하가 높고, 질의당 여러 검색을 발행해 QPS가 크게 튈 수 있다.
-
왜 팬아웃하는가
- 관할권 계층: 도시, 카운티, 주, 연방 법률처럼 서로 다른 권위 계층이 있다.
- 세계적 반복: 이런 계층 구조는 미국에만 있는 것이 아니라 각국 법 체계에 반복된다.
- 시간적 효력: 한 판사가 다른 곳의 판결을 뒤집을 수 있어 판결의 시간적 유효성을 함께 판단해야 한다.
- 예외와 특례: 새 규정이 기존 규정의 예외나 특수 사례를 만들 수 있으므로, 하나를 찾으면 관련 규정도 함께 찾아야 한다.
- 검색 확대: 이런 관계를 빠뜨리지 않으려면 질의 하나가 여러 관할권과 관련 문서로 폭발한다.
5.2. 관할권별 네임스페이스와 핫·콜드 분포
-
Elasticsearch의 비용 문제
- 전체 상주시킴: Elasticsearch에서는 전체 코퍼스를 검색 계층에 올려둬야 했다.
- 비용 급증: 법률 자료가 커지면서 모든 데이터를 항상 활성 상태로 유지하는 비용이 지나치게 커졌다.
-
turbopuffer의 분할 방식
- 관할권 단위: 서로 다른 관할권을 turbopuffer의 네임스페이스로 나눴다.
- 핫 데이터: EU 법률처럼 거의 모든 리서치 질의에서 자주 쓰이는 자료는 뜨거운 계층에 가깝게 둔다.
- 콜드 데이터: 덴마크 법처럼 상대적으로 질의 빈도가 낮은 작은 관할권은 Blob Storage에 둬도 된다.
- 허용 가능한 지연: 심층 리서치 부하에서는 콜드 Blob을 가져오는 데 500밀리초가 걸려도 전체 경험을 해치지 않는다.
- 롱테일 적합성: 대부분 차갑고 일부만 매우 뜨거운 네임스페이스 분포가 오브젝트 스토리지 중심 설계와 잘 맞는다.
6. 메모리 계층을 활용하는 검색 엔진
turbopuffer의 핵심은 데이터를 한 계층에 고정하지 않고 질의 빈도에 따라 레지스터, DRAM, NVMe SSD, 오브젝트 스토리지 사이로 이동시키는 데 있다.
6.1. “Puffing”과 비용·성능의 균형
-
계층의 경제성
- 메모리: 자주 질의하는 데이터일수록 메모리에 둘 때 경제성이 좋다.
- NVMe SSD: 메모리보다 저렴하면서 직접 많은 작업을 수행할 수 있는 중간 계층이다.
- 오브젝트 스토리지: 거의 사용하지 않는 데이터를 가장 저렴하게 보관하는 계층이다.
-
계층을 오가는 데이터
- 핫 네임스페이스: EU 법률처럼 반복적으로 검색되는 네임스페이스는 메모리와 NVMe 쪽으로 올라온다.
- 콜드 네임스페이스: 드물게 검색되는 자료는 아래 계층에 남는다.
- 설계의 지능: 어떤 계층에 얼마나 오래 둘지, 무작위 접근과 순차 접근을 어떻게 조합할지, 왕복을 어떻게 줄일지가 데이터베이스의 핵심 로직이다.
- 목표: 가능한 많은 데이터를 계층 아래쪽에 두면서도 필요한 핫 데이터는 위로 끌어올려 성능 대비 비용을 최적화한다.
6.2. 그래프 대신 트리로 만드는 벡터 검색
-
그래프 탐색의 문제
- 일반적인 방식: 벡터 검색은 벡터 사이의 관계를 그래프로 만들고 그래프를 탐색하는 방식으로 구현할 수 있다.
- 오브젝트 스토리지와의 충돌: 그래프 중심에서 시작해 노드를 하나씩 따라가면, 노드를 이동할 때마다 S3 왕복이 발생한다.
- 왕복 비용: 각 S3 접근의 P99가 약 200밀리초라면 무작위 탐색이 빠르게 비싸진다.
- 근본적 긴장: 그래프의 무작위·순차 접근 절충은 메모리와 레지스터에는 잘 맞지만, 깊은 저장 계층에서는 왕복 비용과 맞지 않는다.
-
계층적 클러스터 트리
- 벡터 공간 분할: 벡터를 거대한 좌표계의 점으로 보고 가까운 점들을 클러스터로 묶는다.
- 다단계 구성: 클러스터를 다시 클러스터로 묶고, 그 위에 또 클러스터를 만들어 전체 벡터를 트리로 조직한다.
- B-tree 비유: 전체 공간의 기하 구조를 근사하는 매우 복잡한 B-tree로 생각할 수 있다.
- 상위 노드: 매 검색마다 확인하는 루트와 상위 centroid는 모든 질의에 필요하므로 DRAM과 레지스터 가까이에 둔다.
- 하위 노드: 실제 판례, 긴 문서, 이미지가 있는 리프는 NVMe SSD 쪽에 두고 마지막에 약 1밀리초 왕복으로 가져온다.
- 경제성: 실제 문서 전체를 DRAM에 올리지 않아도 되므로 데이터베이스를 운영하는 가장 저렴한 방식에 가깝다.
-
적용 범위
- Legora: 수십억 법률 벡터를 다루는 리서치에 적합하다.
- 웹 검색: 수백억 또는 수천억 개 문서가 있는 웹 검색에도 적용할 수 있다.
- 실제 확장: turbopuffer에는 웹 전체의 거대한 일부를 인덱싱하는 고객도 있다.
6.3. 대규모 전문 검색의 내부
-
역색인 구조
- 토큰 해시맵: 큰 문서에서 토큰을 하나씩 추출해 토큰을 해시맵의 키로 넣는다.
- 문서 ID 집합: 각 키의 값에는 해당 토큰을 포함한 문서 ID 집합을 둔다.
- 질의 예시:
New York population을 검색하면new,york,population에 해당하는 세 위치를 해시맵에서 찾는다. - 교집합: 세 문서 ID 집합을 교집합해 세 단어와 관련된 문서를 찾는다.
-
BM25 점수
- 희소성 반영: 모든 단어가 같은 가치가 있는 것은 아니다.
- 예시:
York가 들어간 문서는 흔한new만 들어간 문서보다 검색 의도에 더 유용할 가능성이 크다. - 점수 계산: BM25는 이런 단어의 희소성과 문서 내 출현을 반영해 문서의 관련성을 점수화한다.
-
왕복과 메모리 대역폭
- 사전 조회: 먼저 검색어에 필요한 사전 부분의 위치를 찾는다.
- 목록 조회: 다음 왕복에서 해당 토큰들의 거대한 문서 ID 목록을 가져온다.
- 압축: 목록을 최대한 작게 압축해 저장·전송 비용을 낮춘다.
- 교집합 비용: 교집합할 때 메모리 대역폭을 적게 사용하도록 데이터 이동량을 줄인다.
- 조기 무시: 이미 높은 점수를 얻은 문서가 충분히 많다면, 낮은 점수만 만들 단어의 나머지 목록은 결과에 영향을 주지 않으므로 더 처리하지 않을 수 있다.
-
직관과 다른 결론
- 웹 규모 전문 검색: 대규모 텍스트 검색은 일반적인 예상과 달리 벡터 검색보다 더 어렵고 계산 비용도 더 크다.
- 이유: 긴 역색인 목록의 압축 해제, 교집합, 점수 계산, 왕복 최소화를 동시에 처리해야 하기 때문이다.
주요 발언 모음
“검색은 Legora에서 매우 중요하며 법률 추론의 핵심이다.”
“프로젝트가 사용되지 않으면 Blob에만 남아 있기 때문에 문제없이 처리할 수 있다.”
“네임스페이스가 분리의 원자 단위라면 모든 네임스페이스를 서로 다른 키로 암호화할 수 있다.”
“디스크 캐시를 끄고도 메모리 캐시만으로 성능이 충분히 좋아서 그 상태를 유지했다.”
“그래프는 메모리와 레지스터에서는 잘 맞지만, 더 아래 메모리 계층에서는 오브젝트 스토리지의 왕복 비용과 근본적으로 충돌한다.”
“핵심은 가능한 많은 데이터를 메모리 계층의 아래쪽에 두면서 최고의 성능 대비 비용 비율을 얻는 것이다.”
“4초가 걸리던 일을 이제는 제품 자체를 훌륭하게 만드는 데 집중할 수 있고, 확장성과 인프라에 매달리지 않아도 된다.”
핵심 데이터 & 수치
- 20분 36초: 전체 발표 길이다.
- 수십만 → 20억 개 문서: 프로젝트 검색이 확장된 범위다.
- 약 4,000개 파티션: Postgres에서 문서 청크를 나눈 규모다.
- 약 4억 개 문서: turbopuffer로 이전하기 시작한 시점의 문서 규모다.
- 100밀리초 → 20초: Postgres 파티션 캐시 스래싱으로 악화된 검색·수집 P99 지연이다.
- 약 200밀리초: 약 1MB Blob에 대한 S3 P99 지연이다.
- 약 3회: 오브젝트 스토리지를 포함한 검색 경로에서 목표로 삼는 왕복 횟수다.
- 약 500밀리초: 차가운 관할권 Blob을 가져오는 데 허용할 수 있는 지연이다.
- 약 1밀리초: 벡터 트리의 리프를 NVMe에서 읽는 마지막 왕복의 예시다.
- 100억 벡터: 법률 리서치 코퍼스가 향하는 규모다.
- 70개 이상에서 100~200개로 증가하는 테넌트: 별도 Elasticsearch를 고객마다 운영하기 어려워지는 규모다.
결론 및 시사점
- 업무 단위를 저장 단위로 맞춘다: 프로젝트와 관할권처럼 실제 질의 경계를 네임스페이스로 만들면 검색 격리와 데이터 수명 주기를 함께 관리할 수 있다.
- 핫·콜드 분포를 인프라에 반영한다: 모든 인덱스를 메모리나 SSD에 고정하지 말고, 유휴 데이터는 오브젝트 스토리지에 두며 필요한 순간에만 끌어올린다.
- 규제 요구를 데이터 모델에 내장한다: 네임스페이스를 암호화 키와 버킷의 원자 단위로 삼으면 고객 관리 키, 지역별 거주지, 물리적 격리를 같은 경계로 처리할 수 있다.
- 실험으로 복잡성을 제거한다: SSD 캐시 암호화를 바로 구현하기 전에 캐시를 끄고 측정해, 실제 부하에서 충분한 성능을 확인한 뒤 단순한 운영 경로를 선택할 수 있다.
- 저장 계층에 맞는 인덱스를 선택한다: 오브젝트 스토리지에서는 무작위 그래프 탐색보다 상위 정보를 메모리에 두고 리프를 아래 계층에 두는 트리 구조가 왕복 비용에 유리하다.
- 전문 검색을 과소평가하지 않는다: 토큰 사전, 압축된 역색인 목록, 교집합, BM25 점수 계산을 함께 처리해야 하므로 웹 규모의 텍스트 검색은 벡터 검색보다 더 비쌀 수 있다.
- 에이전트의 질의 폭발을 비용 모델에 넣는다: 단일 RAG 질의의 지연보다 에이전트가 20회 또는 100회 연속 질의할 때 누적되는 지연이 훨씬 중요하다.
- 검색 품질을 법률 추론의 기반으로 본다: 관할권 계층, 판결의 시간적 효력, 규정의 예외 관계를 보존하는 필터링이 검색 결과의 신뢰도를 결정한다.
- 운영 단순성도 제품 기능이다: 수십~수백 테넌트마다 별도 Elasticsearch를 만들지 않고 하나의 클러스터와 네임스페이스로 운영하면 제품 개선에 엔지니어링 시간을 돌릴 수 있다.
- 비용과 성능은 대립하지 않는다: 접근 빈도에 따라 데이터를 메모리 계층 위아래로 이동시키면 콜드 데이터의 저장 비용을 낮추면서 핫 데이터의 응답성도 유지할 수 있다.
핵심 요약 (20줄)
Legora는 로펌과 사내 법무팀이 계약 검토·작성·법률 리서치를 수행하는 협업형 AI 플랫폼이다.
Legora의 검색 부하는 프로젝트 내부 검색과 전 세계 법률 코퍼스를 가로지르는 법률 리서치로 나뉜다.
프로젝트는 수십 개부터 수백만 개 문서를 담는 실제 법률 업무의 단위다.
초기에는 모든 테넌트의 원본 문서와 검색을 하나의 Blob Storage와 Elasticsearch 클러스터에서 처리했다.
미국·EU·호주 데이터 거주지 요구가 생기자 같은 Elasticsearch 구성을 지역별로 세 배 또는 네 배 복제했다.
대형 은행과 로펌은 고객별 데이터베이스, 물리적 격리, 고객 관리 암호화 키를 요구했다.
기존 OLTP 인프라를 활용하려고 검색을 Postgres와 PGVector의 DiskANN·tsvector로 옮겼다.
약 4,000개 파티션에 프로젝트를 빈 패킹한 구조는 핫 프로젝트와 콜드 프로젝트를 한 파티션에 섞었다.
거대한 파티션을 반복해서 메모리에 적재하면서 캐시 스래싱이 발생했고 P99 지연이 100밀리초에서 20초로 늘었다.
약 4억 개 문서 시점에 프로젝트마다 turbopuffer 네임스페이스를 배정해 저장과 검색의 경계를 맞췄다.
프로젝트별 네임스페이스는 진짜 BM25, 더 나은 관련성, 더 낮은 지연, 단일 클러스터 운영을 제공했다.
사용하지 않는 프로젝트는 값싼 오브젝트 스토리지에 머물러 핫·콜드 데이터 혼합 문제와 비용을 줄였다.
turbopuffer는 디스크 복제나 Paxos 없이 오브젝트 스토리지에 직접 쓰고 백그라운드에서 검색 인덱스를 만든다.
네임스페이스는 서로 다른 키와 버킷을 지정할 수 있어 고객별 암호화·지역·물리적 격리를 표현한다.
SSD 캐시를 끄고도 메모리 캐시만으로 충분한 성능을 확인해 복잡한 캐시 암호화를 뒤로 미뤘다.
법률 리서치는 100억 벡터를 향해 커지고 질의 팬아웃 때문에 QPS가 크게 튀는 읽기 중심 부하다.
관할권 계층, 판결의 시간적 효력, 규정의 예외를 보존하려면 질의를 여러 관련 자료로 확장해야 한다.
자주 검색되는 EU 법률은 메모리에 가깝게, 드물게 검색되는 덴마크 법률은 Blob에 두는 방식이 비용 효율적이다.
벡터를 계층형 클러스터 트리로 조직하면 상위 centroid는 DRAM에, 실제 문서는 NVMe에 두어 S3 왕복을 줄일 수 있다.
웹 규모 전문 검색은 역색인 목록의 압축·교집합·BM25 점수 계산 때문에 벡터 검색보다 더 어렵고 비쌀 수 있다.
