Foundry
검색어 자동완성 설계
심화
핵심

답을 노드마다 미리 둔다

읽기를 공짜로 만드는 값을 쓰기가 낸다

앞 절에서 읽을 때 계산할 시간이 없다는 것을 확인했습니다. 그러면 답을 어디에 어떤 형태로 두는지가 다음 문제입니다.

노드마다 답을 들고 있는다

노드 아래를 모아 정렬하는 대신 노드마다 상위 다섯 개를 미리 들고 있는다 읽을 때 모으면 삼 노드 아래 단어 수만 건을 모아 정렬 100ms 를 넘는다 미리 들고 있으면 삼 노드 상위 5개가 이미 여기 있다 읽어서 그대로 보낸다 대가는 저장과 갱신이다. 노드 수만큼 목록이 생긴다 검색어 하나가 늘면 그 경로의 모든 조상 노드를 손봐야 한다 읽기를 공짜로 만드는 값을 쓰기가 낸다

접두사에 해당하는 노드를 찾는 것은 글자 수만큼 내려가면 되므로 값쌉니다. 문제는 그 아래에 있는 단어를 모아 정렬하는 것입니다. 인기 있는 짧은 접두사는 아래에 수만 건이 달려 있습니다.

그래서 노드마다 그 아래의 상위 5개를 미리 저장합니다. 읽기는 노드를 찾아 그 목록을 그대로 보내는 것으로 끝납니다.

방식읽기쓰기
읽을 때 모으기아래 단어 수에 비례값싸다
미리 저장노드 찾기만조상 노드를 다 손봐야 한다

읽기를 공짜로 만드는 값을 쓰기가 냅니다. 검색어 하나의 점수가 바뀌면 그 경로의 모든 조상 노드에서 순위가 바뀔 수 있습니다.

저장 크기를 계산한다

노드마다 목록을 두면 저장이 얼마나 되는지 봐야 합니다.

검색어 1억 종류, 평균 길이 10글자
공통 접두사를 공유하므로 노드 수는 검색어 수보다 적다
노드마다 5개 x (문자열 참조 + 점수)

여기서 중요한 판단이 하나 있습니다. 목록에 문자열을 그대로 담지 않습니다. 같은 단어가 조상 노드마다 반복되기 때문입니다. 열 글자 단어는 조상이 열 개라 열 번 저장됩니다.

식별자만 담고 문자열은 따로 두면 이 중복이 사라집니다. 대신 읽을 때 식별자를 문자열로 바꾸는 일이 생기는데, 그것은 다섯 개뿐이라 값쌉니다.

어느 노드까지 목록을 두나

모든 노드에 둘 필요는 없습니다.

노드목록을 두나
1에서 6글자 접두사둔다. 요청의 대부분이 여기다
아주 긴 접두사두지 않는다. 그 아래에 단어가 몇 개뿐이다

긴 접두사는 아래에 달린 단어가 적어서 읽을 때 모아도 100ms 안에 끝납니다. 목록을 둘 이유가 없습니다.

이 판단이 저장을 크게 줄입니다. 노드 수는 접두사가 길어질수록 급격히 늘어나므로, 긴 쪽을 빼면 대부분이 빠집니다.

점수는 무엇으로 하나

목록의 순서를 정하는 값은 검색된 횟수입니다. 그런데 언제부터의 횟수인지를 정해야 합니다.

전체 기간의 횟수를 쓰면 오래전 유행이 계속 남는다
최근 기간만 쓰면 꾸준한 검색어가 밀린다

두 값을 섞습니다. 최근 며칠에 무게를 더 주고 그보다 오래된 것에는 적게 줍니다. 하루 1회 갱신이므로 이 계산을 그때 한 번만 하면 됩니다.

면접에서 이렇게 나옵니다

Q.왜 노드마다 상위 다섯 개를 미리 저장합니까

읽을 때 그 아래를 모아 정렬할 시간이 없기 때문입니다.

접두사 노드를 찾는 것은 글자 수만큼 내려가면 되므로 값쌉니다. 문제는 인기 있는 짧은 접두사 아래에 수만 건이 달려 있다는 것입니다.

방식읽기쓰기
읽을 때 모으기아래 단어 수에 비례값싸다
미리 저장노드 찾기만조상 노드를 다 손봐야 한다

읽기를 공짜로 만드는 값을 쓰기가 냅니다. 요청이 초당 2만이고 갱신은 하루 1회이므로 이 거래가 유리합니다.

흔한 실수: 이 방식의 대가를 저장 크기로만 보는 것. 더 큰 대가는 갱신 비용입니다. 검색어 하나의 점수가 바뀌면 그 경로의 조상 노드에서 순위가 바뀔 수 있습니다.

Q.노드 목록에 검색어 문자열을 그대로 담으면 무엇이 문제입니까

같은 단어가 조상 노드마다 반복됩니다. 열 글자 단어는 조상이 열 개라 열 번 저장됩니다.

식별자만 담고 문자열은 따로 두면 이 중복이 사라집니다. 읽을 때 식별자를 문자열로 바꾸는 일이 생기지만 다섯 개뿐이라 값쌉니다.

검색어 1억 종류, 노드마다 5개 x (참조 + 점수)

흔한 실수: 읽기 횟수를 줄이려고 문자열을 넣는 것. 자동완성은 요청이 초당 2만이라 읽기를 줄이고 싶은 유인이 크지만, 저장이 몇 배가 되면 그 데이터가 메모리에 안 들어갑니다. 메모리에서 밀려나는 비용이 훨씬 큽니다.

Q.모든 노드에 목록을 두시겠습니까

짧은 접두사에만 둡니다. 긴 접두사는 읽을 때 모아도 됩니다.

노드목록을 두나
1에서 6글자 접두사둔다. 요청의 대부분이 여기다
아주 긴 접두사두지 않는다. 아래에 단어가 몇 개뿐이다

긴 접두사는 아래에 달린 단어가 적어서 모아도 100ms 안에 끝납니다. 그리고 노드 수는 접두사가 길어질수록 급격히 늘어나므로, 긴 쪽을 빼면 저장의 대부분이 빠집니다.

흔한 실수: 일관성을 위해 모든 노드에 두는 것. 구조는 단정해지지만 저장이 몇 배가 되고, 그 대가로 얻는 것은 이미 충분히 빠른 경우를 더 빠르게 만드는 것뿐입니다.

Q.목록의 순서를 정하는 점수를 어떻게 만드시겠습니까

최근 기간과 전체 기간의 횟수를 섞습니다.

전체 기간만 쓰면 오래전 유행이 계속 남는다
최근 기간만 쓰면 꾸준한 검색어가 밀린다

최근 며칠에 무게를 더 주고 오래된 것에는 적게 줍니다. 하루 1회 갱신이므로 이 계산을 그때 한 번만 하면 됩니다.

흔한 실수: 점수 계산을 읽기 경로에서 하려는 것. 읽기 경로에는 계산할 시간이 없습니다. 점수는 갱신할 때 정해지고 읽을 때는 이미 정렬된 결과를 봅니다. 이 분리가 앞 절의 결론을 지킵니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

검색어 자동완성 설계 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.