디시는 갤러리 글에 대해 두 가지 검색을 제공한다



1. 특정 갤러리 검색





특징

- 한 갤러리에서만 검색함

- 한번 요청에 최근 게시물 1만개만 검색해서 옛날글 보려면 계속 눌러야함

- 모든 최신글들이 누락 없이 검색됨

- 삭제된 글은 검색되지 않음

- 검색방식: like쿼리같이 작동함 '면접에서' 문자열이 포함된 글을 '접에'로 검색 가능)



2. 모든 갤러리 통합검색



특징

- 모든 갤러리에서 검색 가능함

- 방금 전 작성한 글이 검색되지 않음

- 댓글 개수 표시 안됨

- 최근 몇개월치의 글만 검색 결과로 나옴

- 이미 삭제된 글이 검색결과로 나올 때가 있음

- 검색방식: like검색이 아님. '면접에서' 문자열이 포함된 글을 '면접의' 같은 쿼리로 검색 가능(형태소단위 검색)



그런데 생각해보면 이상하다.

UI상 갤러리별 검색창과 통합검색창을 나눈것까지는 자연스러운데 왜 검색로직이 서로 다를까?

그리고 통합검색을 사용하다보면 내가 방금 쓴 글을 검색해내지도 못하고, 쿼리에 따라 글을 제대로 검색하지 못할때도 있다.

디시 검색은 왜 이럴까?


이상한점을 정리해보면

- 왜 검색기능이 서로 다른지? 

- 갤러리 검색은 왜 최신 1만개 글만 검색가능한지?

- 통합검색은 어떻게 1만개보다 훨씬 많은 글을 모든 갤러리에서 검색 가능한지? 

- 통합검색은 왜 방금 전 글이 검색되지 않는지?

- 통합검색은 왜 반년정도 이전의 글이 검색되지 않는지?

...



그 이유는 갤러리 통합검색이 디시의 수많은 똥글을 빠르게 검색할 수 있도록 역색인 검색엔진을 사용하기 때문이다.

(갤러리별 검색은 다들 예상하듯이 DB에서 like쿼리를 수행하는 것으로 보인다)


1만개 글에 대한 like쿼리는 가볍지만 수백 수천만 문서에 대한 like쿼리는 너무나 무겁다.

그래서 갤러리 통합검색같이 어느정도 큰 규모의 검색 시에는 검색만을 위한 별도의 자료구조가 필요하고 일반적으로 역색인을 사용한다.


역색인(inverted index)에 대하여


역색인 이야기 전에 도서관에서 색인으로 책을 찾는 방법을 생각해보자. 

'강아지 배변교육 시키는 방법'을 찾으려고 한다면

책을 빠르게 찾기 위해 도서관의 '애완동물' 코너를 찾을 것이다.

또는 찾는 책의 이름을 알아서 그 책의 초성으로 찾아갈수도 있다.

왜냐하면 도서관이 내가 원하는 책에 '애완동물' 또는 책의 초성으로 index를 걸어뒀기 때문이다.


그런데 우리는 인터넷에서 이런 방식으로 자료를 찾지 않는다.

검색창에 '강아지 배변교육 시키는 방법' 그대로 넣어 필요한 문서를 찾는다.

컨텐츠 또는 키워드에서 문서를 찾아냈으며 도서관에서 책을 찾는 방법과 정반대다.


일반적인 index의 역방향으로 작동하므로 inverted index라고 부른다.

대부분의 검색엔진은 inverted index를 사용하고 일상적으로 역색인도 색인이라 부를때가 많다.


개요를 풀어 설명했으니 나머지는 각자 인터넷에 잘 정리된 글로 역색인 구조를 알아보자.

역색인의 구현은 lexicon-posting이며 일반적으로 term을 저장하는 map에 해당하는 lexicon에 hash map을 사용하며 각 term에 대응하는 문서 list를 posting에 skip-list, sparse index 등으로 저장한다.



lexicon-posting구조를 이해한 뒤 읽어보자



갤러리별 검색과 통합검색의 검색방식이 다른 이유는?


lexicon에 해당하는 term은 일반적으로 문서 내용을 형태소 분석한 결과다.

예를 들어 '앙증맞은 골전도 이어폰의 추억이여'라는 문서는 '앙증맞다', 골전도', '이어폰', 추억' 4개 term으로 분해한 뒤 검색엔진에 입력할 수 있다.


그래서 갤러리별 검색에서는 위 문서를 '증맞' 또는 '억이여'라는 쿼리,로 검색할 수 있지만 통합검색에서는 불가능하다.

대신 통합검색에서는 '앙증맞던 이어폰이여'라는 쿼리로 검색할 수 있을것이다.


검색엔진에서 형태소분석을 제공하는 경우도 있지만, 어느정도 이상의 검색 퀄리티를 유지하려면 형태소분석기에 투자해야한다.

예를 들어 신조어를 명사로 인식하지 못한다면 그 신조어가 term으로 분해되지 못해서 검색 퀄리티가 떨어질 수 있다.


사실, 역색인을 사용하는 디시 통합검색도 갤러리별 검색의 like쿼리와 거의 동일한 검색방식을 제공할 수 있다.

형태소분석 대신 bigram(ex: 면접에서 -> 면접/접에/에서)을 사용한다면 like쿼리와 거의 같은 검색경험을 제공할 수 있다.

하지만 bigram은 형태소분석 대비 색인 크기가 매우 커지고 쿼리도 비싸지는데,

영어 기준 색인 크기가 5배 증가한다고 하며 한글은 알파벳 5배보다 훨씬 더할것이므로 디시에서는 사용하기 어렵다.



디시 통합검색은 왜 방금전 글은 검색되지 않는지?


inverted index는 검색 latency를 최소화하기 위해 극도의 최적화가 필요하며 그 결과 증분 색인(검색 대상 문서 추가)가 매우 어렵다.

일반적으로 posting자체가 문서를 추가하기 어려운 구조가 되며,

각 문서에 대해 저장하는 속성값 중 확률 기반 ranking을 위한 term frequency, document frequency(for bm25)로 인해

문서를 추가할때마다 모든 문서에 대한 tf/df 속성값을 업데이트하는것이 불가능하다는 문제도 있다.


lucene 등에서는 증분 색인을 지원하기도 하지만 compaction 전까지 역색인에 물리적으로 반영되는 방식이 아니며,

무리한 업데이트는 상황에 따라 색인 충격을 발생시켜서 검색성능 저하가 발생할 수 있다.

디시의 신규문서 반영이 수 분에서 수십분 이상 걸리는 정확한 이유를 알기는 어렵지만

요구사항이 넉넉하므로 굳이 리소스를 투자하지 않는 것이 아닐까 싶다.

 


디시 통합검색에서 오래된 글을 검색할 수 없는 이유


inverted index도 index의 일종이고 색인 크기가 클수록 메모리를 많이 잡아먹는다.

비용문제 때문에 수 개월 내의 글만 남기고 나머지는 제외하는것으로 보인다.

(정책상으로 실베 힛갤 개념글 등의 색인은 더 오래 남기는지에 대해서는 확인하지 않음)


하지만 역색인 구조상 문서를 추가하는것만큼 삭제도 어렵다.

검색엔진에서는 삭제문서를 효율적으로 반영하기 위해 일단 논리적으로만 마킹한 뒤 나중에 내부 compaction시 한꺼번에 물리적으로 반영하는 경우도 있지만,

매일 수십 수백만건의 문서를 삭제 반영하는것은 거의 불가능한 작업이다.

elasticSearch도 성능 이슈로 문서단위 ttl을 포기하고 색인단위 ttl을 제공하도록 변경된 이력이 있다.


디시의 해결방식은 여러개의 색인을 '적절히' 운영하는 것으로 예상된다.

이미 구축한 색인에 문서를 추가하거나 삭제하는 것 보다 새로운 색인을 구축하는것이 훨씬 쉽다.

예를 들어 디시가 최근 3일치 문서만 검색으로 제공하고, 2일치 문서로 색인을 생성하는데 1일이 걸린다고 가정한다면

매일 과거 이틀치 문서를 색인하고 완료 시 현재 색인을 대체하는 방식으로 운영할 수 있을것이다. 



마무리


검색(information retrieval)은 크게 retrieval과 ranking으로 나뉜다.

retrieval 단계에서는 최대한 많은 문서set에서 빠르게 쿼리에 대응하는 문서를 찾아내고

ranking은 retrieval 결과 내에서 문서간 순위를 결정한다.


디시 갤러리 글 검색은 단순 시간순 정렬하므로 retrieval에 한정된다.


ranking 시에는 일반적으로 bm25등 통계적 방법론이나 기획에서 요구하는 방식(문서 최신성, 사이트/도메인 점수)을 반영하여 문서 순위를 정한다.

최근에는 AI 영향으로 bert나 splade를 적용하기도 하고 검색엔진에 각 문서를 임베딩한 벡터를 입력해서 쿼리 벡터와 ANN한 결과를 전통적인 랭킹과 섞어 좀 더 개선된 랭킹을 수행하기도 한다.


일반적으로 검색 하면 포탈 검색을 떠올리기 쉬우나 오늘날 거의 모든 IT서비스는 검색기능을 포함한다.

검색의 요구사항은 검색 서비스의 숫자만큼 다양하므로 요구사항과 리소스를 파악하여 적절하게 운영하는것이 중요하다고 할 수 있다.