DEVOCEAN 블로그의 유사글 추천이 비어 있던 이유는 비용 때문에 벡터DB 를 걷어냈기 때문이다. 24시간 떠 있는데 얻는 값이 적었다. 인프라를 되살리는 대신 MySQL 저장 + 일 배치 계산으로 다시 설계해, 무료 티어 안에서 글 70%에 추천이 붙는 상태로 복구했다.
핵심 포인트- 벡터DB 없이 float32 배열을 MySQL 에 넣어 1,291건 임베딩 전체가 약 5MB 에 들어간다
- 하루 한 번 배치로 167만 쌍 유사도를 몇 초 만에 전부 계산한다
- Gemini API 를 taskType SEMANTIC_SIMILARITY 로 호출해 의미 매칭 품질을 올렸다
- 코사인 유사도 임계값 0.85 는 추측이 아니라 실측으로 잡았다
- Gemini 배치 응답에 인덱스 필드가 없어 한 건만 어긋나도 이후 벡터가 전부 밀리므로 건수 검증이 필수다
상세 정리- 배경: 추천 기능의 값어치 대비 벡터DB 상시 가동 비용이 커서 먼저 걷어냈고, 그 결과 추천 영역이 빈 채로 남았다
- 재설계 방향: 전용 인프라를 다시 세우는 대신 이미 있는 MySQL 과 배치로 옮겨 상시 비용을 0 으로 만들었다
- 저장 방식: 임베딩을 float32 배열로 직렬화해 컬럼에 넣었고 전체 용량이 5MB 수준이라 부담이 없다
- 계산 방식: 전량 비교가 1,291건 기준 167만 쌍인데 배치에서 수 초에 끝나 실시간 인덱스가 필요 없다
- 증분 처리: 본문 SHA-256 해시로 변경 여부를 판단해 새 글이나 수정된 글만 다시 임베딩한다
- 입력 선택: 전문 대신 요약 텍스트를 임베딩해 노이즈를 줄였다
- 임계값: 0.85 아래는 추천에서 제외하며, 이 값은 실제 결과를 보고 확정했다
- 배치 응답 함정: Gemini 가 인덱스를 안 돌려주므로 요청 순서에 의존하는데, 한 건이 빠지면 그 뒤 전부가 잘못된 글에 매핑된다
- 방어책: 응답 건수를 요청 건수와 대조해 불일치면 배치를 중단한다
- 신선도 방어: API 실패로 코퍼스 커버리지가 임계 아래로 떨어지면 오래된 추천이 남지 않도록 막는다
- 장애 1: API 키가 빈 문자열로 들어가 댓글과 임베딩이 동시에 403 으로 조용히 깨졌다
- 장애 2: git 시맨틱 충돌로 중복 메서드 정의가 컴파일 단계까지 발견되지 않았다
- 결과: 상시 비용이 무료 티어로 내려갔고 운영 부담이 사라졌으며 글의 70%가 추천을 받는다
왜 읽나추천이나 유사도 기능에 벡터DB 를 붙였다가 비용이 부담스러운 팀에게, 규모가 작을 때 배치와 관계형 DB 로 충분하다는 구체적 근거를 준다.