2026년 11월 2일, 블로그 글 제목의 중복 판별 로직을 손봤다. 기존에는 저장된 임베딩을 Python으로 모두 읽어 유사도를 계산했는데, 이번에는 MariaDB 11.8+의 벡터 인덱스에서 가까운 제목 후보만 가져오도록 바꿨다.

이때 챙겨야 할 부분이 SQL의 정렬식이었다. ORDER BY 1 - VEC_DISTANCE_COSINE(...) DESC는 유사도 순서를 표현하지만, 거리 함수를 계산식으로 감싸면 벡터 인덱스를 활용하지 못할 수 있다. 공식 문서는 거리 함수 또는 그 별칭을 그대로 오름차순 정렬하고 LIMIT을 함께 쓰도록 설명한다. MariaDB 공식 문서

그래서 검색은 다음처럼 거리 기준으로 두고, 반환된 값만 Python에서 similarity = 1 - distance로 바꿨다. 아래는 핵심만 줄인 SQLAlchemy 바인딩 예시다. :query_vector에는 같은 임베딩 모델이 만든 벡터의 JSON 문자열을 전달한다.

sql
SELECT translation_id,
       VEC_DISTANCE_COSINE(embedding, VEC_FromText(:query_vector)) AS distance
FROM title_vectors
ORDER BY VEC_DISTANCE_COSINE(embedding, VEC_FromText(:query_vector)) ASC
LIMIT 10;

이 예제는 embedding에 DISTANCE=cosine 벡터 인덱스가 있다는 전제다. 인덱스를 만들 때의 거리 방식과 검색 함수가 다르면 인덱스를 사용하지 않는다. VEC_DISTANCE_COSINE 문서

이번 변경에는 EXPLAIN 결과의 key가 기대한 벡터 인덱스인지 검사하는 통합 테스트도 추가했다. 성능 개선 폭은 별도 측정이 필요하지만, 전체 벡터를 Python으로 읽던 검색 경로를 후보 조회로 줄인 점은 코드에서 확인할 수 있다.

인덱스를 만들었다면, 쿼리가 실제로 그 인덱스를 쓰는지까지 확인하자. 수식으로 같은 순서를 표현해도 실행 계획까지 같아지는 것은 아니었다.