사용자에게 업로드된 사진 수를 표시하기 위해
가장 단순하고 직관적인 방식은 필요할 때마다 개수를 직접 세는 것이다.
기존 운영에서는 이 방식이 큰 문제가 되지 않았다.
데이터 양도 많지 않았고, 호출 빈도 역시 낮았기 때문이다.
문제는 데이터가 쌓이기 시작하면서 발생했다
서비스가 운영되면서 다음과 같은 변화가 생겼다.
- 사진 데이터가 지속적으로 누적됨
- 업로드 및 조회 트래픽 증가
- 동일 조건의 count 쿼리가 짧은 시간에 반복 실행됨
그 결과, 단순한 count 쿼리 하나가 API 응답 속도에 영향을 주기 시작했다.
특히 다음과 같은 상황에서 체감이 컸다.
- 목록 화면 진입 시 여러 종류의 사진 개수 표시
- 업로드 직후 전체 개수 갱신
- 사용자/타입별 개수를 동시에 조회하는 경우
기능은 단순하지만, 성능 비용은 점점 커지고 있었다.
그래서 선택한 방법: count 결과를 “저장”하기
문제의 본질은 명확했다.
매번 같은 조건으로 전체 데이터를 다시 세고 있다는 것
이를 해결하기 위해, 사진 업로드 시점에 개수를 관리하고
조회 시에는 이미 계산된 값만 읽는 구조로 변경하기로 했다.
처음 시도한 방식: +1 / -1 카운트 관리
가장 먼저 떠올릴 수 있는 방식은 아래와 같다.
사진 업로드 → count +1 사진 삭제 → count -1
구현도 단순하고 성능도 좋아 보였다.
하지만 실제 운영 환경에서는 이 방식이 생각보다 쉽게 어긋났다.
왜 +1 / -1 방식은 쉽게 틀어질까?
운영하면서 확인한 주요 원인은 다음과 같다.
1. 트랜잭션 경계 문제
- 사진 저장은 성공
- count 업데이트는 실패
→ 두 작업이 분리되면 불일치 발생
2. 중복 요청
- 네트워크 타임아웃
- 모바일 업로드 재시도
- 동일 요청이 여러 번 처리됨
3. 예외 흐름 누락
- 예외 발생 후 롤백 처리 불완전
- count만 먼저 변경되는 경우
4. 상태 변화 누락
- soft delete
- 승인/비승인
- 사진 타입 변경
+1 방식 자체가 문제라기보다,
현실적인 예외 상황을 모두 감당하지 못하는 것이 문제였다.
그래서 선택한 타협안: +1 방식 + 주기적 배치 갱신
+1 / -1 방식이 어긋날 수 있다는 점은 분명했지만,
그렇다고 매번 COUNT(*)를 수행하기에는
이미 성능 문제가 발생하고 있는 상황이었다.
또한, 시간과 리소스 제약상
Redis와 같은 별도의 캐시 서버를 도입하기도 쉽지 않았다.
그래서 최종적으로 선택한 방식은
실시간 +1 방식과 주기적인 배치 재계산을 함께 사용하는 구조였다.
적용한 방식 정리
구조는 비교적 단순하다.
- 사진 업로드 시점에는 count를 +1로 갱신
- 조회 시에는 저장된 count 값만 사용
- 일정 시간마다 배치 작업을 통해 원본 사진 데이터를 기준으로 count를 다시 계산
이렇게 하면 실시간으로는 빠른 응답 속도를 확보하면서도,
일시적으로 발생할 수 있는 불일치는
배치 작업을 통해 자연스럽게 보정할 수 있다.
이 방식이 현실적인 이유
다행히 지금 운영하는 서비스는
사진 개수는 금액이나 정산처럼 절대적으로 정확해야 하는 데이터는 아니다.
약간의 지연이나 일시적인 오차는 허용되지만,
화면 응답 속도는 반드시 보장되어야 한다.
이런 특성을 고려했을 때,
- 실시간 정확성에 집착하지 않고
- 복잡한 동기화 로직을 줄이며
- 운영 중에도 복구 가능한 구조를 선택하는 것이
현실적인 판단이었다.
정리하며
이 구조의 핵심은 간단하다.
count를 “정답”으로 취급하지 않고,
언제든 다시 계산할 수 있는 값으로 관리한다.
완벽한 구조는 아니지만,
현재 상황에서 가장 안전하고 구현 비용이 낮은 선택이었다.
추후 트래픽이 더 증가하거나
캐시 서버를 도입할 여유가 생긴다면
그때 구조를 다시 개선해도 늦지 않다고 판단했다.
'Programmer > JAVA' 카테고리의 다른 글
| CCTV RTSP 연동 삽질기, MJPEG 부터 go2rtc 적용 (0) | 2026.03.20 |
|---|---|
| 다시 Selenium SessionNotCreatedException 발생으로 인한 다운로드 경로 정리(ChromeDriver 140 ~ ChromeDriver 147) (0) | 2026.02.06 |
| Java 배치 처리에서 구독 서비스 토큰 관리 설계 고민 (0) | 2026.01.09 |
| Java Gemini API 파일 업로드가 실패하는 이유: state ACTIVE 기다려야 하는 이유 (0) | 2026.01.06 |
| Chrome 143 업데이트 후 Selenium SessionNotCreatedException 해결 방법 (0) | 2026.01.02 |