업데이트 압축률 67%, 플레이스 리뷰 tagging 시스템 개선경험
안녕하세요, 플레이스 리뷰플랫폼 개발팀 강준우입니다.
저희는 네이버 플레이스 서비스인 MY플레이스를 포함해, LINE PLACE, Y!Map 등으로부터 등록된 방문자리뷰를 저장 및 관리하는 플랫폼 팀입니다. 리뷰 각 항목엔 여러 부가정보를 함께 태깅하고 있는데요, 이번에 전체 태깅프로세스를 재설계해 업데이트 부하 67%를 줄이는 등의 개선을 이룰 수 있었습니다. (같은 팀 Sungwon Kim님과 협업했습니다.)
- 간단히, 저희 팀에서 담당하는 태깅이 어떤 것인지 알아보고
- 기존 태깅시스템은 어떤 방식을 가지고 있었고, 어떤 아쉬운 점이 있었는지
- 개선한 태깅시스템은 어떤 형태이며, 어떻게 각 지표가 개선되었는지
이번 글을 통해 공유하고자 합니다.
1. 태깅이란?
- 리뷰이미지:
음식이 중앙에 있는
,플레이팅이 멋진
, ... - 리뷰텍스트:
긍정적인 단어가 많은
,중국음식이 언급된
,서비스에 대한 평가가 있는
위와 같이 이미지, 텍스트 등 리뷰 각 항목을 분석한 결과를 태그라고 하고, 태그를 뽑아내 리뷰와 함께 저장하는 과정을 태깅이라고 부르고 있습니다. 예시로 제가 MY플레이스에 등록한 다음 리뷰는 아래와 같이 태깅되어있습니다. (실제 더 많은 항목들을 태깅하고 있지만, 공개 제약으로 인해 두 가지 예시만 담았습니다.)
태그는 다음과 같이 다양한 형태로 사용되고 있고, 모델이 발전하고 서비스가 점점 많아짐에 따라 그 용도 또한 늘어나는 중입니다.
2. 기존 태깅시스템
플레이스 내 AI 개발팀을 포함한 Naver AI팀들은 다양한 모델을 활용해 이미지/텍스트 분석을 담당해주시고 계시는데요. 저희 플레이스 리뷰플랫폼 개발팀에선 이러한 분석 API를 호출 후, 해당 분석값으로부터 태그를 추출해 저장합니다. 가장 단순한 방법으로는
리뷰 등록/수정 API 호출 시, 분석 API를 호출해 그 결과를 한꺼번에 리뷰와 함께 DB에 저장하기
가 있을 것입니다. 그러나 위 방식은 한 리뷰에 여러 이미지를 업로드한 유저의 경우, 리뷰가 등록되기까지 몇십개의 분석 결과를 기다려야 하므로 사용자 경험이 좋지 못할 것입니다. 그래서 기존의 태깅 구조는 다음과 같이 별도의 배치로 분리되어 있었습니다.
리뷰 등록/수정 API 호출 이후, Kafka Consumer를 통해 리뷰가 insert/update될 경우 비동기적으로 태깅이 이뤄지도록 함
언뜻 보면 납득이 가는 분리일 텐데요, 점점 태깅이 고도화되며 다음 문제들이 발생했습니다.
(1) 변화하는 비즈니스 로직에 취약한 구조
하나의 분석 API엔 태그 하나보다 많은 정보가 담겨있고, 저희는 그 중 서비스에서 유의미한 정보를 태그로 추출하고 있습니다.
다음은 리뷰이미지 내 음식인식
태그값 추출의 가상 예시입니다.
분석 결과에선 3가지 음식이 탐지되지만, score 0.7 이상인 음식만 태깅한다 라는 비즈니스 로직에 따라 태그가 만들어집니다.
어느 날 음식태그 정확도가 떨어진다고 기획팀에서 판단해, threshold를 0.7에서 0.8로 올려달라는 요청을 했다고 가정해보겠습니다.
이전에 업로드된 모든 이미지들에 대해 소급해야 할 텐데, 기존 태깅시스템 상에선
위 과정을 리뷰DB 내 이미지 전체에 대해 반복해야 했습니다. 분석 API의 원본 응답값을 저장하고 있지 않아, 다시 처음부터 api를 호출해야 했고 이에 따른 비용이 낭비되곤 했습니다.
또한 5. 태그 추출
에 해당하는 비즈니스 로직이 3~6 (분석~태깅)
전체와 커플링이 되어 있었는데, 보다 복잡한 태깅 적용을 위해선 비즈니스 로직을 분리할 필요가 있었습니다. 그 예시로 N차태깅이 있습니다.
N차태깅: 태그 여러 개가 input이 되어 또다른 태그를 만드는 태깅
단순히 상위 값만을 취하는 게 아닌 점차 복잡한 비즈니스 로직이 등장하기 시작했고, 수월한 코드 관리를 위해 해당 로직의 분리가 필요했습니다.
(2) 운영 관련
기획에서 이전부터 태그 운영툴 관련 요청을 꾸준히 주셨으나, 다음 관련 문제들이 있었습니다.
각 음식사진들이 정확도 높게 태깅되고 있는지, 일목요연하게 운영툴에서 확인하고 싶어요.
태그들이 각자의 Kafka Consumer를 타고 여러 필드에 흩어져있어서 하나의 공통된 태그관리 운영툴을 만들기 어려웠습니다.
잘못 태깅된 값을 발견했을 때, 이를 수정하고 싶어요. (e.g. 쫄면인데 비빔국수로 태깅되어있어, 이를 쫄면으로 수정)
이 경우 개발자가 직접 수동으로 db update를 치곤 했습니다. 그러나 분석결과값
/ 운영자입력값
이 따로 구분되지 않는 형태였기에, 특정 상황에서 운영자 입력값이 유실될 가능성이 있었습니다. 예를 들어 (1)의 경우처럼 threshold가 0.8로 올라 전체 재태깅이 일어나게 된다면, 수정한 태그들이 모두 기존 태그로 초기화되곤 했습니다.
(3) 불필요하게 빈번한 DB 업데이트
태깅으로 인한 DB 업데이트가 굉장히 빈번하게 발생했었습니다. 예를 들어 한 이미지의 분석이 끝나면 이미지
컬렉션 뿐 아니라 리뷰
컬렉션이나 통계
컬렉션 등, 영향받는 다양한 컬렉션에 태그 전파가 이뤄지곤 했습니다.
(컬렉션 이름은 예시입니다.)
유저가 한 리뷰에 이미지 10개를 업로드한다면, 10개가 각각 비동기적으로 분석되므로 전파되는 컬렉션당 10번씩 추가 업데이트가 발생하곤 했었습니다. 전파될 컬렉션이 2개였다면, 리뷰 하나당 이미지 관련 DB 업데이트가 30번 일어났던 셈입니다.
문제되는 부분들을 종합해 보니, 개선할 포인트가 다음과 같이 보였습니다.
(1) 변화하는 비즈니스 로직에 취약한 구조
- 비즈니스 로직 변경 시 분석 API 재호출하지 않도록 분석값 원본 저장
- 태깅 비즈니스 로직 적용을 별도 모듈로 분리
(2) 운영 관련
- 운영툴에서 조회될 수 있도록 태그를 하나의 컬렉션으로 모으기
- 운영자 입력값을 기존 분석값과 구분
(3) 불필요하게 빈번한 DB 업데이트
- 업데이트 내용을 대기소에 쌓아두고, 한 번에 DB 업데이트
3. 개선된 태깅시스템
개선포인트를 반영할 수 있도록, 크게 두 가지 목표를 가지고 설계를 시작했습니다.
- (타겟의 실제 태깅 전까지) 태깅 과정동안 각 저장소를 거치도록 함
: 분석 api 호출 시 얻은분석값 원본
과 비즈니스 로직을 적용한분석값
이 각자의 저장소에 머무르게 하고, 이를 담당하는 Kafka Consumer를 역할별로 분리해 하나의 flow를 이루도록 했습니다. - (타겟의 실제 태깅시) write buffer의 도입
: 태그 업데이트가 같은 타겟별로 모아서 될 수 있도록, 업데이트 정보들이 태깅 전 머무르며 타겟별로 합쳐지는 공간을 도입하기로 했습니다. 실제 타겟에 쓰기 전 버퍼에 해당하므로, 이 공간을write buffer
로 부르기로 했습니다.
이를 간단하게 그려보면 다음과 같고,
이에 맞춰 구현한 결과 다음의 구조도가 나오게 되었습니다.
흐름을 설명하기 전, 파란색으로 동그라미 친 세 개의 컬렉션 이름을 짚고 가보겠습니다.
- 분석값 원본 저장소:
rawtags
컬렉션 - 분석값(태그) 저장소:
tags
컬렉션 - 태그 write buffer:
updatabletargets
컬렉션
분석 시작, rawtag 및 tag 생성
(sources => 1 => 2)
리뷰 insert를 감지한 카프카 컨슈머는, 기존 태깅로직과 동일하게 분석 api를 호출합니다. 그러나 실제 태깅은 다른 카프카 컨슈머들에게 맡기고, 분석결과를 rawtags
컬렉션에 그대로 저장하는 것까지만 담당합니다.
rawtag insert를 감지한 두 번째 카프카 컨슈머는, 해당 rawtag 타입에 매핑된 비즈니스 로직을 적용해 실제 태그를 추출하고, 이를 tags
컬렉션에 저장합니다. 이 과정에서 필요한 모든 태깅 비즈니스 로직은 각각의 독립적인 파일 및 모듈로 존재하고, 알맞은 태그 타입에 따라 불려오게 됩니다.
updatabletargets에 target 정보 등록, bulk로 업데이트 수행
(2 => 3 => targets)
tag insert를 감지한 세 번째 카프카 컨슈머는, updatabletargets
컬렉션에 "업데이트될 타겟id" 정보를 등록합니다. 이후 동일한 타겟에 대한 tag insert가 감지되어도, 이미 updatabletargets
컬렉션에 해당 타겟 정보가 등록되어 있으므로 추가 등록은 일어나지 않습니다. updatabletargets
컬렉션은 20분마다 한 번씩 Airflow Scheduling을 통해 타겟에 해당하는 모든 태그 조회 후, 타겟 하나당 업데이트 쿼리 한 번으로 모든 태그를 담아 업데이트합니다.
이를 현재 이미지태깅(분위기인식
/ 이미지점수
/ Object Detection
) 3개에 도입해보았는데요, 기존 대비 업데이트 압축률 67%가 나오는 것을 확인할 수 있었습니다. 이전에 따로 3번에 나누어 업데이트해야 했던 것을 1번만에 하는 셈입니다.
다음은 작성일 현재 10월 20일 점심시간대의 업데이트 로그입니다.
# KR 이미지태깅 업데이트 압축률 66.7%
Compressed 10274 updates to 3425 updates. Compression rate 66.7%
# JP 이미지태깅 업데이트 압축률 66.7%
Compressed 948 updates to 316 updates. Compression rate 66.7%
이 외에도 이미지
=> 리뷰
전파 또한 기존 대비 업데이트 횟수가 감소한 것을 확인할 수 있었습니다.
# KR 이미지 => 리뷰 업데이트 압축률 47.1%
Compressed 2518 updates to 1331 updates. Compression rate 47.1%
# JP 이미지 => 리뷰 업데이트 압축률 31.9%
Compressed 335 updates to 228 updates. Compression rate 31.9%
운영 관점에서
이전과 달리 tags
컬렉션에 모든 타겟&종류의 태그들이 모여있기 때문에, 위와 같이 필터 및 조회, 수정기능을 만들 수 있었습니다. 또한 태그 document에 valueByAdmin
필드를 두고, 운영자를 통한 수동 태그입력시 Place Data Lakehouse에 전송함으로서 다음 이점을 누릴 수 있었습니다.
- 운영자 입력값의 유실 가능성 낮춤
- ML 재학습 파이프라인 구축
마치며
rawtags
& tags
양이 워낙 빠르게 차오르다보니, 나중에 디스크 가용량을 많이 차지하게 되는 시점이 올 수도 있을 것입니다. 어느 정도 기간이 지나면 운영에 영향없는 구 데이터들을 삭제하는 것으로 조절할 수 있을 것이라 생각합니다.
크게 보면
- 비슷한 프로세스들을 한 flow로 묶고
- 여러 저장소와 대기소를 거치게 해 안정성과 성능을 높인
변화인데요, 꼭 태깅이 아닌 개발에서도 필요시 비슷하게 적용해보실 수 있을 것 같아 이번 경험을 담은 글을 포스팅해봅니다. 감사합니다.