You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ 가장 중요한 위험: 지표를 하나씩 그때그때 추가하는 방식으로 만들면, 나중에 새 지표("물품 상세 조회수", "검색 키워드 빈도", "특정 버튼 클릭률" 등)가 필요할 때마다 새 컬렉션/스키마/마이그레이션이 생긴다. 운영 중 스키마 변경은 사고 위험이 크다.
🛠️ 해결 방안 / 제안 기능
핵심 설계 원칙 — 지표별 테이블이 아니라 "공통 이벤트 스트림" 하나로 통합한다.
모든 사용자 행동을 단일 이벤트 스키마로 쌓고, 모든 지표(방문자/동접/조회수/클릭률 등)는 이 스트림에서 집계로 파생한다. 새 지표 추가 = 코드(집계 쿼리)만 추가, DB 스키마/마이그레이션 변경 없음. MongoDB의 schemaless·Time Series 특성을 그대로 활용한다.
1. 공통 이벤트 스키마 (UserActivityEvent, MongoDB Time Series)
고정 공통 필드 + 자유 확장 필드(properties)로 구성. 모든 이벤트가 같은 컬렉션에 쌓인다.
공통 필드 (모든 이벤트 공유):
eventId UUID
eventType String // "PAGE_VIEW", "API_CALL", "SESSION_START", "SESSION_END",
// "ITEM_VIEW", "SEARCH", "TRADE_CREATED" ... (enum 확장만 하면 됨)
eventCategory String // "TRAFFIC" | "ENGAGEMENT" | "TRANSACTION" ... (대분류, 집계 그룹핑용)
memberId UUID? // 비로그인은 null
anonymousId String? // 비로그인 식별(쿠키/디바이스)
sessionId String? // 동접/세션 집계용
path String?
ip String?
userAgent String?
platform String? // AOS / iOS / WEB
occurredAt Instant // Time Series timeField
확장 필드 (이벤트별 자유):
properties Map<String,Object> // ex) {"itemId":"...", "keyword":"닌텐도", "price":50000}
→ 새 이벤트 종류는 eventType enum에 값 하나 추가하고 properties에 필요한 키를 담으면 끝. 컬렉션/인덱스/마이그레이션 불변.
2. 수집 지점 (공통 진입점으로 일원화)
지표마다 수집 코드를 흩뿌리지 않고, 단일 기록 진입점(ActivityEventRecorder)을 두고 모든 수집 지점이 그것만 호출한다.
지표
수집 지점
기록되는 eventType
방문자(PV/UV)
Spring HandlerInterceptor
PAGE_VIEW / API_CALL
현재 온라인
기존 채팅 WebSocket connect/disconnect
SESSION_START / SESSION_END (+ Redis 실시간 set)
시간별 동접
스냅샷 스케줄러
집계 결과를 동일 스트림 or 파생 롤업에 적재
(확장) 물품 조회·검색·클릭
각 도메인 서비스에서 recorder 호출만 추가
ITEM_VIEW / SEARCH / CLICK ...
3. 집계 (파생)
실시간: Redis online set (현재 온라인) — 누적 DB 아님, 정확.
일·시간별: MongoDB Aggregation + (선택) 롤업 컬렉션으로 사전 집계해 차트 쿼리 비용 절감.
모든 집계는 eventType/eventCategory/기간으로 그룹핑 → 새 지표도 같은 패턴 재사용.
4. 설계 원칙 (운영 안정성)
적재는 반드시 비동기(@Async/이벤트/큐) — 요청 지연 방지, 유실 시 본 요청 영향 없음.
MongoDB Time Series Collection(5.0+) + TTL 인덱스 — 원본 raw 이벤트는 N일 자동 만료, 롤업은 장기 보존.
차트: ApexCharts (시계열·실시간·줌·툴팁 내장, Tailwind/daisyUI 색 통일). daisyUI엔 차트 컴포넌트 없음(stat/radial-progress뿐) — 추이 그래프엔 부적합.
📝 현재 문제점
🛠️ 해결 방안 / 제안 기능
핵심 설계 원칙 — 지표별 테이블이 아니라 "공통 이벤트 스트림" 하나로 통합한다.
모든 사용자 행동을 단일 이벤트 스키마로 쌓고, 모든 지표(방문자/동접/조회수/클릭률 등)는 이 스트림에서 집계로 파생한다. 새 지표 추가 = 코드(집계 쿼리)만 추가, DB 스키마/마이그레이션 변경 없음. MongoDB의 schemaless·Time Series 특성을 그대로 활용한다.
1. 공통 이벤트 스키마 (
UserActivityEvent, MongoDB Time Series)고정 공통 필드 + 자유 확장 필드(
properties)로 구성. 모든 이벤트가 같은 컬렉션에 쌓인다.→ 새 이벤트 종류는
eventTypeenum에 값 하나 추가하고properties에 필요한 키를 담으면 끝. 컬렉션/인덱스/마이그레이션 불변.2. 수집 지점 (공통 진입점으로 일원화)
지표마다 수집 코드를 흩뿌리지 않고, 단일 기록 진입점(
ActivityEventRecorder)을 두고 모든 수집 지점이 그것만 호출한다.HandlerInterceptorPAGE_VIEW/API_CALLSESSION_START/SESSION_END(+ Redis 실시간 set)ITEM_VIEW/SEARCH/CLICK...3. 집계 (파생)
eventType/eventCategory/기간으로 그룹핑 → 새 지표도 같은 패턴 재사용.4. 설계 원칙 (운영 안정성)
@Async/이벤트/큐) — 요청 지연 방지, 유실 시 본 요청 영향 없음.⚙️ 작업 내용
공통 수집 인프라 (이 이슈의 본체)
UserActivityEvent문서 정의 (공통 필드 +propertiesMap) — MongoDB Time Series Collection 생성, timeField=occurredAt, TTL 인덱스EventType/EventCategoryenum 정의 (확장 가능하도록 분류 체계 설계)ActivityEventRecorder단일 기록 진입점 (비동기 적재) — 모든 수집 지점이 이것만 호출@Async풀 또는 이벤트 발행) + 화이트리스트(정적/헬스체크/admin 자체 호출 제외)수집 지점 연결
HandlerInterceptor—PAGE_VIEW/API_CALL기록SESSION_START/SESSION_END+ Redis online set(TTL stale 정리)집계 / API
AdminAnalyticsService신규 (RomRom-Application/service)@ModelAttribute, 별도 DTO 금지):POST /api/admin/analytics/realtime— 현재 온라인 / 오늘 UV·PV / 신규가입·신규거래POST /api/admin/analytics/timeseries—eventType(oreventCategory) + 기간 + 버킷단위(시간/일) 범용 시계열. 새 지표가 추가돼도 이 API 하나로 커버 (파라미터만 다름)AdminRequest/AdminResponse필드 추가AdminAnalyticsControllerDocs+@ApiChangeLog최상단 추가Frontend
검증 / 성능
eventType1개 추가 시 DB 마이그레이션 없이 timeseries API로 즉시 집계되는지 확인 (설계 목표 충족 여부)관련 이슈
🙋♂️ 담당자