토스증권 추천과 검색은 어떻게 진화하고 있을까?
안녕하세요, 토스증권 AI 데이터 플랫폼 팀에서 ML 엔지니어로 일하고 있는 황호익입니다.
이번 글에서는 토스증권 안에서 추천과 검색이 어떤 방향으로 진화하고 있는지 이야기해보려고 해요. 추천은 어떻게 배치 중심에서 실시간 개인화로 바뀌고 있는지, 검색은 어떻게 키워드와 필터를 넘어 RAG와 그래프 RAG로 확장되고 있는지 순서대로 다룰게요.
완성된 정답이라기보다는, 시스템을 만들면서 마주친 고민들이라고 봐주시면 좋겠습니다.
토스증권 AI 데이터 플랫폼 팀의 미션
토스증권의 모든 AI 서비스가 공통적으로 접근할 수 있는 탄탄한 데이터 생태계를 제공하자.

AI 제품이 많아질수록 각 서비스가 필요한 데이터를 각자 수집하고, 각자 가공하고, 각자 검색하는 방식에는 한계가 생깁니다. 데이터 품질을 맞추기도 어렵고, 비슷한 문제를 여러 팀이 반복해서 풀게 되거든요.
그래서 이런 기반을 만들고 있어요.
오늘 이야기할 추천과 검색도 결국 이 공통 기반 위에서 함께 진화하고 있는 문제들입니다.
추천
예전에는 배치와 클러스터로 충분했어요
토스증권 추천에는 비교적 명확한 문제가 많았습니다. 개인화 뉴스 추천, 관심 종목 추천, 커뮤니티 인기글 추천 같은 것들이요.
이때는 유저와 아이템 데이터를 모아 배치로 처리하고, 유저를 클러스터링해서 클러스터별 추천 리스트를 미리 계산해두는 방식으로도 꽤 많은 문제를 풀 수 있었어요. 유저 DB에서 여러 원천 데이터를 배치 스케줄로 가져와 유저별 클러스터 정보를 만들고, 그걸 기반으로 추천 아이템 리스트를 만들어 Redis나 MongoDB 같은 저장소에 넣어두는 식이었죠.
중요한 건 이 방식이 틀렸다는 게 아닙니다. 당시에는 제품 생애주기도 빠르고 추천해야 하는 콘텐츠 종류도 지금보다 단순했기 때문에, 배치 중심·클러스터 중심의 접근이 현실적인 선택이었어요.
그런데 네 가지가 바뀌었어요
서비스가 성장하면서 추천이 마주하는 문제가 조금씩 달라지기 시작했습니다.

1️⃣ 유저의 관심사가 훨씬 빠르게 변해요 사용자는 앱 안에서 여러 화면을 탐색하고, 특정 종목을 보고, 뉴스를 읽고, 커뮤니티 글을 봅니다. 그 순간마다 관심 맥락이 계속 달라져요.
2️⃣ 시장과 종목 이벤트가 계속 움직여요 금융 서비스에서는 같은 사용자라도 장중에 관심을 가져야 하는 주제가 계속 변할 수 있습니다.
3️⃣ 콘텐츠 자체가 다양해졌어요 뉴스, 커뮤니티, AI 콘텐츠 등 사용자가 탐색할 수 있는 콘텐츠가 계속 늘어나고 있어요.
4️⃣ 기술적인 조건도 바뀌었어요 토스증권의 실시간 데이터·머신러닝 플랫폼이 성장하면서, 예전에는 비용 때문에 시도하기 어려웠던 고도화된 추천 시스템을 이제는 구축해서 운영해볼 수 있는 환경이 만들어졌습니다.
이런 배경에서 추천의 문제 정의가 이 사용자가 원래 좋아할 만한 것을 제공하는 것에서, 지금 이 사용자의 맥락에서 필요한 것을 제공하는 방향으로 이동했어요.
그래서 추천은 리스트가 아니라 루프가 됐어요
지금 이 사용자의 맥락에서 필요한 것을 제공하려면, 유저의 행동 이벤트와 아이템 변경 이벤트가 계속 들어와야 하고, 그에 대한 유저의 피드백도 피처 스토어와 벡터 인덱스에 반영되어야 합니다. 추천 서비스는 그 위에서 노출할 콘텐츠를 찾고, 유저가 좋아할 만한 순서대로 보여주고요.
그래서 추천은 정적인 리스트가 아니라, 실시간 이벤트와 피드백을 계속 흡수하는 루프 형태의 시스템이 됩니다.

흐름을 조금 더 자세히 보면,
필연적으로 이런 시스템에는 관측과 운영·디버깅 도구도 필요해집니다. 학습 데이터가 잘 만들어지고 있는지, 인덱스가 잘 갱신되고 있는지, 왜 특정 결과가 추천되었는지 확인할 수 있어야 하니까요.

운영하면서 마주친 두 가지 고민
임베딩 모델을 바꾸면 벡터 저장소 운영이 따라와요
추천을 고도화할수록 유저의 취향과 아이템의 맥락을 몇 가지 규칙만으로 표현하기 어려워집니다. 임베딩은 이런 복잡한 신호를 하나의 표현 공간에 담아, 대규모 후보에서 관련 있는 아이템을 효율적으로 찾게 해줍니다.
그런데 임베딩 기반 추천을 실제 트래픽에 붙일 때는 모델 성능만 보면 안 됐어요. 임베딩 모델을 계속 고도화할 수 있도록 열어두는 순간, 벡터 저장소 운영도 같이 따라옵니다.

임베딩 모델이 바뀌면 기존 벡터와 새 벡터가 같은 공간에 있다고 보기 어려워요. 그래서 이런 것들이 필요했습니다.
벡터 DB를 검색 성능만으로 검증할 수 없었어요
추천에서 유사 아이템을 찾기 위한 벡터 검색은 반드시 필요했어요. 그런데 사용자의 과거 선호 아이템이나 최근 행동을 피처로 쓰기 위해, 여러 아이템 임베딩을 한 번에 가져오는 멀티겟 연산도 발생합니다.
과거 아이템의 임베딩을 대량으로 조회하는 워크로드에서는 고차원 벡터의 응답 생성으로 JVM 메모리 압력이 커질 수 있었습니다. 그래서 검색 지연 뿐 아니라 mget 처리량과 GC 추이도 실제 요청 패턴으로 검증해야 했습니다.
검색, 그리고 RAG
데이터를 가져오는 방식이 파편화되고 있었어요
LLM을 활용하는 서비스가 하나둘 생기면서 비슷한 구조가 반복됐습니다. LLM으로 답변을 만들려면 결국 어떤 데이터를 가져와야 하거든요. 뉴스, 공시, 리포트 같은 것들이요.

그런데 서비스마다 그 방식이 조금씩 달랐어요.
여기서 기존의 키워드 검색이나 필터 기반 검색이 항상 나쁜 건 아니에요. 오히려 명확한 조건을 알고 있을 때는 굉장히 강력합니다. 하지만 사용자가 처음부터 정확한 검색어를 알고 있지 않거나, 특정 상황을 설명하는 뉴스처럼 맥락적으로 찾고 싶을 때는 한계가 분명했어요.
LLM 서비스는 늘어나는데 데이터를 가져오는 방식은 파편화되는 조짐. 저희는 이 지점을 문제로 봤습니다.
RAG를 '검색을 제품화하는 문제'로 봤어요
RAG를 단순히 LLM에 문서를 붙이는 기술로 보기보다, 검색을 제품화하는 문제로 보기 시작했어요.

LLM이 좋은 답변을 만들려면 좋은 컨텍스트를 함께 전달해야 합니다. 그러려면 사용자의 질문이 들어왔을 때 이런 판단이 필요해요.
이 과정이 서비스마다 파편화되면 품질도 흔들리고 운영 부담도 커집니다. 특히 LLM은 사실과 다른 정보를 생성할 수 있기 때문에, 답변이 검색된 문서에 잘 그라운딩되어 있는지 확인하는 일도 공통으로 중요해지고요.
그래서 RAG를 공통 플랫폼으로 제공할 수 있다고 봤습니다.
뉴스 RAG 파이프라인
사용자가 "오늘 미국 증시 요약해줘"처럼 질문합니다. 동시에 그 질문을 더 잘 이해하기 위한 단계가 시작돼요.

질문 이해. 이 질문이 어떤 카테고리의 뉴스를 원하는지 분류하고, 날짜 조건과 검색 범위를 만들어내고, 임베딩을 생성해 쿼리 벡터를 만듭니다. 이렇게 만들어진 조건들을 합쳐 Elasticsearch 쿼리를 구성해요.
하이브리드 검색. 검색은 하나의 방식만 쓰지 않습니다. 텍스트 검색과 벡터 검색을 조합하고, 메타데이터 필터를 함께 적용해 질문과 관련된 후보 뉴스를 찾아요.
리랭킹. 검색 결과는 리랭커 모델을 통해 한 번 더 걸러내는 동시에 중요한 순서로 정렬합니다.
최종적으로 정제된 결과만 각 서비스의 생성 단계로 전달돼요.
이 흐름에 포함된 모델은 대부분 LLM을 호출하는 방식으로 시작했지만, 우선순위에 따라 중요한 모델부터 하나씩 내재화하는 방식으로 구축했습니다.
RAG를 만들면서 마주한 것들
벡터 검색은 키워드 중심 질의에서 한계가 있었어요 금융 특화 임베딩 모델을 학습했음에도, 모든 회사·제품·금융 전문 용어에 대해 학습이 고루 일어나기는 굉장히 어렵습니다. 특히 사용자가 전통적인 키워드 검색 쿼리를 던진 경우에 이 문제가 더 잘 드러났어요.
텍스트 검색은 사전 관리가 중요했어요 형태소 분석기가 아직 인식하지 못하는 새로운 금융 이벤트 관련 키워드들이 있었습니다. 이런 경우에는 사전을 업데이트하는 방식의 운영도 필요했어요.
리랭커로 마지막 노이즈를 줄였어요 의도가 확실한 질문이 들어왔는데 불필요한 뉴스가 한두 건 섞여 반환되면, 사용자는 "그럴 수 있지"라고 이해하기보다 시스템 자체의 신뢰도를 의심하게 됩니다. 이 부분은 LLM을 파인튜닝한 리랭커를 두면서 해결할 수 있었어요.
메타데이터는 또 하나의 검색 신호가 되었어요 뉴스 카테고리를 잘 분류하는 모델을 만들고, 유저 쿼리가 들어오면 함께 분류한 뒤 태그가 매칭되면 더 높은 점수를 주었습니다. 이걸로 검색 정확도를 높일 수 있었어요. 그리고 잘 구축된 분류 체계는 RAG 말고도 여러 제품에서 활용할 수 있었습니다.
결국 RAG의 검색 품질에는 하나의 은탄환이 존재하지 않았어요. 서로의 단점을 보완하는 모듈들이 점진적으로 추가되면서 개선되는 흐름이었습니다.
제품은 달라도 흐름은 하나였어요
이렇게 만들어진 RAG 플랫폼은 하나의 제품에서만 쓰지 않았습니다. 뉴스 RAG만 예로 들어도 이런 니즈가 있었어요.

겉으로 보면 서로 다른 제품의 요구사항이에요. 그런데 플랫폼 관점으로 열어두면 모두 자연어 검색 API로 들어오고, 내부에서는 메타데이터 태그로 범위를 제어한 뒤 하이브리드 검색과 리랭킹을 수행하는 공통 흐름을 타게 됩니다.
제품은 달라도, 컨텍스트를 만드는 흐름은 하나의 RAG 플랫폼으로 공통화될 수 있었어요.
그래프 RAG
문서의 유사도만으로 답할 수 없는 질문들
앞에서는 필요한 문서를 잘 찾고 그걸 컨텍스트로 만드는 흐름을 봤습니다. 그런데 금융 도메인에는 비슷한 문서들을 잘 찾는 것만으로 충분하지 않은 질문들이 있어요.

관계를 따라가야 하는 질문들이죠. 그래서 그래프 RAG는 RAG의 연장선이긴 하지만, 문서를 잘 찾는 문제라기보다 파편화된 금융 지식 속에서 관계를 찾아내어 지식화하는 문제로 봐야 했습니다.
이 글에서 다루는 범위
전제가 하나 있어요.
토스증권에서는 회사·종목·제품·이벤트·테마를 연결하는 금융 지식그래프 구축이 진행되고 있었습니다. 일반적인 GraphRAG 솔루션이 그래프 구축과 검색을 함께 다룬다면, 여기서는 이미 정의된 금융 온톨로지 위에서 제품이 사용할 검색기를 만드는 데 집중했습니다.
따라서 엔티티 추출이나 링킹 과정은 다루지 않습니다. 이 글의 범위는 사용자 질문과 관련된 서브그래프를 찾아 반환하는 검색 모듈입니다.
그래프 검색기는 두 단계예요
시작 지점 찾기. 사용자 쿼리가 들어오면 앞서 설명한 검색 파이프라인을 활용한 방식으로 Elasticsearch를 사용해 관련 엔티티 후보를 찾습니다.
그래프 탐색. 반환된 시작 지점에서 출발해 그래프 위에서 연결된 노드를 탐색하며 후보 경로를 확장하고 선별해나갑니다.
이때 모든 관계를 다 따라갈 수는 없어요. 어떤 규칙으로 탐색할 것인지 정해야 하고, 이 규칙을 만들어내는 일이 검색 알고리즘의 핵심이 됩니다.

후보 경로가 5,000만 개로 늘어났어요
저희가 구축한 지식 그래프의 특성상, 의미 있는 서브그래프를 만들려면 3홉 이상 탐색해야 했습니다. 그런데 홉이 늘어날수록 탐색 경로가 기하급수적으로 늘어나요.
아무 전략 없이 수행하면 하나의 노드에서 출발한 경로가 이렇게 됩니다.
이 후보들을 모두 메모리에 유지한 채 점수화하고 정렬하는 작업은 Neo4j의 트랜잭션 메모리로 감당하기 어려웠습니다.

결국 문제는 얼마나 깊이 탐색할지가 아니라, 각 홉에서 어떤 경로를 일찍 버릴지였습니다.
이를 해결하기 위해 Beam Search에서 착안한 단계별 후보 제한 방식을 적용했습니다. 한 홉씩 관계를 확장하고 경로에 점수를 부여한 뒤, 상위 후보만 다음 홉에서 다시 확장했습니다. 최대 탐색 깊이와 홉별 후보 수를 제한하고, 불필요한 노드와 관계도 확장 전에 제외했습니다.
덕분에 중간 후보 수를 통제하면서 3홉 탐색을 안정적으로 수행할 수 있었습니다. 다만 일찍 제외된 경로는 다시 살아날 수 없기 때문에, 어떤 경로에 높은 점수를 줄 것인지가 다음 과제가 되었습니다.
어떤 경로를 남길 것인가
어떤 경로가 사용자 질문과 더 관련 있는지를 점수로 표현해야 했습니다. 질문과의 관련도를 중심에 두고, 금융 도메인에서 중요하게 보는 신호를 함께 반영했어요.
시가총액과 최신성이 검색 결과를 결정하는 것은 아닙니다. 질문과의 관련성이 비슷한 후보들 사이에서, 금융적으로 더 의미 있는 경로가 앞에 남도록 돕는 보조 신호로 사용했습니다.
우선해야 할 경로를 검색 파라미터로 제어했어요
질문과의 관련도가 높더라도, 제품마다 중요하게 보는 관계의 흐름은 달랐습니다. 예를 들어 어떤 제품에서는 다른 경로보다 기업과 기업 사이의 관계를 우선해서 보여주고 싶을 수 있어요.
이를 위해 특정 노드 유형의 순서와 일치하는 경로에 가산점을 주는 파라미터를 만들었습니다. 내부적으로는 이를 path_preference라고 불렀습니다. 관련 없는 경로를 완전히 제외하는 대신, 제품이 선호하는 경로가 탐색 과정에서 더 오래 살아남게 하는 방식입니다.
덕분에 검색기를 제품별로 따로 만들지 않고도, 같은 그래프 위에서 서로 다른 탐색 의도를 반영할 수 있었습니다.
같은 탐색 로직도 Cypher 실행 형태에 따라 비용이 달랐어요
Neo4j에도 쿼리 플래너가 있지만, 그래프 탐색은 시작 노드와 관계 분포에 따라 중간 후보 수가 크게 달라집니다. 논리적으로 비슷한 쿼리라도 실행계획에 따라 메모리 사용량과 처리 시간이 달라질 수 있어, 실제 실행 결과를 보며 최적화해야 했습니다.
PROFILE에서는 인덱스 사용 여부뿐 아니라 예상 행 수와 실제 Rows의 차이, 관계 확장 지점의 DB Hits, 연산자별 메모리 사용량을 확인했습니다. 결국 Cypher 최적화는 후보가 어느 지점에서 늘어나는지 찾고, 다음 탐색 전에 줄이는 것이었습니다.

그래프 검색의 품질을 결정한 것
그래프 검색의 품질은 그래프를 얼마나 깊게 탐색하느냐보다, 어디서 시작해 어떤 경로를 남기느냐에 달려 있었습니다.
관련 있는 시작점을 찾고, 각 홉에서 후보 수를 제한하고, 질문과 금융 도메인에 맞는 경로에 점수를 부여했습니다. 제품마다 중요하게 보는 관계가 다를 때는 경로 선호도를 추가로 반영했고요.
최종 결과에는 엔티티뿐 아니라 탐색한 관계 경로도 함께 담았습니다. 덕분에 결과가 어떤 연결을 통해 발견됐는지 추적하고, 제품에서 이를 근거로 활용할 수 있었습니다.

마치며
이번 글에서는 추천, RAG 검색, 그래프 검색을 만들며 마주한 문제와 선택을 소개했습니다. 서로 다른 시스템이었지만, 세 사례에서 반복해서 확인한 점은 같았습니다.
AI 제품의 품질은 모델 성능만으로 결정되지 않았습니다. 필요한 데이터를 어떻게 갱신할지, 어떤 후보를 남길지, 그리고 그 과정을 어떻게 관측하고 안정적으로 운영할지가 함께 중요했습니다.
보편적인 해답을 제시하기보다, 실제 시스템을 만들며 어떤 문제를 만났고 어떤 선택을 했는지 나누고 싶었습니다. 이 경험이 AI 제품을 만들 때 모델 바깥의 시스템을 바라보는 작은 힌트가 되었으면 좋겠습니다.
읽어주셔서 감사합니다.
테크톡톡(Tech Talk Talk)은 테크 업계 종사자들의 함께 교류하고 성장하는 네트워킹 및 지식공유 포럼이에요.
토스증권의 기술과 경험을 나누는 행사로, 토스증권의 엔지니어들이 직접 기술적 고민과 시행착오를 통한 배움, 그리고 성장의 여정을 공유해요. 세션 강연 뿐 아니라 실시간 질의응답을 통해 참여자들과 인사이트를 나누며, 같은 분야의 동료들과 함께 배우고 성장할 수 있는 열린 기술 교류의 장이에요!
