fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agent 페이지로 이동
    • claude-code 페이지로 이동
    • harness 페이지로 이동
    • llm 페이지로 이동
    • ops 페이지로 이동
    • practice 페이지로 이동
  • algorithm 페이지로 이동
    • Alias Method: 가중치 랜덤 선택을 전처리 O(n), 추출 O(1)로 바꾸기
    • Welford's Online Algorithm: 값을 저장하지 않고 평균과 분산 계산하기
  • architecture 페이지로 이동
    • distributed-systems 페이지로 이동
    • domain 페이지로 이동
    • evolution 페이지로 이동
    • patterns 페이지로 이동
  • database 페이지로 이동
    • milvus 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • qdrant 페이지로 이동
    • redis 페이지로 이동
    • vespa 페이지로 이동
    • DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 한국어 형태소 분석기 Nori vs Lindera — OpenSearch에서 Milvus로 갈 때 어휘 검색은 유지되나
    • 정규화와 역정규화: 무결성과 조회 비용 사이의 선택
    • 벡터 DB 5종, 아키텍처는 어떻게 다른가
    • 벡터 DB 5종을 실제로 벤치마크했다 — 같은 recall에서 QPS는 얼마나 갈리나
    • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa · pgvector 비교
    • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • Envoy Proxy
    • Graceful Shutdown
    • 종료 신호는 어디서 멈추는가
  • http 페이지로 이동
    • HTTP Connection Pool
    • HTTPS와 TLS: 핸드셰이크, 인증서, 종료 지점
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • testing 페이지로 이동
    • Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • 로그에 traceId 남기기 — MDC 부터 OpenTelemetry 까지
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 클러스터 아키텍처: Broker, KRaft Controller와 복제
    • Kafka 기본 개념: Topic, Partition, Offset과 Segment
    • Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • Kafka 파티션·리밸런스·컨슈머 지연 운영
    • Kafka를 로그로 이해하기: 복제, 재처리, 상태와 데이터 통합
    • Spring Kafka 컨슈머 오프셋 커밋과 트랜잭션 정렬: AckMode, manual ack, 멱등 처리
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • SSH forced command 로 배포 키가 실행할 수 있는 명령 제한하기
  • mlops 페이지로 이동
    • llm-serving 페이지로 이동
    • model-router 페이지로 이동
    • Python CUDA 버전 생태계 — nvidia-smi, nvcc, pip, conda가 다 다른 버전을 말하는 이유
    • GPU 컨테이너의 CUDA 버전 호환성 — nvidia-smi부터 이미지 다이어트까지
    • Kubernetes GPU 노드에서 /run tmpfs가 꽉 차서 Pod가 안 뜰 때
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • 한 GPU 를 여러 프로세스가 나눠 쓰기 — Time-Slicing 과 MPS
  • network 페이지로 이동
    • Connection reset by peer는 누가 보낸 걸까 — 리버스 프록시 홉마다 TCP 연결은 따로 논다
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • observability 페이지로 이동
    • Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • K8s 위 Spring Boot 앱의 메트릭 수집
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • ThreadLocal 에서 contextvars 로 — Python 의 요청 컨텍스트 전파
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS가 줄지 않는 이유: CPython, gc.collect(), malloc_trim
  • rabbitmq 페이지로 이동
    • RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • resume 페이지로 이동
    • 경험 및 경력기술서
    • 김병태 경력기술서
    • 김병태 포트폴리오
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • thinking 페이지로 이동
    • 자기주도 학습: 질문에서 시작해 글로 검증하기
    • 좋은 일을 넘어 중요한 일을 하는 법
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • Glossary↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/AI/Beam search로 GraphRAG 경로…
aidb

Beam search로 GraphRAG 경로 폭발을 제한하기

GraphRAG 검색에서 어려운 부분은 그래프를 얼마나 깊이 탐색하느냐가 아니다. 각 홉에서 어떤 경로를 일찍 버리느냐가 실제 문제다. 이 글은 Cypher, 제약, 쿼리 계획으로 검색 안정화하기와 벡터·전문·그래프 탐색을 결합한 하이브리드 검색이 다루지 않는 부분인 경로 수 제한과 경로 점수화를 정리한다. 토스증권 ML 엔지니어가 쓴 토스증권 추천과 검색...

2026.10.02·4 min read·9 views

GraphRAG 검색에서 어려운 부분은 그래프를 얼마나 깊이 탐색하느냐가 아니다. 각 홉에서 어떤 경로를 일찍 버리느냐가 실제 문제다.

이 글은 Cypher, 제약, 쿼리 계획으로 검색 안정화하기와 벡터·전문·그래프 탐색을 결합한 하이브리드 검색이 다루지 않는 부분인 경로 수 제한과 경로 점수화를 정리한다. 토스증권 ML 엔지니어가 쓴 토스증권 추천과 검색은 어떻게 진화하고 있을까?(황호익, 토스 기술 블로그, 2026-08-26)의 그래프 RAG 부분이 사례다. 발표 영상은 테크톡톡 2026 | ML Engineer - 토스증권 추천과 검색은 어떻게 진화하고 있을까?다. 수치와 파라미터 이름은 글 판본에서 확인한 값이다.

검색기의 두 단계

원문의 그래프 검색기는 두 단계로 나뉜다.

  1. 시작 지점 찾기: 일반 검색 파이프라인으로 질문과 관련된 엔티티 후보를 찾는다.
  2. 그래프 탐색: 시작 지점에서 출발해 연결된 노드를 따라 후보 경로를 확장하고 선별한다.

모든 관계를 따라갈 수는 없으므로 어떤 규칙으로 탐색할지가 검색 알고리즘의 핵심이 된다. 이 글의 범위는 사용자 질문과 관련된 서브그래프를 찾아 반환하는 검색 모듈이다. 엔티티 추출과 연결은 다루지 않는다.

홉마다 후보 경로가 자릿수로 늘어난다

원문의 지식 그래프에서는 의미 있는 서브그래프를 만들려면 3홉 이상 탐색해야 했다. 아무 제한 없이 하나의 노드에서 출발하면 후보 경로 수는 다음과 같았다.

홉후보 경로 수
1홉1,105건
2홉134,520건
3홉49,241,786건

이 후보를 모두 메모리에 올려 점수화하고 정렬하는 작업은 그래프 DB의 트랜잭션 메모리로 감당하기 어려웠다고 한다. 이 수치는 해당 그래프의 실측 사례이므로 다른 그래프에 그대로 대입하지 않는다. 홉이 늘 때 분기 수가 곱해지기 때문에 자릿수가 바뀌는 구조는 일반적이다.

Beam search로 홉마다 후보를 제한한다

원문은 Beam search에서 착안한 단계별 후보 제한을 적용했다.

  • 한 홉씩 관계를 확장하고 경로에 점수를 매긴다.
  • 상위 후보만 다음 홉에서 다시 확장한다.
  • 최대 탐색 깊이와 홉별 후보 수를 제한한다.
  • 불필요한 노드와 관계는 확장 전에 제외한다.

이렇게 중간 후보 수를 통제하면 3홉 탐색을 안정적으로 수행할 수 있다. 대가도 있다. 일찍 버린 경로는 다시 살아나지 않는다. 제한을 도입하고 나면 다음 과제는 항상 "어떤 경로에 높은 점수를 줄 것인가"로 넘어간다.

어떤 경로를 남길 것인가

점수는 질문과의 관련도가 중심이다. 원문은 금융 도메인에서 중요한 신호를 보조로 얹었다.

  • 초기 검색에서 관련도가 높았던 엔티티나 관계가 경로에 포함되면 가중치를 더한다.
  • 종목의 중요도를 나타내는 신호로 시가총액을 반영한다.
  • 이벤트는 최근에 발생한 것일수록 가중치를 더한다.

시가총액과 최신성은 검색 결과를 결정하는 값이 아니다. 질문과의 관련성이 비슷한 후보들 사이에서 순서를 정하는 보조 신호다. 이 경계가 흐려지면 질문과 무관한 대형 종목이 상단을 차지한다.

시작 노드가 틀리면 이후 탐색 전체가 흔들린다. 따라서 시작점 검색은 경로 탐색과 따로 평가해야 한다. 이 판단은 원문이 직접 말한 것이 아니라 두 단계 구조에서 이끌어 낸 것이다.

탐색 의도는 검색기를 복제하지 않고 파라미터로 받는다

질문과의 관련도가 같아도 제품마다 중요하게 보는 관계의 흐름은 다르다. 예를 들어 어떤 제품은 기업과 기업 사이의 관계를 먼저 보여 주고 싶을 수 있다.

원문은 특정 노드 유형의 순서와 일치하는 경로에 가산점을 주는 파라미터를 만들었고, 내부에서 path_preference라고 불렀다. 관련 없는 경로를 완전히 제외하지 않고 선호 경로가 탐색 과정에서 더 오래 살아남게 하는 방식이다. 이 덕분에 검색기를 제품별로 따로 만들지 않고, 같은 그래프 위에서 서로 다른 탐색 의도를 반영할 수 있었다.

결과에는 경로를 함께 담는다

최종 결과에는 엔티티뿐 아니라 탐색한 관계 경로도 담았다. 결과가 어떤 연결을 통해 발견됐는지 추적할 수 있고, 제품이 이를 근거로 쓸 수 있다. 이 시리즈가 말하는 "원문 근거까지 돌아가는 경로"와 같은 방향이다.

Cypher 실행 계획은 직접 확인한다

원문은 같은 탐색 로직도 Cypher 실행 형태에 따라 비용이 크게 달랐다고 말한다. 시작점을 인덱스로 찾고, 방향과 타입 필터를 확장 초기에 적용하고, WITH로 필요한 값만 넘기고, EXPLAIN과 PROFILE로 확인하는 절차다. 이 내용은 03번 글이 이미 같은 방향으로 설명하므로 여기서 반복하지 않는다. 원문에서 추가로 확인한 점은 PROFILE에서 인덱스 사용 여부뿐 아니라 예상 행 수와 실제 행 수의 차이, 관계 확장 지점의 DB Hits, 연산자별 메모리 사용량을 봤다는 것이다.

정리

  • 3홉 이상이 필요한 그래프에서는 후보 경로가 홉마다 자릿수로 늘어난다.
  • Beam search로 홉마다 후보를 제한하면 깊이를 확보할 수 있지만, 일찍 버린 경로는 복구되지 않는다.
  • 그래서 점수 함수가 품질을 정한다. 도메인 신호는 주 신호가 아니라 보조 신호로 둔다.
  • 제품별 의도는 검색기 복제 대신 선호 경로 파라미터로 받는다.
on this page
  • 01검색기의 두 단계
  • 02홉마다 후보 경로가 자릿수로 늘어난다
  • 03Beam search로 홉마다 후보를 제한한다
  • 04어떤 경로를 남길 것인가
  • 05탐색 의도는 검색기를 복제하지 않고 파라미터로 받는다
  • 06결과에는 경로를 함께 담는다
  • 07Cypher 실행 계획은 직접 확인한다
  • 08정리
tags
#심화#study

이런 글도

  • ai

    하네스 회고는 새 문서로 쌓지 않고 종류에 맞는 기존 단일 소스에 환원한다

    2026.10.02
  • ai

    에이전트가 배운 회피 패턴은 조건을 통과한 것만 파일로 쌓고 주기적으로 지운다

    2026.10.02
  • ai

    ADR 은 되돌리기 어려운 결정의 이유만 남기고, 대안 기각은 옵션마다 줄을 나눈다

    2026.10.02
  • ai

    스킬 문서는 반복 실행을 스크립트로 내리고 같은 지시를 한 곳에서만 소유하게 쓴다

    2026.10.02

댓글 (0)