Post

검색 성능 측정

검색 성능 측정

지난 시간

저번 시간에 pgvector로 바꿔봤죠. 그리고 계산을 해보니깐 생각보다 얼마 빨라지지 않았습니다. 그래서, 이번 시간에는 DB안에 데이터 개수를 늘려봤습니다.

데이터 늘리기

별거 없이 그냥

1
2
INSERT INTO news (title, content, summary, url, source, published_at, embedding)
SELECT title, content, summary, url, source, published_at, embedding FROM news;

이렇게 기존 데이터를 그대로 복사했습니다. 한번 하면 600건, 두번 하면 1200건… 19200건까지 해서. 300건, 2400건, 19200건을 기준으로 잡아봤습니다. 같은 데이터기는 하지만, 모든 행을 하나씩 다 보기 때문에, 속도 측정하는데에는 상관이 없습니다.

측정

측정 방식은 저번처럼 ai 에이전트 통해서, 단어 50개를 5번씩 검색시켜서 중앙값이랑 95%가 이 값보다 빨랐다는 p95 값을 봤습니다

pgvector

구분중앙값p95
300건196.8 ms233.8 ms
2400건211.2 ms246 ms
19200건287.4 ms334.3 ms

자바 캐시

구분중앙값p95
300건196.7 ms230.2 ms
2400건239.8 ms285.7 ms
19200건525.2 ms568.6 ms

데이터가 커지니깐, 이제는 차이가 꽤 유의미해보이죠?

그렇다면 캐쉬가 없는 원시고대시절의 제 코드는 어떨까요?

캐시 없음

구분중앙값p95
300건225.3 ms273.1 ms
2400건427.2 ms460.8 ms
19200건2136.8 ms2316.7 ms

에이전트가 잘못 측정한 줄 알고, 몇번 다시 돌려봤는데, 값은 다 비슷했습니다.

100배 늘면 100배 느려짐.

API 호출 시간을 빼야 순수 계산 비용이 나오는데, 그게 정확히 얼마인지 모르잖아요.

그래서 빼는 대신 없애버렸습니다.

2400건이랑 19200건 일때를 빼면, 양쪽에 같이 들어있던 API 호출 시간이 사라지거든요.

(2136.8 − 427.2) ÷ (19200 − 2400) = 101.8μs

구분건당 소요 시간
캐시 없음 (조회 + 파싱 + 계산)101.8 μs
자바 캐시 (계산만)17 μs
pgvector (DB 안에서 스캔)4.5 μs

캐시 없음은 19200건을 전부 DB에서 가져와서 파싱하고, 자바 캐시는 메모리에 있는 것 만 계산을 하고, pgvector는 DB안에서 끝내고 10건만 보내요. 하는 일이 적어지니깐, 건당 비용도 줄어드는거죠.

하나씩 다 훑어요. 즉, O(n)입니다.

선형이요. 100배 늘면 100배 느려집니다. 그렇다면, 하나씩 다 안훑게 만들면 되겠죠? 바로 인덱스를 활용하는겁니다.

인덱스

백터는 일반 인덱스처럼 목차를 쓰지는 않습니다. 일반 인덱스는 A,B,C. 가,나,다. 처럼 순서가 있는데, 벡터에서는 순서가 없습니다. 제가 이전 글에서 벡터는 방향이라고 했죠.

친구가 편의점을 가르키고, 제가 반대 방향을 가리킨다고 다시 가정해봅시다.

어느게 먼저 인가요? 편의점을 가리키는 친구? 아니면 반대를 가리키는 저? 선뜻 답을 하기 힘들죠? 그래서 순서가 없습니다.

벡터에서의 인덱스는 그럼 뭐냐면, 지하철 노선 처럼 점끼리 선으로 이어버리는데, 검색할때 그 선을 따라서 목적지에 가장 가까운곳으로 가는 그런 메카니즘 입니다.

우리가 지하철 타고 어디론가 갈때 급행 타고 그러잖아요? 근데, 급행을 타다 보면 특정 역을 놓칠 수가 있겠죠.

그런거 처럼, 벡터 인덱스도 특정한 검색 결과를 놓칠 수가 있습니다.

검색어랑 제일 비슷한 게 E라고 쳐볼게요.

B에서 출발해서 -> C -> D -> E, 이렇게 선을 타고 갑니다. 19,200개를 다 안 보죠.

근데 안 들른 F가 사실 E보다 가까웠을 수도 있어요. 길이 안 이어져 있으면 못 찾습니다.

이게 인덱스의 단점이에요. 있었는데요 없었습니다. 가 될 수도 있습니다.

그래서 실제로 확인해봤습니다. 검색어 50개로 인덱스 있을 때와 없을 때 상위 10건을 비교해봤는데, 500건 중에 인덱스가 놓친 기사는 0건이었습니다. 49개는 순서까지 완전히 같았고요.

딱 하나, ‘경제’에서 8·9위가 서로 바뀌었는데 두 기사 점수 차가 0.0001 수준이라 사실상 동점입니다.

다만 두 조건을 따로 돌려서 검색어 임베딩이 미세하게 달라졌기 때문에, 이 순서가 바뀐 게 인덱스 때문인지 임베딩 차이 때문인지는 못 가렸습니다.

pgvector + 인덱스

구분중앙값p95
19200건209.2 ms270.6 ms

이런 값이 나옵니다. 300건이나 2400건은 큰 차이가 없을거 같아서 뺐습니다.

정리해보면,

방식300건2,400건19,200건
캐시 없음225.3427.22,136.8
자바 캐시196.7239.8525.2
pgvector196.8211.2287.4
pgvector + 인덱스——209.2

확실히 빨라졌죠? 이제는 API 호출 시간을 제외하면, 거의 딜레이가 없다고 봐도 무방하겠죠?

다만 인덱스 측정은 복제 데이터에서 행한 측정값이라서, 벡터가 아마 넓게 분포 되어 있지는 않기에, 실제로 각기 다른 19200건이었다면, 아마 측정보다 더 소요가 될거라는 점, 감안하시고요.

엥? 아까 상관 없다면서요? 라고 하실 수 있는데, 맞습니다. 그거는 아까 말했듯이, 전체를 순회할때는 상관이 없는데, 인덱스는 상관이 있습니다.

다음 시간

다음 시간에는 이제 API 호출하는 시간도 아까우니깐, 검색어 벡터를 재사용하는 방법을 한번 생각해 볼까 합니다.

This post is licensed under CC BY 4.0 by the author.