일단 추천 알고리즘이 들어가기 시작하면 매우 복잡해진다. 여기선 아주 단순하게 구현할 수 있는 방법을 적어본다.
실시간으로 현재 인기 있는 게시글 상위 10개 정도를 추천하기 위해서 판단 기준이 필요하다.
- 구매순
- 조회순
- 좋아요순
- 댓글순
활동 로그를 이용한 실시간 적재
유저가 좋아요, 구매 등이 발생하면 이 테이블에 로그를 기록해 둔다.
ACTIVITY_LOG_TABLE
Logical Name | Physical Name | Data Type | Length | 비고 |
활동로그 번호 | ACTIVITY_LOG_NO | number | 19 | PK |
게시글 번호 | POST_NO | number | 19 | FK |
활동 종류 | ACTIVITY_TYPE | varchar2 | 20 | ORDER, VIEW, LIKE, COMMENT |
활동 수 | ACTIVITY_CNT | Int | ||
생성자 | CREATED_BY | number | 19 | |
생성일시 | CREATED_AT | timestamp |
Index는 POST_NO, CREATED_AT 두 컬럼을 사용한다.
유저가 게시글에 구매, 조회, 좋아요, 댓글이 달리면 기록하기 위한 테이블인데 물론 행동이 발생할 때마다 1 row씩 삽입하는 건 꽤나 비효율적일 것이다. 그래서 Redis 같은 Cache Store에 먼저 등록해 두고 일정 주기로 DB로 insert 할 수 있도록(Write-Back) ACTIVITY_CNT 컬럼을 두었다.
Redis 사용 전략
Value를 increment 만큼 증가시키는 HINCRBY 커맨드를 사용한다.
# 특정 사용자(77777)에 대해 views 증가
HINCRBY post:metrics:{postNo}:{userId} views 1
# 좋아요와 댓글은 삭제가 발생할 수 있다. 삭제 API 호출시에는 -1을 increment 해준다.
HINCRBY post:metrics:{postNo}:{userId} comment -1
# 모든 사용자 키 검색
KEYS post:metrics:{postNo}:*
다만 현재 키 계층구조에서 게시글 이후의 업데이트 대상 key만을 조회하려고 한다면 (모든 사용자 키 검색) 경우에 따라 사용하는 커맨드가 달라질 수 있다.
KEYS 커맨드는 Cluster Mode에서 사용할 수 없다. 정확히는 목적지 Node를 명시해야 해당 Node 에서만 탐색이 가능하다. 따라서 이 상황에서는 SCAN 명령어를 통해 조회하는 편이 낫다.
HGETALL 커맨드도 사용할 수 없고 키 조회 자체가 정책적으로 금지되어있다면 차라리 키만 SET에 담아서 DB에 insert 하고 지우는 방법도 있겠다.
# 메트릭을 저장할 Set 생성
sadd post:metrics post:metrics:{postNo}:{userId}
smembers post:metrics
5분 정도 주기를 설정해서 Redis에 누적된 데이터를 DB로 전송하는 스케줄링 작업을 세팅한다. 스프링 멀티모듈 사용하고 있다면 batch 모듈 만들어 작업할 수 있고 단일 작업이라 jenkins 같은 cicd 툴 이용해서 엔드포인트 호출하는 식으로 가능할 것 같다. cron 도 가능할 거고.
가중치를 이용한 분석 및 노출
특정 시간에 실시간 추천 게시글로 노출이 됐던 목록을 알아내려면 조회용 테이블이 따로 필요하다.
로그 테이블에 있는 데이터를 기준으로 조회 테이블에 데이터를 전부 다 넣고, 가중치에 따른 점수 계산을 실행한다.
그리고 기존 데이터는 inactive 상태로 변경한다.
POST_METRICS_TABLE
Logical Name | Physical Name | Data Type | Length | 비고 |
메트릭 번호 | METRICS_NO | number | 19 | PK |
게시글 번호 | POST_NO | number | 19 | FK |
구매수 | ORDERS | number | 5 | |
조회수 | VIEWS | number | 5 | |
좋아요 수 | LIKES | number | 5 | |
댓글수 | COMMENTS | number | 5 | |
점수 | SCORE | number | 5,2 | |
상태 | STATUS | varchar | 10 | ACTIVE, INACTIVE, DELETE |
생성자 | CREATED_BY | number | 19 | |
생성일시 | CREATED_AT | timestamp | ||
수정자 | MODIFIED_BY | number | 19 | |
수정일시 | MODIFIED_AT | timestamp |
Index는 POST_NO, SCORE, CREATED_AT 를 사용한다.
그리고 가중치를 조절할 수 있도록 테이블을 생성하고 admin 서비스를 통해 조절하게 한다.
POST_METRICS_WEIGHT_TABLE
Logical Name | Physical Name | Data Type | Length | 비고 |
메트릭 번호 | WEIGHT_NO | number | 19 | PK |
활동 종류 | ACTIVITY_TYPE | number | 19 | ORDER, VIEW, LIKE, COMMENT |
가중치 | WEIGHT | number | 5,2 | |
상태 | STATUS | varchar | 10 | ACTIVE, INACTIVE, DELETE |
생성자 | CREATED_BY | number | 19 | |
생성일시 | CREATED_AT | timestamp | ||
수정자 | MODIFIED_BY | number | 19 | |
수정일시 | MODIFIED_AT | timestamp |
그리고 초기 데이터를 넣어주면 된다.
'Back-end' 카테고리의 다른 글
Nest.js에 대한 개인적인 생각 (7) | 2024.10.26 |
---|---|
Protobuf와 Map Struct 친해지길 바래 (0) | 2024.10.15 |
[Redis] 나야 조회수 (3) | 2024.10.15 |
[Redis] Redis 데이터 저장 근데 protobuf를 곁들인 (3) | 2024.10.14 |
[gRPC] Armeria + gRPC 띄워보기 (7) | 2024.10.07 |