파트너 포털에 결제 KPI 실시간 폴링 기능 추가
목차
partner-portal 영역에 결제 KPI 실시간 폴링 기능을 추가했다. 변경된 파일은 내부 클래스 하나, 뷰+스타일 하나. 파일 수만 보면 작은 작업처럼 보이지만, 실제론 쿼리 레벨부터 권한 체크, 화면 렌더링까지 전 레이어를 손댔다.
파트너가 보는 대시보드에서 KPI 수치가 정적으로 고정되어 있으면 의미가 없다. 결제 집계는 분 단위로 바뀌는데 화면은 새로고침 전까지 어제 숫자를 보여주고 있으면, 그 데이터를 믿고 의사결정하는 사람 입장에서는 그냥 틀린 정보다. 이번 작업의 시작은 거기서였다.
폴링 vs 이벤트, 어느 쪽을 선택할 것인가
실시간 갱신 방식을 고를 때 SSE(Server-Sent Events)나 WebSocket을 먼저 떠올리기 쉽다. 그런데 파트너 포털 특성상 동시 접속자 수가 제한적이고, KPI 수치 자체가 초 단위로 변하는 게 아니라 집계 배치가 돌 때마다 바뀌는 구조였다. 그래서 커넥션을 유지하는 비용을 지불할 근거가 없었다.
폴링 주기를 어떻게 잡느냐가 실질적인 설계 포인트였다. 너무 짧으면 DB 집계 쿼리가 반복 실행되면서 부하가 생기고, 너무 길면 "실시간"이라는 의미가 희석된다. 쿼리 실행 계획을 먼저 확인하고 평균 응답 시간을 측정한 뒤 주기를 결정했다. 기존 인덱스로 충분히 커버되는 쿼리였기 때문에 선택한 주기에서 성능 영향은 없다고 판단했다.
| 방식 | 장점 | 단점 | 선택 근거 |
|---|---|---|---|
| Polling (AJAX) | 구현 단순, 인프라 변경 없음 | 불필요한 요청 발생 가능 | 접속자 수 적음, 집계 주기 기반 갱신 |
| SSE | 서버 푸시, 커넥션 재사용 | 서버 자원 점유, 레거시 브라우저 이슈 | 오버엔지니어링 |
| WebSocket | 양방향, 저지연 | 인프라 변경 필요, 복잡도 높음 | 단방향 갱신에 불필요 |
구현 흐름
백엔드는 기존 API 구조를 따라 새 엔드포인트를 만들었다. 권한 인터셉터 통과 여부를 먼저 확인하고, 집계 쿼리 결과를 JSON으로 내려주는 구조다. 내부 클래스 한 개 안에 서비스 로직과 DAO 호출이 들어갔고, 뷰 쪽은 JSP에 AJAX 폴링 로직과 스타일을 함께 넣었다.
// KPI 폴링 루프 - 응답 실패 시 조용히 넘기되 콘솔엔 남긴다
function startKpiPolling(intervalMs) {
function poll() {
$.ajax({
url: '/partner/api/kpi/summary',
type: 'GET',
success: function(data) {
if (data && data.result === 'OK') {
updateKpiCards(data.kpi);
}
},
error: function(xhr, status, err) {
console.warn('[KPI] 폴링 실패:', status, err);
},
complete: function() {
setTimeout(poll, intervalMs);
}
});
}
poll();
}
complete 콜백 안에서 다음 타이머를 거는 방식을 택했다. setInterval로 고정 주기를 걸면 응답이 늦어질 때 요청이 겹칠 수 있어서다. 실패해도 폴링이 멈추지 않고 계속 시도하는 게 맞다고 봤다. 다만 에러는 콘솔에 남겨서 개발 중에 놓치지 않게 했다.
예외 케이스는 세 가지를 방어했다.
- 집계 결과가 없을 때(빈 데이터) - NULL 대신 0으로 내려서 화면이 깨지지 않게
- 권한 없는 접근 - 인터셉터에서 걸러지지만, API 레벨에서도 이중 확인
- 숫자 포맷 불일치 - 내려오는 값을 프론트에서 다시 포맷팅하지 않고 서버에서 맞춰서 내림
사이드바 메뉴 활성화는 기존 패턴 그대로 따랐다. 신규 메뉴가 생길 때마다 활성화 처리를 빠뜨리면 UX가 이상해지는데, 이게 은근히 놓치기 쉬운 부분이다.
검증과 배운 것
구현 끝나고 바로 배포하지 않고, 먼저 기존 화면에서 같은 기간 같은 조건으로 조회한 숫자를 메모해 뒀다. 그걸 새 KPI 카드 수치와 cross-check했다. 숫자가 다르면 쿼리 조건이나 집계 기준이 다른 거니까 그 차이를 먼저 이해하고 넘어가야 한다.
금융/결제 도메인에서 "대충 비슷해 보인다"는 기준으로 넘어가면 반드시 돌아온다. 숫자 하나가 틀렸을 때 신뢰가 깎이는 속도가 빠르고, 다시 쌓는 데는 시간이 오래 걸린다. 그래서 꼼꼼함을 기본값으로 두는 게 팀 전체 비용을 낮추는 길이라고 생각한다.
작업 단위는 논리적으로 독립된 커밋 여러 개로 쪼갰다. 폴링 로직 추가, 뷰 변경, 권한 확인, 메뉴 활성화가 각각 다른 커밋이다. 나중에 어느 변경에서 뭔가 깨졌을 때 범위를 좁히기 훨씬 쉽고, 리뷰할 때도 의도를 읽기 편하다. 커밋 메시지는 "무엇을 바꿨다"보다 "왜 바꿨는지"를 담으려고 계속 의식하는 중이다.
사내 서비스라고 해서 덜 신경 써도 되는 게 아니다. SQL 집계, 상태 표현, 예외 처리, 권한 체크, 화면 렌더링이 전부 연결돼 있고, 하나라도 빠지면 특정 조건에서 이상한 결과가 나온다. 기능 하나를 제대로 추가한다는 건 버튼 하나 올리는 일이 아니라는 걸, 이 작업을 하면서 다시 확인했다.
댓글 0
첫 댓글 달아줘.