한빛미디어 서평단 <나는리뷰어다> 활동을 위해서 책을 협찬받아 작성된 서평입니다.

 

어느새 RAG는 손쉽게 구현할 수 있는 것처럼 여겨지지만, 기반 지식없이는 효율적으로 원하는 바를 구현하기가 쉽지 않다고 생각한다. 물론 RAG를 보다 쉽고 효과적으로 구축할 수 있는 다양한 오픈소스 프레임워크들이 나오고 있지만, 그래도 기반 동작을 익히는 데에는 이유가 있다. 이번에 읽은 책 <벡터 데이터베이스(한빛미디어, 니틴 보르완카르 저) >는 RAG의 핵심 구성요소인 임베딩과 벡터DB의 기본을 다져주는 책이다.

 

한빛에서 번역서가 나오기 전에 원서로 앞부분을 읽었던 터라, 2장 임베딩과, 5장 PGVector 이용한 논문 검색 시스템 구축, 6장 Ollama로 RAG 시스템 구축하기 관련 내용을 중점적으로 읽었다. 역시 한글이 쏙쏙 쉽고 빠르게 읽힌다.

 

먼저 임베딩부터 이야기해보자면, 문장을 구성하는 토큰을 다차원 공간에 매핑함으로써 유사한 어휘(토큰)들이 비슷한 공간에 위치하게 만드는 것이다. 좀 더 정확히 기술하자면 비정형 데이터를 고차원 벡터 공간의 숫자 표현으로 변환하는 것이다.  의미나 맥락이 비슷한 데이터는 이 벡터 공간에서도 상대적으로 가까운 위치에 배치된다. 가장 유명한 그림이 아래 그림이다. Word2Vec이 보여준 흥미로운 결과중 하나가 단어 사이의 의미 관계를 벡터 연산으로 어느정도 드러난다는 점이다. 인간이 사용하는 언어의 관계가 군집으로 표현되고, 그 군집간 벡터를 상징하는 단어가 있다는 사실이 놀랍다. 이걸 처음 발견한 초기 연구자들은 어떤 감정이었을까?

예전에 검색엔진 개발 팀에서 형태소 분석 결과를 바탕으로 역색인 파일을 만들고, 동의어 검색을 구현하기 위해 사전을 만들고, 사용자 질의를 어떻게 확장할 것인지를 정의하기 위해 온갖 짓들을 다했는데, 그때와 비교하면 요즘 임베딩과 벡터DB로 구현된 시맨틱 검색은 정말 엄청난 상전벽해의 느낌을 준다.

왕 - 남성 + 여성 = 여왕, 출처: 1장 벡터데이터베이스 소개, 37쪽

 

이 관계를 Word2Vec, Doc2Vec, 트랜스포머 모델까지  설명하면서 벡터데이터베이스의 등장 배경을 설명하는 것이 1장과 2장의 주요 내용이다. RDB나 NoSQL로도 벡터를 저장할 수 있지만, 코사인 유사도나 최근접 이웃검색을 효과적으로 수행하기엔 역부족이다. 그래서 결국 HNSW나 ANN 인덱스 등으로 벡터 검색에 특화된 벡터DB 사용을 고려할 수 밖에 없다.  개인적으로는 별도의 벡터DB를 도입하기 전에 일단 pgvector를 검토한다. FAISS, SQLite3-VSS,Chroma 등이 있지만, 메타 데이터와 벡터를 하나의 트랜잭션 경계 내에서 관리한다는 장점도 크고, 별도의 운영 시스템을 추가하기 않아도 되므로 pgvector를 사용하는 것이 정답이라 생각한다.

 

책의 전체적인 얼개는 벡터DB의 필요성 -> 개인이 사용하는 RAG 유틸 -> 서비스로 구축하기 -> 앞으로의 개선점(벡터 질의 언어)로 구성된다. 1,2장은 벡터DB의 등장 배경, 필요성을 언급했다면 3장에서는 벡터DB의 유사도 검색을 위한 색인(인덱싱) 기법을 설명한다. 정확도를 약간 희생하더라도 검색속도를 높이기 위한 인덱싱 기법을 살펴보면 벡터DB가 단순히 벡터 데이터를 저장하는 것이 아니라는 점을 알 수 있다. 5장에서는 arXiv 논문을 다운로드 받아서 텍스트를 추출하고, 이를 청크로 나눠서 벡터화한 다음, 조회하는 시스템을 Pgvector를 기반으로 구현한다. 6장은 Sqlite3를 사용하지만 Ollama를 통한 로컬 임베딩 API를 사용하는 것에 대한 내용이 주다. 임베딩 모델은 대체로 크기가 작은 편이므로 충분히 로컬 검색 환경을 구성하기가 쉽다. 이후 장에서 다루는 내용은 개인이 사용하는 것이 아니라 서비스로 구축하기 위한 스키마 설계부터 데이터 처리 파이프라인을 다룬다.

 

따라서 RAG를 한번이상 고민해 봤다면 1장부터 3장까지는 상식 측면에서 쉽게 읽을 수 있고, 4장부터는 서비스를 구축하는 과정으로 이해해서 8장까지 파이썬 코드와 함께 책을 읽으면 된다. 전체적으로 벡터DB와 RAG에 대하여 좀 더 자세히 이해하는데 도움이 되는 책이다. 다시 한번 RAG에서 어려운 부분은 벡터 검색 구현이 아니라, 어떤 데이터를 수집하여 청크를 어떻게 나누고, 어떻게 임베딩하며, 메타 데이터를 관리할 것이며, 검색 결과를 평가하는 것이  더 중요하다는 점을 느낀다. 결국 RAG도 데이터 엔지니어링의 도메인에 속하는 것 같다.

 

파이썬 코드를 사용하지만, 그렇게 난이도가 있는 코드를 사용하지 않으므로(또한 파이썬도 직관적인 언어이므로) 개발자 뿐만 아니라 AI 서비스 기획자도 앞부분부터 차례대로 읽어나가면서 개념을 이해할 수 있는 책이라 생각된다. RAG 시스템을 구축하겠다 이런 의도를 가진 팀이라면 반드시 학습해야 장점과 단점, 한계점을 공유할 수 있을 것이다.

 

단점은..결국 원서인 까닭에 한글 처리에 대한 내용이 부족하다. 책에서는 all-MiniLM-L6-v2 모델을 주로 사용하는데, 이 모델은 한국어 처리가 부실한 것으로 알려져 있다. 따라서 다중언어 버전인 paraphrase-multilingual-MiniLM-L12-v2이나 한국어에 특화된 KURE모델, Ko-SBERT 모델 등으로 예제가 보완되었으면 어떨까 하는 아쉬움이 있다. LLM이 등장하면서 한국어 처리의 벽이 많이 무너지긴 했지만, 그래도 벽은 여전히 벽이다.

 

9월 벡터DB와 함께 떠난 여행은 재미있었다.

 

 

 

 

 

반응형

제미나이로 정리한 기술 원고입니다.

긱뉴스에 올라온 글과 댓글, 지인들이 댓글에서 언급한 키워드를 중심으로 정리하라고 했더니 아래와 같은 글이 작성되었습니다. 정말 공부하기 좋은 세상입니다.!

 

리눅스 커널 변화와 PostgreSQL 성능 분석 보고서

1. 핵심 개념 상세 설명

SpinLock (스핀락)

스핀락은 임계 구역(Critical Section)에 진입하기 위해 대기하는 스레드가 잠들지 않고 루프를 돌며 권한을 획득할 때까지 계속 확인하는 동기화 기법

  • 동작 방식:
    1. 스레드가 자원 사용을 위해 락(Lock)을 요청합니다.
    2. 락이 이미 사용 중이라면, 스레드는 대기 큐로 들어가는 대신 CPU를 계속 사용하며(Spinning) 락의 해제 여부를 체크합니다.
    3. 락이 해제되는 순간 즉시 자원을 점유합니다.
  • 특징:
    • Context Switch 없음: 스레드가 잠들고 깨어나는 오버헤드가 없습니다.
    • 비결정적 대기: 대기 중에도 CPU를 100% 점유하므로, 대기가 길어지면 시스템 성능이 급락합니다.
  • 장단점:
    • 장점: 임계 구역이 매우 짧을 때, 잠들고 깨어나는 시간보다 그냥 기다리는 시간이 짧아 효율적입니다.
    • 단점: 락을 쥐고 있는 스레드가 지연되거나 선점(Preemption)당하면, 기다리는 모든 스레드가 CPU를 낭비하며 '미친 듯이 도는' 현상이 발생합니다.

rseq (Restartable Sequences)

리눅스 커널이 제공하는 최신 기술

유저스페이스 코드가 원자적(Atomic)으로 실행되어야 하는 구간에서 커널에 의해 중단(선점)되었는지를 감지

  • 도입 배경: 멀티 코어 환경에서 'CPU별 데이터(Per-CPU data)'에 접근할 때 락 없이 빠르게 처리하기 위해 고안되었습니다.
  • 동작 원리:
    1. 프로세스가 특정 코드 구간(rseq 구간)을 시작함을 커널에 알립니다.
    2. 만약 이 구간을 실행하다가 커널이 스레드를 중단시키고 다른 일을 시킨다면(Preemption), 커널은 이 사실을 기록합니다.
    3. 스레드가 다시 CPU를 잡았을 때, 구간 중간부터 이어서 하는 것이 아니라 구간의 처음으로 돌아가 다시 실행(Restart)합니다.
  • 효과: 락을 쓰지 않고도 데이터의 일관성을 유지할 수 있으며, 이번 PostgreSQL 사례처럼 "락을 쥐고 잠드는" 최악의 시나리오를 방지할 수 있는 대안으로 제시되었습니다.
비교 항목 SpinLock Mutex rseq / Lock-free
대기 방식 계속 회전 (CPU 소모) 잠듦 (CPU 반납) 재시도 또는 원자적 처리
권장 상황 매우 짧은 임계 구역 긴 대기 시간 예상 시 고성능 멀티코어 환경
오버헤드 낮음 (즉시 반응) 높음 (문맥 교환) 매우 낮음 (난이도 높음)

2. 커널 스케줄링 및 선점 (Preemption) 관련

리눅스 커널이 CPU 자원을 여러 프로세스에게 나누어 주는 방식에 대한 용어입니다.

  • 선점 (Preemption): 실행 중인 스레드의 의사와 상관없이 OS 스케줄러가 강제로 CPU 사용권을 뺏어 다른 스레드에게 주는 행위입니다.
  • PREEMPT_NONE: "나 건드리지 마" 모드입니다. 스레드가 스스로 I/O를 기다리거나 잠들기 전까지는 커널이 웬만해서는 실행을 중단시키지 않습니다. 서버 환경에서 처리량(Throughput)을 극대화할 때 유리합니다.
  • PREEMPT_FULL / LAZY: "언제든 비켜줄게" 모드입니다. 응답 속도(Latency)는 빨라지지만, 자주 흐름이 끊겨 전체적인 일 처리량은 줄어들 수 있습니다. 리눅스 7.0에서 NONE 모드가 사라지면서 이번 성능 저하의 도화선이 되었습니다.
  • Restartable Sequences (rseq): 커널이 유저스페이스(PostgreSQL 등)에 "방금 네 실행 흐름이 중간에 끊겼었어"라고 알려주는 기능입니다. 이를 통해 중단되었던 작업을 처음부터 다시 안전하게 실행할 수 있게 돕는 최신 기술입니다.

3. PostgreSQL 성능 회귀 사건 분석 (통합)

최근 리눅스 7.0(내부 커널 버전 기준)에서 발생한 이슈는 하드웨어와 OS, 소프트웨어의 설계 철학이 충돌한 대표적 사례입니다.

📍 사건의 발단: 커널 정책의 변화

  • PREEMPT_NONE 제거: 과거 서버용 리눅스의 기본값이었던 "자발적 양보 외엔 절대 끊지 않음" 모드가 사라졌습니다.
  • PREEMPT_LAZY 도입: 처리량을 고려하되 필요시 선점하는 절충안이 기본이 되었으나, PostgreSQL의 전역 스핀락 구조와 상극을 일으켰습니다.

📍 문제의 메커니즘 (Cascading Failure)

  1. 스핀락 점유: PostgreSQL의 백엔드 프로세스가 공유 버퍼 풀 접근을 위해 단일 전역 스핀락(s_lock)을 획득합니다.
  2. 마이너 페이지 폴트: 락을 쥔 상태에서 메모리에 접근했는데, 해당 페이지가 물리 램에 매핑되지 않아 커널이 개입합니다.
  3. 선점 발생: 페이지 폴트 처리 중, 커널이 스레드를 잠시 중단(Preemption)시킵니다.
  4. 폭주: 락을 쥐고 있는 놈이 자리를 비우자, 나머지 수백 개의 CPU 코어들이 락을 얻으려고 스핀락 루프를 돌며 CPU 점유율을 100%까지 끌어올립니다.

📍 해결책 및 트레이드오프

해결 방법 주요 내용 장점 단점
Huge Pages 메모리 페이지를 4KB → 2MB/1GB로 확대 페이지 폴트 발생 빈도 감소 메모리 사전 할당 필요
rseq 도입 선점 시 재시작 메커니즘 선점 병목 해소 대대적 코드 수정 필요
Direct I/O OS 캐시 우회 중복 캐싱 방지 성능이 케바케임

4. 용어 사전 요약

  • Direct IO
    • 운영체제(OS)의 페이지 캐시(Page Cache)를 거치지 않고 데이터베이스 엔진이 저장 장치(Disk)와 직접 데이터를 주고받는 방식
    • 보통 OS는 성능 향상을 위해 디스크에서 읽은 데이터를 메모리에 임시로 저장(캐싱)합니다. 하지만 DBMS는 자체적으로 더 정교한 버퍼 풀(Buffer Pool) 관리 시스템을 가짐
    • 중복 캐싱(OS와 DBMS가 같은 데이터를 이중으로 들고 있는 현상)을 방지하고 CPU 사용량을 줄일 수 있음. 다만 OD가 제공하는 최적화 기능을 사용하지 못하므로 DBMS가 데이터를 효율적으로 관리하지 못하면 성능이 떨어질 수 있음
  • 네할렘 (Nehalem):
    • 메모리 컨트롤러를 CPU 내부로 통합. 이로 인해 NUMA 구조를 확립한 아키텍처.
    • 2008년 말 인텔이 출시한 CPU 마이크로 아키텍처. 2009년 서버 시장에 보편화
  • SMP( ymmetric Multi-Processing, 대칭형 다중 처리):
    • 모든 CPU가 동일한 메모리 접근 권한을 갖는 대칭형 구조.
    • CPU 개수가 늘어날수록 메모리로 가는 통로(Bus)에 병목 현상이 생겨 성능 확장 어려움 -> NUMA 등장
  • NUMA( Non-Uniform Memory Access , 비균할 메모리 접근): 
    • CPU별 독립 메모리 구조. 멀티 소켓 서버의 표준. 네할렘 이후의 멀티 소켓 서버는 대부분 이 구조를 따름
    • 각 CPU(소켓)마다 자신만의 전용 메모리 영역을 가짐. 자기 옆 메모리 접근시에는 빠르지만, 다른 CPU에 붙은 메모리 접근하려면 버스로 주고 받아야 하므로 속도가 느려짐.
    • NUMA를 지원하지 않는 소프트웨어는 멀리 떨어진 메모리만 쓰다가 성능이 떨어질 수 있음.
  • CCIX(Cache Coherent Interconnect for Accelerators):
    • CPU와 가속기 (GPU, FPGA 등) 간 캐시 일관성을 유지하면서 아주 빠른 속도로 데이터를 주고받기 위한 차세대 인터페이스.
    • NUMA의 데이터 전송 속도 한계를 극복하고, 서로 다른 제조사의 칩들이 마치 하나의 메모리를 쓰는 것처럼(캐시 일관성) 통신
    • 최근에는 인텔이 주도하는 CXL(Compute Express Link) 기술이 이 분야의 대세가 되면서, NUMA가 가졌던 물리적 한계를 극복하는 메모리 풀링(Memory Pooling) 시대로 이행되고 있
  • TLB(Translation Lookaside Buffer):
    • 가상 주소를 실제 주소로 바꾸는 맵이 담긴 변환용 초고속 캐시. TLB가 작으면 자주 매핑 정보를 구성해야 해서 속도 느려짐. Huge Pages 사용 시 효율 급증.
  • Huge Page
    • 기본 메모리 페이지 크기를 4K에서 2M/1GB로 뻥튀기. 페이지 수가 줄어들어 페이지 폴트 발생횟수가 줄어들면서 TLB 효율이 높아짐.
  • 페이지 폴트(Page Fault)
    • 프로그램이 메모리 주소를 불렀는데, 실제 물리 램에 아직 준비가 안되었을 때 발생하는 이벤트
    • 커널은 급히 램을 할당해줘야 하므로 아주 미세한 지연이 발생.

5.  성능 분석 및 벤치마크 관련

  • TPS (Transactions Per Second): 초당 처리되는 트랜잭션 수로, DB의 심박수와 같습니다. 이번 사례에서는 9.8만에서 5만으로 반토막 났습니다.
  • pgbench: PostgreSQL의 성능을 측정하는 표준 도구입니다.
  • perf: 리눅스 커널의 성능 분석 도구입니다. CPU가 어떤 함수에서 시간을 가장 많이 쓰는지 초 단위로 쪼개서 보여줍니다. 이번 분석에서 s_lock(스핀락)이 CPU의 55%를 먹고 있다는 범인을 잡아냈습니다.

 

반응형

+ Recent posts