개발 slecs

파트너 대시보드에 잔액 실시간 폴링 기능 추가

목차

partner-portal에 잔액 실시간 폴링 기능을 붙였다. 변경 파일 수만 보면 별거 아닌 것 같지만-내부 클래스 하나, SQL 매퍼 하나, 뷰/스타일 하나-실제로는 쿼리 설계부터 권한 체크까지 수직으로 다 손댄 작업이었다. 기존 화면/API에서 제공하지 않던 동작이 필요해졌고, 단순히 UI 하나 얹는 게 아니라 쿼리 레벨부터 설계해서 정합성을 맞췄다.

왜 폴링이었나

처음엔 이벤트 방식도 고민했다. WebSocket이나 SSE(Server-Sent Events)를 쓰면 서버 푸시로 변경 즉시 반영할 수 있으니까. 근데 파트너 대시보드 특성상 동시 접속자가 많지 않고, 잔액 변동 주기도 초 단위 이하로 빠를 필요가 없었다. 인프라 복잡도를 높여서 얻을 이득이 없다고 판단해서 AJAX 폴링으로 정리했다.

방식 장점 단점 적합한 상황
AJAX 폴링 구현 단순, 서버 상태 없음 갱신 간격만큼 지연, 트래픽 일정 발생 변동 주기 느림, 동시 접속 적음
WebSocket 즉각 반영, 실시간 양방향 연결 유지 비용, 인프라 설정 복잡 채팅, 트레이딩 등 ms 단위 반응 필요
SSE 서버 푸시, 단방향 단순 브라우저 연결 수 제한, 재연결 처리 필요 알림, 피드 등 단방향 스트림

"충분히 실시간처럼 보이면 된다"는 기준이라면 폴링이 제일 관리하기 편하다. 30초 간격 정도면 파트너 입장에서 충분히 신선한 데이터고, 나중에 인터벌만 조정하면 되니 유연성도 나쁘지 않다.

SQL부터 화면까지 수직으로 내려간 이유

기존 API가 있으면 거기 올라타고 싶은 게 당연한데, 이번엔 그러지 않았다. 기존 잔액 조회 로직은 화면 전체 렌더링 목적으로 짜여 있어서, 주기적으로 폴링해서 숫자 하나만 뽑아오기엔 불필요하게 무거웠다. 그래서 잔액만 반환하는 가벼운 엔드포인트를 따로 만들었다.

SQL 매퍼도 신규 추가했다. 기존 쿼리를 재사용하면 조인이 많아서 실행 계획이 나빴고, 폴링용 쿼리는 인덱스를 타는 단순 집계로 짧게 끊었다. 배포 전에 실행 계획 확인해서 풀스캔 없는지 체크하는 건 기본이다.

프론트는 JSP에 AJAX 호출을 붙이는 구조. 대략 이런 패턴으로 짰다:

function startBalancePolling(intervalMs) {
  function fetchBalance() {
    $.ajax({
      url: '/partner/api/balance',
      method: 'GET',
      success: function(res) {
        if (res.success) {
          $('#balance-display').text(formatCurrency(res.balance));
        }
      },
      error: function(xhr) {
        // 에러 시 폴링 중단하지 않고 다음 인터벌에 재시도
        console.warn('balance fetch failed', xhr.status);
      }
    });
  }

  fetchBalance(); // 최초 즉시 실행
  return setInterval(fetchBalance, intervalMs);
}

에러 핸들링에서 포인트는 네트워크 오류나 서버 에러가 났을 때 폴링 자체를 죽이지 않는 것이다. 한 번 실패했다고 clearInterval해버리면 이후로 영영 갱신이 안 된다. 에러 로그만 남기고 다음 인터벌에 재시도하는 구조가 맞다.

권한 인터셉터는 기존 파트너 포탈 접근 제어 로직을 그대로 통과시켰다. 신규 엔드포인트를 인터셉터 설정에서 빼먹으면 로그인 없이 접근 가능해지는 구멍이 생기니까, 이 부분은 따로 확인했다. 사이드바 메뉴 활성화도 기존 패턴을 따라서 처리했다.

금융 도메인에서 숫자 정합성이 왜 유독 까다로운가

일반 콘텐츠 화면이면 데이터가 1초 늦게 보여도 크게 문제없다. 근데 잔액이나 결제 수치는 다른 화면에서 보여주는 값과 단 1원이라도 다르면 신뢰 문제가 된다. 파트너 입장에서 대시보드 잔액이 정산 내역 합계랑 안 맞으면 "시스템이 잘못됐다"는 의심을 바로 한다. 기술적인 문제를 넘어서 운영 신뢰 문제가 되는 거다.

이번 구현에서 가장 신경 쓴 부분:

  • 동일 파트너 계정으로 기존 정산 화면 vs 신규 대시보드 폴링 값 교차 확인
  • NULL 잔액 케이스-신규 파트너, 거래 없는 계정-방어 처리
  • 집계 기준 타임존이 기존 로직과 동일한지 명시적으로 확인
  • 빈 데이터, 권한 없는 접근, 음수 잔액 엣지 케이스 표시 처리

타임존 건은 의외로 놓치기 쉬운 함정이다. 기존 쿼리가 서버 로컬 타임 기준으로 집계하고 있는데 신규 쿼리에서 UTC 기준으로 짜버리면, 자정 근처에 숫자가 달라지는 현상이 생긴다. 신규 로직이 기존 로직의 집계 기준을 그대로 따르는지 확인하는 게 중요하다.

검증은 직접 화면에서 했다. 자동화 테스트가 없는 레거시 파트에선 결국 수작업 cross-check이 마지막 방어선이다. 귀찮아도 기존 화면 수치를 메모해두고 신규 화면과 비교하는 루틴을 지켰다. 구현 후 "되는 것 같다"로 넘기는 게 제일 위험한 패턴이고, 나중에 반드시 다시 돌아온다.


커밋도 "SQL 매퍼 추가", "API 엔드포인트 구현", "프론트 폴링 연동", "권한 설정 추가"를 별도로 쪼갰다. 나중에 폴링 인터벌 조정이나 쿼리 최적화 작업이 생겼을 때 어디서 찾아야 할지 명확해지는 효과가 있다. 커밋 메시지도 무엇을 했는지보다 왜 이렇게 했는지가 담기게 썼다. "잔액 폴링 API 추가"보다 "파트너 대시보드 실시간 갱신 - 기존 조회 API가 화면 전체 렌더링 목적으로 무거워 폴링 전용 경량 엔드포인트 분리"처럼.

기능 하나가 화면에 버튼 하나 추가하는 것으로 끝나지 않는다는 걸 이 도메인에서 일할수록 더 실감한다. SQL 집계, 권한 체크, 예외 처리, 화면 렌더링이 모두 엮여 있어서 어느 하나만 빠뜨려도 숫자가 안 맞거나 특정 사용자에게 이상한 화면이 뜬다. 꼼꼼함이 선택이 아니라 기본값이어야 하는 이유다.

댓글 0

첫 댓글 달아줘.