Backend · Search · Database

검색엔진을 들일 때 - DB로 버틸 수 있는 선

검색이 느리다고 바로 엔진을 들이면 저장소가 둘이 되고 동기화라는 새 일이 생긴다. DB로 어디까지 버틸 수 있고, 무엇을 보고 넘어갈지 판단표로 닫는다.

·검색이 빠른 이유 6편
목차
  1. 엔진부터 떠올리기 전에
  2. DB로 버티는 길이 셋 있다
  3. DB 내장 전문 검색이 실제로 하는 일
  4. 그런데 어디서 막히나
  5. 넘어가는 신호
  6. 엔진을 들이면 생기는 새 일
  7. 색인은 원본이 아니라 파생물이다
  8. 그래도 운영은 남는다
  9. 실무에서: 판단표
  10. 정리

검색엔진이 무슨 일을 하는지는 여기까지 다 봤다. 남은 질문은 하나다 - 그걸 지금 들여야 하나.

엔진부터 떠올리기 전에

검색이 느리다는 말이 나오면 곧장 검색엔진 이름이 오간다. 그런데 그 전에 확인할 게 있다.

엔진을 들인다는 건 저장소를 하나 더 운영한다는 뜻이다. 데이터가 두 곳에 살게 되고, 두 곳을 맞추는 일이 새로 생기고, 그 일이 실패하는 방식이 새로 생긴다. 검색이 빨라지는 값으로 이걸 내는 것이다.

그러니 순서를 뒤집어서 묻는 게 맞다. DB로 어디까지 버틸 수 있나.

DB로 버티는 길이 셋 있다

앞을 고정한다. 자동완성·아이디 조회·태그 검색처럼 앞에서부터 치는 게 자연스러운 자리는 접두 검색으로 바꿀 수 있다. 그러면 인덱스를 그대로 탄다. 검색 요구의 상당수가 사실 이쪽이고, 이걸 본문 검색으로 뭉뚱그려 놓아 어려워지는 경우가 많다.

DB에 있는 전문 검색 기능을 쓴다. 주요 관계형 DB에는 본문 검색용 인덱스가 이미 들어 있다. 이게 가장 저평가된 선택지다. 아래에서 따로 본다.

검색 결과를 캐시한다. 인기 검색어 상위 몇십 개가 전체 검색의 큰 몫을 차지하는 서비스가 많다. 그 결과만 캐시해 둬도 DB가 받는 부담이 확 떨어진다. 검색어는 그대로 캐시 키가 되니 붙이기도 쉽다.

셋 다 지금 쓰는 저장소 안에서 끝난다. 새로 운영할 물건이 안 생긴다는 게 가장 큰 값이다.

DB 내장 전문 검색이 실제로 하는 일

두 번째 길을 조금 더 보자. 이걸 오해하는 경우가 많은데, DB의 전문 검색 인덱스는 지금까지 본 역색인을 DB 안에 만드는 것이다. 다른 원리로 도는 물건이 아니다.

본문을 토큰으로 자르고, 낱말마다 포스팅 리스트를 만들고, 낱말 하나를 짚어 문서를 꺼낸다. 관련도 점수를 매겨 순위를 내주는 기능도 대개 딸려 있다. 이 시리즈에서 본 이야기가 그대로 적용된다는 뜻이다. 토큰화 규칙이 색인과 검색 양쪽에 맞아야 하는 것도, 색인이 갱신 비용을 낸다는 것도 똑같다.

그래서 이 길로 가면 얻는 게 크다. 트랜잭션 안에서 본문과 색인이 같이 갱신되므로 동기화라는 문제 자체가 없다. 조인도 그대로 되고, 백업도 하나만 하면 된다.

그런데 어디서 막히나

막히는 자리도 뚜렷하다.

한국어 분석이 가장 먼저 걸린다. 내장 기능은 공백과 기호 기준으로 자르는 것을 기본으로 삼는 경우가 많아서, 조사를 떼는 처리를 따로 붙여 줘야 한다. 붙일 수는 있지만 손이 가고, 분석기를 갈아 끼우거나 사전을 얹는 일이 엔진 쪽만큼 편하지 않다.

랭킹 손잡이가 적다. 관련도 점수는 나오는데, 제목에 가중치를 더 주거나 최신성으로 감점하거나 클릭을 반영하는 식의 조정을 하려면 직접 만들어야 한다. 순위를 계속 고쳐야 하는 서비스에서는 이게 곧 한계선이다.

부하가 같은 DB에 얹힌다. 이게 가장 실질적이다. 검색은 무겁고 튀는 작업이라, 검색이 몰리는 순간 검색과 상관없는 주문·결제 쿼리가 같이 느려진다. 저장소를 나누면 적어도 검색이 죽어도 나머지는 산다.

집계와 필터가 많아지면 벌어진다. 검색 결과 옆에 “브랜드별 개수” 같은 것을 붙이기 시작하면, 엔진 쪽은 구조상 싼 일이고 DB 쪽은 매번 세는 일이 된다.

넘어가는 신호

정리하면 넘어갈 때가 됐다는 신호는 이렇게 생겼다.

  • 검색이 부가 기능이 아니라 제품 그 자체가 됐다
  • 순위를 두고 회의를 한다. 즉 관련도를 계속 손봐야 한다
  • 검색 하나가 DB의 자원을 먹어 다른 기능을 끌어내린다
  • 필터·집계·자동완성·오타 교정 같은 것을 하나씩 직접 만들고 있다
  • 검색 대상이 여러 테이블에 흩어져 있고 매번 조인해서 훑고 있다

반대로 이 중 어느 것도 해당 안 되는데 엔진을 들이면, 얻는 것보다 운영할 것이 많아진다.

엔진을 들이면 생기는 새 일

넘어가기로 했다면 이제 새로 생기는 일을 정확히 알아야 한다. 이름은 하나다. 동기화.

원본은 DB에 있고 색인은 엔진에 있다. 글이 하나 저장될 때마다 엔진에도 넣어 줘야 하고, 이 일은 실패할 수 있다. 방식은 대체로 셋이다.

저장하면서 같이 넣는다. 코드가 DB에 쓰고 이어서 엔진에도 쓴다. 단순하지만 엔진이 잠깐 죽으면 그 글은 영원히 색인에 안 들어간다. 게다가 DB 트랜잭션이 나중에 롤백되면 색인에만 남는다.

작업을 남기고 나중에 처리한다. 저장할 때 “이 문서를 색인해라”를 같은 트랜잭션 안에 기록해 두고, 별도 처리기가 그걸 읽어 엔진에 넣는다. 실패하면 다시 시도할 수 있다는 게 크다. 대신 처리기가 밀리면 지연이 길어진다.

DB의 변경 기록을 흘려보낸다. 데이터베이스가 남기는 변경 내역을 읽어 색인으로 보낸다. 애플리케이션 코드가 색인을 몰라도 된다는 게 장점이고, 대신 다룰 부품이 하나 더 는다.

어느 쪽이든 공통점이 있다. 지연이 있고, 실패할 수 있고, 그래서 두 곳이 어긋난다.

색인은 원본이 아니라 파생물이다

어긋남을 다루는 원칙은 하나뿐이고, 이걸 놓치면 나머지 대책이 다 소용없다.

원본 데이터베이스 여기서 잃으면
되돌릴 데가 없다
언제든 다시 만든다
✕ 이 방향은 없어야 한다 색인에만 쓰면 원본이 둘이 된다
파생 검색 색인 통째로 지워도
다시 구우면 된다

두 저장소는 대등하지 않다. 원본에서 색인으로 가는 길만 열어 두면 어긋남은 사고가 아니라 다시 만들면 되는 상태가 된다.

원본은 DB에 하나만 둔다. 색인은 그 원본에서 언제든 다시 만들 수 있는 파생물이다.

이 시리즈를 따라온 책으로 말하면 이렇다. 본문이 원본이고 뒤에 붙은 찾아보기는 본문에서 다시 뽑아낼 수 있는 것이다. 찾아보기를 통째로 뜯어내도 책은 책이지만, 찾아보기에만 적힌 내용이 있다면 그건 이미 찾아보기가 아니라 본문의 일부다.

이 원칙이 서 있으면 어긋남은 사고가 아니라 다시 만들면 되는 상태가 된다. 색인이 깨져도, 분석 규칙을 바꿔도, 어느 문서가 빠졌어도 답은 같다. 원본에서 다시 굽는다.

깨지는 순간은 색인에만 있는 데이터가 생길 때다. 검색 화면에서 편집한 값을 엔진에만 쓰거나, 계산해 넣은 필드를 DB에 안 남기면, 그 순간 원본이 둘이 된다. 그러면 재색인이 곧 데이터 유실이 되어 다시 만들 수 없는 색인이 된다. 실제로 이 상태가 되고 나서야 알아차리는 경우가 많다.

주의

그래서 판단 기준을 하나 들고 다니면 편하다. “지금 색인을 통째로 지워도 되는가.” 답이 “안 된다”면 어딘가에 색인에만 있는 데이터가 있는 것이고, 그건 색인 문제가 아니라 설계 문제다.

그래도 운영은 남는다

원칙을 지켜도 사라지지 않는 일들이 있다. 도입 전에 이걸 세어 보는 게 정직하다.

  • 메모리를 많이 쓴다. 검색엔진은 색인을 메모리에 올려 두고 도는 물건이라 장비가 싸지 않다
  • 버전을 올려야 한다. 색인 형식이 바뀌면 재색인이 따라오고, 그 재색인이 몇 시간짜리 작업일 수 있다
  • 감시할 지표가 는다. 색인 지연, 세그먼트 수, 병합 중 지연 튐, 원본과의 건수 차이
  • 장애가 번지지 않게 막아야 한다. 엔진이 죽었을 때 검색만 실패하고 나머지 기능은 살아 있어야 한다. 그러려면 호출하는 쪽에 그 대비를 넣어 둬야 한다

마지막 것이 실무에서 가장 자주 빠진다. 저장소를 나눈 이유의 절반이 검색이 죽어도 서비스는 산다인데, 코드가 검색 응답을 기다리다 같이 멈추면 그 값을 못 챙긴다.

실무에서: 판단표

물어볼 것은 여섯 개고, 오른쪽이 둘 이상이면 넘어갈 때다.

물어볼 것DB로 버틴다엔진으로 간다
검색이 무엇인가부가 기능이다제품 그 자체다
대상 규모수십만 건 이하수백만 건 이상
순위최신순·정확 일치면 충분관련도를 계속 손본다
한국어 분석제목 수준이면 된다본문 품질이 곧 만족도다
부하아직 여유가 있다검색이 다른 기능을 끌어내린다
운영맡을 사람이 없다맡을 사람이 있다

그리고 순서에 대한 조언 하나. 엔진으로 가더라도 한 번에 다 옮기지 않는다. 검색이 가장 아픈 화면 하나만 먼저 엔진에 태우고, 나머지는 DB에 둔다. 그러면 동기화 대상이 좁고, 잘못 판단했을 때 되돌리기도 쉽다. 처음부터 모든 데이터를 색인하겠다고 시작하면 도입 자체가 반년짜리 일이 된다.

정리

  • 엔진을 들인다는 건 저장소를 하나 더 운영한다는 뜻이다. 검색 속도를 그 값으로 산다.
  • 먼저 볼 것은 셋이다. 앞을 고정한 검색으로 바꾸기, DB 내장 전문 검색, 결과 캐시. 전부 지금 저장소 안에서 끝난다.
  • DB 내장 전문 검색은 역색인을 DB 안에 만드는 것이라 이 시리즈의 이야기가 그대로 적용된다. 한국어 분석과 랭킹 손잡이에서 먼저 막힌다.
  • 넘어갈 신호는 뚜렷하다. 검색이 제품이 되고, 순위를 계속 손보고, 검색이 다른 기능을 끌어내릴 때다.
  • 넘어가면 동기화가 새 일로 생긴다. 어느 방식이든 지연이 있고 실패하고 어긋난다.
  • 그래서 원칙은 하나다. 원본은 DB에 하나, 색인은 언제든 다시 만들 수 있는 파생물. 판단 기준은 “색인을 통째로 지워도 되는가”다.
  • 옮기더라도 가장 아픈 화면 하나부터 옮긴다.