Pinecone 아키텍처
Pinecone 아키텍처
Pinecone이 데이터를 어떤 구조로 보관하고, 쓰고, 읽는지 궁금할 때가 있어요. 결론부터 말하면 Pinecone의 아키텍처는 두 층으로 나눠져요. 데이터를 정리하는 리소스 계층(리소스 계층 hierarchy)과, 데이터를 저장하고 서빙하는 서버리스 인프라예요. 이 두 층이 어떻게 연결돼 있는지 하나씩 따라가 볼게요.
리소스 계층 (Resource hierarchy)
Pinecone 데이터는 리소스 계층 구조로 정리돼요. 조직(Organization)이 프로젝트(Project)를, 프로젝트가 인덱스(Index)를, 인덱스는 다시 네임스페이스(Namespace)로 나뉘고, 그 네임스페이스가 레코드(Record)나 문서(Document)를 담아요. 즉 조직 → 프로젝트 → 인덱스 → 네임스페이스 → 레코드/문서 순으로 겹겹이 쌓이는 구조예요. 각 객체의 정의가 궁금하면 핵심 용어 문서를 보면 돼요.
서버리스 인프라 (Serverless infrastructure)
Pinecone은 AWS, GCP, Azure 같은 클라우드 플랫폼 위에서 관리형 서비스로 운영돼요. 요청을 보내면 API 게이트웨이(API gateway)가 그 요청을 전역 컨트롤 플레인(control plane) 또는 지역 데이터 플레인(data plane)으로 라우팅해요. 모든 벡터 데이터는 고효율의 분산 객체 스토리지(object storage)에 저장돼요.
API 게이트웨이
모든 Pinecone 요청에는 특정 프로젝트에 할당된 API 키가 포함돼요. API 게이트웨이는 먼저 API 키를 검증해 그 프로젝트에 접근 권한이 있는지 확인해요. 검증이 끝나면 요청을 어떻게 처리할지에 따라 전역 컨트롤 플레인(프로젝트·인덱스 관리용)이나 지역 데이터 플레인(데이터 읽기·쓰기용)으로 라우팅해요.
컨트롤 플레인 (Control plane)
전역 컨트롤 플레인은 프로젝트와 인덱스 같은 조직 리소스를 관리해요. 이런 객체를 추적하기 위한 전용 데이터베이스를 사용하고, 결제와 사용자 관리를 담당하며 여러 지역에 걸친 작업을 조정해요.
데이터 플레인 (Data plane)
데이터 플레인은 특정 클라우드 지역 안의 인덱스에서 레코드를 읽고 쓰는 모든 요청을 처리해요. 각 인덱스는 하나 이상의 논리적 네임스페이스로 나뉘고, 모든 데이터 읽기·쓰기 요청은 특정 네임스페이스를 대상으로 해요.
Pinecone은 쓰기와 읽기 작업을 서로 다른 경로로 분리해서, 각각 수요에 따라 독립적으로 확장돼요. 이 분리 덕분에 쿼리가 쓰기를 느리게 만들지 않고, 쓰기도 쿼리를 느리게 만들지 않아요.
객체 스토리지 (Object storage)
서버리스 인덱스의 각 네임스페이스에서 Pinecone은 레코드를 slab이라는 불변 파일로 정리해요. slab은 빠른 쿼리를 위해 최적화되어 있고, 사실상 무한한 확장성과 높은 가용성을 제공하는 분산 객체 스토리지에 저장돼요.
쓰기 경로 (Write path)
레코드를 추가·업데이트·삭제하는 쓰기 요청을 보내면 데이터 플레인이 먼저 고유 시퀀스 번호(LSN)와 함께 요청 세부 정보를 요청 로그(request log)에 기록해요. 이 덕분에 모든 작업이 올바른 순서로 실행되고 인덱스 상태를 추적할 수 있어요. Pinecone은 바로 200 OK 응답을 돌려줘서 쓰기가 내구성 있게 보장되고 유실되지 않음을 알려줘요. 그다음 시스템이 백그라운드에서 쓰기를 처리해요.
인덱스 빌더(index builder)는 쓰기 데이터를 memtable이라는 인메모리 구조에 저장해요. 여기에는 벡터 데이터, 첨부한 메타데이터, 시퀀스 번호가 포함돼요. 레코드를 업데이트하거나 삭제하면 쿼리 시에 이전 버전을 어떻게 처리할지도 함께 추적해요.
인덱스 빌더는 주기적으로 memtable의 데이터를 영구 스토리지로 옮겨요. 객체 스토리지에서 데이터는 slab이라는 불변 파일로 정리돼서, 쿼리 성능에 최적화돼요. 작은 slab은 자원 부담이 적으면서도 좋은 성능을 내는 빠른 인덱싱 기법을 쓰고, slab이 커지면 규모에서 더 나은 성능을 주는 정교한 방법을 쓰는 큰 slab으로 병합해요. 이 적응형 과정은 각 slab의 쿼리 성능을 최적화하면서, 비용이 더 드는 인덱싱 비용을 네임스페이스의 수명에 걸쳐 분산시켜요.
읽기 작업은 항상 memtable을 먼저 확인하기 때문에, 방금 쓴 데이터도 영구 스토리지로 옮기기 전에 바로 검색할 수 있어요.
읽기 경로 (Read path)
검색 쿼리를 보내면 데이터 플레인이 먼저 요청을 검증하고 속도·객체 제한 같은 시스템 한도를 초과하는지 확인해요. 그다음 쿼리 라우터(query router)가 어떤 slab에 관련 데이터가 있는지 파악해 쿼리를 적절한 실행기(executor)로 라우팅하고, 아직 영구 스토리지로 안 옮겨진 최근 데이터를 찾아 memtable도 검색해요.
각 쿼리 실행기는 담당 slab을 검색해 가장 관련성 높은 후보를 쿼리 라우터에 돌려줘요. 쿼리에 메타데이터 필터가 있으면, 실행기는 최선의 매칭을 찾기 전에 조건에 맞지 않는 레코드를 먼저 제외해요.
대부분의 경우 slab은 메모리나 로컬 SSD에 캐시되어 있어 쿼리가 매우 빠르게 처리돼요. slab이 캐시에 없으면(첫 접근이거나 최근에 안 쓰인 경우) 실행기가 객체 스토리지에서 가져와 이후 쿼리를 위해 캐시해요. 마지막으로 쿼리 라우터가 모든 실행기의 결과를 합치고 중복을 제거한 뒤 memtable의 결과와 병합해 최종 최선 매칭 집합을 돌려줘요.