Post

자바 백엔드 실무

자바 백엔드 실무

들어가며

“개발 서버에서는 잘 떴는데, 운영 서버에서 조회 버튼만 누르면 화면이 멈춥니다.”

SI/SM 업무를 수행하다 보면 지겹도록 듣는 이야기입니다. 특히 제가 지원하는 자산관리 시스템이나 ERP 분야에서는 감가상각 현황, 전사 보유 자산 목록 등 조회 한 번에 수만, 수십만 건의 데이터를 처리해야 하는 경우가 비일비재합니다.

이때 단순히 SELECT *를 날리고 Java List에 담아 리턴한다면, OOM(Out of Memory)은 물론이고 사용자 경험(UX)을 심각하게 해칩니다.

오늘은 대용량 데이터를 효율적으로 처리하기 위한 두 가지 핵심 전략, 통신 프로토콜 최적화(SSV)DB 레벨의 페이징 처리 기법을 정리해 보겠습니다.

전략 1. 무거운 XML을 버리고 SSV(Stream Service Value)를 입다

넥사크로(혹은 마이플랫폼)는 기본적으로 XML 기반의 통신을 지원합니다. XML은 가독성은 좋지만, 태그(Tag)가 데이터를 감싸고 있어 데이터 양이 커질수록 오버헤드가 심각해집니다.

데이터가 10,000건만 넘어가도 파싱 속도가 눈에 띄게 느려집니다. 이때 PL(Project Leader)로서 제시해야 할 해결책은 SSV(Stream Service Value) 프로토콜입니다.

SSV란?

SSV는 데이터를 구분자(Separator)로 나누어 전송하는 넥사크로 전용 포맷입니다. XML 대비 패킷 사이즈를 획기적으로 줄일 수 있어 대용량 조회 시 필수적입니다.

Java 백엔드 적용 (X-API)

어제 구현한 UiAdapterController 레벨에서 응답 형식을 지정할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping(value = "/asset/huge-list.do")
public NexacroResult searchHugeList() {
    NexacroResult result = new NexacroResult();
    
    // ... 데이터 조회 로직 ...

    // PlatformType을 SSV로 명시적 설정
    // XML 대비 패킷 사이즈 약 50% 이상 감소 효과
    PlatformData platformData = new PlatformData();
    platformData.setContentType(ContentType.SSV); 
    
    // X-API 버전에 따라 HttpServletResponse에서 contentType을 설정하기도 함
    // response.setContentType("text/html; charset=euc-kr"); // SSV 전송 시 MIME 타입 주의
    
    return result;
}

실제로 과거 프로젝트에서 5만 건의 자산 실사 데이터를 조회할 때, XML에서 SSV로 전환하는 것만으로 응답 시간을 3초에서 0.8초로 단축시킨 경험이 있습니다.

전략 2. Oracle DB 페이징과 부분 범위 처리

아무리 통신이 빨라도 DB에서 10만 건을 다 읽어오면 DB 부하가 걸립니다. 게시판 형태라면 OFFSET-LIMIT을 쓰겠지만, 엑셀처럼 스크롤로 내리는 그리드(Grid) 환경에서는 어떻게 해야 할까요?

1. Count 쿼리와 데이터 쿼리의 분리

전체 데이터가 몇 건인지 알아야 스크롤바의 크기를 계산할 수 있습니다. 하지만 매번 COUNT(*)를 하는 것은 비효율적입니다. 검색 버튼을 눌렀을 때 최초 1회만 전체 건수를 가져오고, 이후 스크롤 시에는 데이터만 가져오는 방식이 유리합니다.

2. Oracle 페이징 쿼리 (MyBatis)

Oracle 12c 이상이라면 OFFSET ... FETCH 구문을 쓰면 되지만, 많은 레거시/국방/금융 시스템은 아직 11g 이하를 사용하는 경우가 많습니다. 따라서 전통적인 ROWNUM 방식을 능숙하게 다룰 줄 알아야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- MyBatis Mapper XML -->
<select id="selectAssetList" parameterType="map" resultType="map">
    SELECT * 
      FROM (
        SELECT A.*, ROWNUM AS RNUM
          FROM (
            SELECT /*+ INDEX(T_ASSET IDX_ASSET_01) */ -- 힌트로 실행계획 유도
                   ASSET_ID, ASSET_NAME, DEPT_CODE, ACQ_DATE, AMOUNT
              FROM T_ASSET
             WHERE USE_YN = 'Y'
               AND DEPT_CODE = #{deptCode}
             ORDER BY ASSET_ID DESC
          ) A
         WHERE ROWNUM &lt;= #{endRow}
      )
     WHERE RNUM &gt;= #{startRow}
</select>

여기서 중요한 점은 인덱스(Index)입니다. 정렬(ORDER BY) 작업이 DB 성능을 가장 많이 갉아먹습니다. 적절한 인덱스를 태워 별도의 정렬 부하 없이 데이터를 가져오도록(Index Range Scan) 설계하는 것이 백엔드 개발자의 역량입니다.

전략 3. 프론트엔드와 백엔드의 협업 (Lazy Loading)

백엔드에서 준비가 끝났다면, 넥사크로 화면(Form)에서는 onload 시점에 모든 데이터를 부르는 것이 아니라, 초기 50건만 먼저 조회합니다.

이후 사용자가 스크롤을 바닥까지 내리면(OnVScroll 이벤트), 백엔드에 startRow=51, endRow=100 파라미터를 보내 다음 데이터를 요청합니다. 이를 Append 방식으로 데이터셋에 붙여주면 사용자는 끊김 없이 수만 건의 데이터를 탐색하는 것처럼 느낍니다.

마무리하며

오늘은 시스템 운영의 가장 큰 적인 ‘성능 이슈’를 해결하기 위해 SSV 프로토콜Oracle 페이징 기술을 다뤘습니다.

  • 통신: XML 대신 SSV를 사용하여 네트워크 비용 절감
  • DB: 인덱스를 활용한 부분 범위 처리(Partial Range Scan)로 I/O 비용 절감
  • UI: 스크롤 기반의 Lazy Loading으로 렌더링 부하 분산

이 세 박자가 맞아야 비로소 “쓸 만한 시스템”이 됩니다.

하지만 성능만큼 중요한 것이 바로 복잡한 비즈니스 로직의 처리 위치입니다. 감가상각비 계산이나 결산 처리 같은 무거운 로직은 자바에서 루프를 돌리는 게 빠를까요, 아니면 DB 프로시저 한 방이 빠를까요?

다음 글에서는 [Oracle DB: 복잡한 비즈니스 로직을 위한 PL/SQL vs Java 선택 기준]을 주제로, 유지보수성과 성능 사이의 영원한 난제에 대해 이야기해 보겠습니다.

This post is licensed under CC BY 4.0 by the author.