배경: 왜 캐싱이 필요했을까?
우리가 구축 중인 서비스에서 사용자가 특정 요청을 보낼 때마다 AI 서버의 "매칭 결과 반환 API"를 호출해 추천 사용자 리스트를 받는다. 문제는 이 API가 AI 모델을 기반으로 작동하다 보니 계산 비용이 크고 응답 속도도 느리다. 따라서 동일한 사용자가 동일한 조건으로 여러 번 요청할 경우, 이미 계산된 결과를 재사용하는 캐싱 전략이 필요하다는 것으로 판단했다.
처음 생각한 방안: Redis vs MySQL
캐싱을 위한 저장소로 가장 먼저 떠오른 후보는 다음 두 가지였다.
- Redis: 인메모리 저장소로 빠른 응답 속도 보장
- MySQL: 영구 저장이 가능한 관계형 데이터베이스
Redis의 한계: 영속성
Redis는 빠르고 가볍지만 기본적으로 휘발성 인메모리 구조를 가진다.
물론 RDB나 AOF 방식으로 영속성을 보장할 수는 있지만, 여전히 다음과 같은 한계가 있다.
- 장애 시 복구 불확실성
- 디스크에 쓰기 위한 오버헤드 발생
- 백업 주기 설정이 필요함
결론적으로, 중요한 추천 결과를 영구 저장해야 한다면 MySQL을 병행해서 사용하는 것이 안전하다는 판단을 내렸다.
MySQL 테이블 구조 설계
- 처음 시도한 JSON 방식
CREATE TABLE match_results (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
matched_user_ids JSON NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
이 구조는 간단하고 직관적이지만 JSON 필드 기반 검색은 인덱스 지원이 불완전해서 추천 유저 중 특정 ID가 포함되어 있는지를 빠르게 찾는 데 한계가 있다.
- 개선된 테이블 구조 (정규화)
CREATE TABLE match_sessions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
category VARCHAR,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE match_session_results (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
match_session_id BIGINT NOT NULL,
matched_user_id BIGINT NOT NULL,
order_index INT NOT NULL,
FOREIGN KEY (match_session_id) REFERENCES match_sessions(id)
);
ALTER TABLE match_sessions
ADD CONSTRAINT uq_user_category UNIQUE (user_id, category);
- UNIQUE 인덱스로 같은 user_id와 category 조합으로 중복 INSERT가 불가능 제약 조건 추가
- match_sessions: 매칭 요청 단위
- match_session_results: 매칭 결과 유저 ID 리스트
- order_index: AI 서버가 리턴한 정확한 순서 보장용 필드 ⭐️
👉 RDB는 행의 저장 순서를 보장하지 않기 때문에, 정렬 순서를 보장하려면 order_index는 반드시 필요!
성능과 쿼리 효율 체크
한 사용자의 특정 카테고리(커플, 친구, 밥친구)에 대한 매칭 결과를 저장할 때 다음과 같은 프로세스가 이루어진다.
- Spring JPA
// 1. 매칭 세션 저장
matchSessionRepository.save(session); // INSERT 1회
// 2. 매칭된 유저 리스트 저장 (벌크 Insert)
matchSessionResultRepository.saveAll(results); // INSERT 1회
- MySQL
INSERT INTO match_sessions (user_id, category) VALUES (1, 'default');
INSERT INTO match_session_results (match_session_id, matched_user_id, order_index)
VALUES (101, 30, 0), (101, 1, 1), (101, 5, 2), ...;
👉 매칭 리스트가 10명이든 100명이든 단 2개의 쿼리만으로 처리 가능!
AI 서버에서 제공한 추천 순서 그대로 사용자에게 한 명씩 보여줘야 하므로, 데이터 조회 시에는 다음과 같은 1개 쿼리로 데이터 순서대로 조회
SELECT r.matched_user_id
FROM match_sessions s
JOIN match_session_results r ON s.id = r.match_session_id
WHERE s.user_id = :userId
AND s.category = :category
ORDER BY r.order_index ASC
LIMIT 1;
👉 데이터 꺼낼 때도 단 하나의 쿼리로 처리 가능!
끝.
'카카오테크 부트캠프' 카테고리의 다른 글
[기술 검토 및 선정] 로컬 캐시(Local Cache) 기술 선정 (0) | 2025.04.27 |
---|---|
[기술 검토 및 선정] NoSQL 선택 (Redis VS Memcashed) (0) | 2025.04.22 |
[기술 검토 및 선정] 양방향 통신과 Message Broker (0) | 2025.04.21 |
복합키 vs 정수형 단일 기본 키 (with UNIQUE) (0) | 2025.04.20 |
직접 설계한 API, RESTful 설계부터 예외 처리까지 (0) | 2025.04.20 |