포스트

의미로 검색하다 — 벡터 검색(ANN·HNSW)과 PageRank, 그리고 시리즈의 끝

임베딩·코사인 유사도로 의미를 찾는 벡터 검색(ANN·HNSW)과 링크로 권위를 흐르게 하는 PageRank. 현대 검색·추천·랭킹의 두 기둥을 애니메이션으로 정리합니다.

의미로 검색하다 — 벡터 검색(ANN·HNSW)과 PageRank, 그리고 시리즈의 끝

키워드가 아니라 의미를 찾는다

전통적 검색은 단어 일치입니다. “강아지”를 검색하면 “강아지”가 든 문서만 나오죠. 하지만 “반려견 키우는 법”은 “강아지”라는 글자가 없어도 분명 관련이 있습니다. 의미로 검색하려면 텍스트·이미지를 고차원 벡터(임베딩) 로 바꿔야 합니다. 모델이 “강아지”와 “반려견”을 벡터 공간에서 가까운 점으로 만들면, 검색은 “쿼리 벡터에 가장 가까운 점 찾기”가 됩니다.

사실 이 블로그의 AI 시맨틱 검색이 바로 이 원리로 돕니다 — 글을 임베딩해두고, 검색어도 같은 모델로 임베딩한 뒤 코사인 유사도로 가장 가까운 글을 찾습니다. 시리즈의 마지막 글이 블로그 자신의 검색 엔진을 설명하는 셈이네요.

이 글은 「알고리즘 A-Z」의 피날레로, 현대 검색·추천·랭킹의 두 기둥인 벡터 검색(근사 최근접 이웃) 과 PageRank를 다룹니다. 전체 파이프라인은 이렇게 흐릅니다 — 색인 시점엔 문서를 벡터로 바꿔 인덱스에 넣고, 질의 시점엔 같은 모델로 쿼리를 벡터화해 가장 가까운 점들을 찾습니다.

flowchart LR
    subgraph IDX["색인 (offline)"]
      D["문서/이미지"] --> E1["임베딩 모델"] --> V["벡터"] --> H["ANN 인덱스<br/>(HNSW)"]
    end
    subgraph QRY["질의 (online)"]
      Q["검색어"] --> E2["같은 임베딩 모델"] --> QV["쿼리 벡터"]
    end
    QV -->|최근접 탐색| H --> R["top-k 결과<br/>(코사인 유사도)"]

유사도와 차원의 저주

두 벡터의 가까움은 보통 코사인 유사도로 잽니다 — 크기가 아니라 방향이 얼마나 같은지:

\[\text{cos}(\mathbf{a},\mathbf{b}) = \frac{\mathbf{a}\cdot\mathbf{b}}{\lVert\mathbf{a}\rVert\,\lVert\mathbf{b}\rVert}\]

정확히 가장 가까운 $k$개를 찾는 k-NN의 가장 단순한 방법은 모든 점과 거리를 재는 것 — 점 $N$개, 차원 $d$면 쿼리당 $O(Nd)$입니다. 수백만~수십억 벡터에선 감당이 안 됩니다. “그럼 트리로 공간을 분할하면?”이라고 생각할 수 있지만, 여기서 차원의 저주가 덮칩니다 — 차원이 수십을 넘으면 모든 점이 서로 비슷하게 멀어져, k-d 트리 같은 공간 분할이 사실상 전수 탐색으로 퇴화합니다.

그래서 현대 벡터 검색은 정확함을 약간 포기합니다. “정확한 최근접” 대신 “거의 최근접”을 훨씬 빠르게” — 이것이 **ANN(Approximate Nearest Neighbor) 입니다. 99% 정확도를 100배 속도와 맞바꾸는, 확률적 자료구조·스트리밍에서 본 “근사로 충분하다”의 또 다른 얼굴입니다.

HNSW — 고속도로에서 골목길로 내려가는 탐색

오늘날 ANN의 사실상 표준은 HNSW(Hierarchical Navigable Small World)입니다. 발상은 건너뛰기 리스트(skip list) 의 그래프 버전입니다. 노드들을 여러 층(layer) 으로 쌓되, 위층일수록 노드가 희박하고 먼 거리를 잇는 간선(고속도로) 만 둡니다. 아래층으로 갈수록 조밀해지고 간선은 가까운 이웃(골목길) 을 잇습니다.

탐색은 맨 위층 한 점에서 시작해 쿼리에 더 가까워지는 이웃으로 탐욕적으로 이동하다, 더 못 가까워지면 한 층 내려갑니다. 위층에서 성큼성큼 대략의 동네로 워프하고, 아래층에서 세밀하게 좁히는 것이죠. 덕분에 쿼리당 평균 $O(\log N)$ 수준으로 수십억 벡터에서도 최근접을 찾습니다.

상위층 (희박·고속도로) 중간층 하위층 (조밀·골목길) 최근접

또 다른 갈래는 LSH(Locality-Sensitive Hashing) — 가까운 점이 같은 해시 버킷에 떨어지도록 일부러 충돌을 설계한 해시입니다(일관된 해싱과는 정반대 목적이죠). 같은 버킷만 비교해 후보를 줄입니다. 구현은 단순하지만, 대부분의 벤치마크에서 HNSW가 정확도·속도 모두 앞서 실무 1순위가 됐습니다.

방식쿼리 시간정확도(recall)메모리비고
완전 탐색(brute)$O(Nd)$100%낮음소규모·기준선
k-d 트리고차원서 퇴화100%(저차원)중간차원의 저주
LSH빠름중간높음튜닝 까다로움
HNSW$O(\log N)$높음높음실무 표준
IVF-PQ빠름중상낮음(압축)초대규모·FAISS

실무에선 OpenSearch·Elasticsearch의 kNN 필드, PostgreSQL의 pgvector, 그리고 FAISS·Milvus·Pinecone 같은 벡터 DB가 HNSW나 IVF-PQ를 엔진으로 씁니다. RAG·추천·이미지 검색의 검색 단계가 전부 이 위에 섭니다.

PageRank — 링크로 권위를 흐르게 하다

벡터 검색이 “쿼리와 얼마나 비슷한가(관련성)”라면, 또 하나의 축은 “이 문서가 얼마나 중요한가(권위)”입니다. PageRank(Brin·Page, 구글의 출발점)는 이를 웹 그래프로 풉니다. 핵심 직관: 중요한 페이지로부터 링크를 많이 받은 페이지가 중요하다. 권위가 링크를 타고 흐르는 재귀적 정의죠.

이를 랜덤 서퍼(random surfer) 모델로 봅니다. 무작위로 링크를 클릭하며 떠도는 사람이 어떤 페이지에 머무를 장기 확률이 곧 그 페이지의 PageRank입니다. 페이지 $u$의 점수는:

\[PR(u) = \frac{1-d}{N} + d \sum_{v \in B_u} \frac{PR(v)}{L(v)}\]

$B_u$는 $u$로 링크하는 페이지들, $L(v)$는 $v$의 바깥 링크 수, $d$(보통 0.85)는 감쇠 인자입니다. $(1-d)/N$ 항은 “가끔 아무 페이지로나 순간이동”하는 확률로, 링크 없는 막다른 페이지(dangling)에 권위가 갇히는 걸 막습니다.

푸는 방법은 파워 반복(power iteration): 모든 점수를 균등하게 시작해, 위 식으로 동시에 갱신하길 반복하면 점수가 수렴합니다. 수학적으로는 거대한 전이 행렬의 주고유벡터를 찾는 것과 같습니다.

B C D E A 권위(주황)가 링크를 타고 A로 흘러들어 A의 점수가 커지며 수렴

PageRank는 단지 옛 검색 알고리즘이 아닙니다. 그래프에서 중요도를 매기는 범용 도구로, 소셜 네트워크 영향력, 추천(아이템-아이템 그래프), 부정행위 탐지, 학술 인용 분석에 두루 쓰입니다. 그래프 순회와 최단 경로가 그래프의 구조를 다뤘다면, PageRank는 그래프의 중요도를 정의합니다.

실제 검색 랭킹은 이 둘을 합칩니다 — 벡터 검색(또는 BM25 같은 키워드 점수)으로 관련성 높은 후보를 빠르게 추리고, PageRank류 권위 점수와 개인화 신호를 더해 최종 순위를 냅니다. 현대 검색은 단일 알고리즘이 아니라 이 신호들의 합주입니다.

프로덕션에서 마주치는 함정

함정증상해법
recall↔지연 트레이드오프너무 정확히 찾다 느려짐(혹은 반대)efSearch/M 튜닝, 목표 recall 설정
메모리 폭발HNSW 인덱스가 RAM 초과IVF-PQ 양자화, 샤딩
임베딩 모델 불일치색인·쿼리 모델이 달라 엉뚱한 결과동일 모델·버전 고정(이 블로그도 그 규칙)
정규화 누락코사인인데 미정규화로 크기 왜곡벡터 L2 정규화 후 내적
PageRank 미수렴dangling/spider trap에 권위 갇힘감쇠 $d$(텔레포트)로 해소, 반복 상한

면접/리뷰 단골 질문

  • Q. 왜 정확한 k-NN 대신 ANN? → 고차원에서 정확 최근접은 차원의 저주로 전수탐색화. ANN은 약간의 정확도를 큰 속도와 맞바꿔 수십억 벡터를 실시간 검색.
  • Q. HNSW가 빠른 이유? → 계층 그래프 — 위층 희박 간선으로 성큼 워프, 아래층 조밀 간선으로 세밀 탐색. 평균 O(log N).
  • Q. 코사인 유사도를 쓰는 이유? → 벡터 크기가 아닌 방향(의미)을 비교. 정규화하면 내적과 동치.
  • Q. PageRank 한 줄 정의? → 랜덤 서퍼가 페이지에 머무는 장기 확률 = 전이행렬의 주고유벡터. 중요한 곳에서 받은 링크가 권위를 키운다.
  • Q. 감쇠 인자 d의 역할? → 텔레포트 확률 $(1-d)$로 dangling/spider trap에 권위가 갇히는 것을 막고 수렴을 보장.

정리

  • 벡터 검색은 키워드가 아닌 의미(임베딩) 로 찾는다 — 고차원의 차원의 저주 때문에 정확 k-NN 대신 ANN(HNSW) 으로 “거의 최근접”을 빠르게 찾는다.
  • HNSW는 계층 그래프로 위층은 고속도로, 아래층은 골목길 — 평균 O(log N)에 수십억 벡터를 검색한다.
  • PageRank는 링크 그래프에서 권위를 흐르게 해 중요도를 매기는 범용 도구 — 파워 반복으로 주고유벡터를 구한다.
  • 현대 검색은 관련성(벡터) 과 권위(PageRank류) 의 합주다.

이 글로 「알고리즘 A-Z」 20편이 끝납니다. 복잡도와 Big-O에서 “비용을 어떻게 재는가”로 시작해, 정렬·탐색·트리·그래프·동적계획법 같은 고전을 지나, 합의·레이트리미팅·벡터검색 같은 분산·AI 시대의 알고리즘까지 왔습니다. 알고리즘은 결국 유한한 자원으로 점점 더 큰 문제를 푸는 인간의 방법론입니다 — 그 정점에서 기계가 의미를 검색하고 권위를 계산하게 됐고요. 문자열 알고리즘을 더 파고 싶다면 접미사 트리·나이브 베이즈로 이어가 보세요.

</content>

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

시리즈 · 알고리즘 A-Z (20편)

  1. 복잡도 분석 — Big-O, 점근 표기, 그리고 분할상환이라는 거짓말 같은 진실
  2. 배열과 연결 리스트 — 같은 O(n)인데 왜 한쪽이 수십 배 느린가
  3. 스택·큐·힙 — 접근 규칙 하나가 만드는 강력함
  4. 해시 테이블 — 평균 O(1)의 비밀과 그 평균이 깨지는 순간
  5. 비교 정렬 — 퀵·머지·힙, 그리고 Ω(n log n)이라는 넘을 수 없는 벽
  6. 선형 시간 정렬 — 비교를 버리면 Ω(n log n) 벽이 사라진다
  7. 이진 탐색 — 반씩 버리는 마법, 그리고 "답을 이분하라"
  8. 재귀와 분할정복 — 콜스택에 쌓이는 신뢰, 마스터 정리로 푸는 비용
  9. 이진 탐색 트리와 균형 — 한쪽으로 쏠리는 트리를 회전으로 바로 세우기
  10. 그래프 순회 — BFS의 물결, DFS의 깊이, 그리고 위상정렬
  11. 최단 경로 — 다익스트라의 확신, A*의 직감, 벨만-포드의 인내
  12. 최소 신장 트리와 Union-Find — 가장 싸게 잇기, 그리고 거의 O(1)의 마법
  13. 동적계획법 — 같은 계산을 두 번 하지 않는다는 단 하나의 원칙
  14. 그리디 — 눈앞의 최선만 집었는데 전체가 최적일 때
  15. 문자열 매칭 — 어긋났을 때 얼마나 똑똑하게 다시 시작하는가
  16. 확률적 자료구조 — 정확함을 메모리와 맞바꾸는 기술 (Bloom·HyperLogLog·Count-Min)
  17. 일관된 해싱 — 노드가 들고날 때 데이터를 통째로 옮기지 않는 법
  18. 합의 알고리즘 — 죽는 서버들 사이에서 하나의 진실을 정하는 법 (Raft·Paxos)
  19. 레이트 리미팅과 스트리밍 알고리즘 — 폭주를 막고, 끝없는 흐름에서 표본을 뽑다
  20. 의미로 검색하다 — 벡터 검색(ANN·HNSW)과 PageRank, 그리고 시리즈의 끝 지금 보는 글