Post

자바 백엔드 실무

자바 백엔드 실무

들어가며

자바 백엔드 개발자 사이에는 오랜 논쟁이 있습니다. “비즈니스 로직은 전부 애플리케이션(Java) 코드에 있어야 한다” vs “데이터 중심의 대용량 처리는 DB(Procedure)가 훨씬 빠르다”

특히 제가 지원하는 자산관리 시스템이나 ERP 같은 기업형 솔루션에서는 매달 수행하는 ‘감가상각비 계산’이나 ‘월 마감(Closing)’ 작업이 존재합니다. 수십만 건의 자산 데이터를 읽어서, 복잡한 수식을 적용해 계산하고, 다시 결과를 업데이트해야 합니다.

이런 상황에서 무조건적인 정답은 없습니다. 오늘은 5년 차 이상의 개발자로서, 시스템 운영 환경과 데이터 성격에 따라 로직의 위치를 결정하는 저만의 기준을 공유합니다.

Case 1. PL/SQL(프로시저)을 선택해야 할 때: 대용량 배치 처리

데이터가 있는 곳에서 연산하는 것이 가장 빠릅니다. 이것은 불변의 진리입니다.

예를 들어, 10만 건의 자산에 대해 감가상각비를 계산해서 T_DEPRECIATION 테이블에 INSERT 해야 한다고 가정해 봅시다.

Java로 처리할 경우:

  1. DB에서 10만 건을 SELECT 하여 JVM 메모리로 로드 (Network I/O 발생)
  2. Java Loop를 돌며 계산
  3. 다시 DB로 10만 번 INSERT (혹은 Batch Update)

이 과정에서 발생하는 네트워크 비용과 객체 생성 비용(Overhead)은 시스템 전체 성능을 저하시킬 수 있습니다.

PL/SQL로 처리할 경우:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE OR REPLACE PROCEDURE SP_CALC_DEPRECIATION (
    P_STD_DATE IN VARCHAR2,
    P_RESULT   OUT VARCHAR2
) IS
BEGIN
    -- 커서나 루프 없이 집합적 사고로 한방에 처리
    INSERT INTO T_DEPRECIATION (ASSET_ID, AMT, DEP_DATE)
    SELECT ASSET_ID, 
           (ACQ_AMT * 0.9 / LIFE_YEAR) AS AMT, -- 계산 로직
           P_STD_DATE
      FROM T_ASSET
     WHERE USE_YN = 'Y';
     
    COMMIT;
    P_RESULT := 'SUCCESS';
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        P_RESULT := SQLERRM;
END;

이처럼 데이터의 이동 없이 DB 엔진 내부에서 처리가 끝나는 경우, 혹은 복잡한 조인(Join) 연산이 비즈니스 로직의 핵심인 경우에는 과감하게 프로시저를 사용하는 것이 운영 측면에서 이득입니다.

Case 2. Java(Spring)를 선택해야 할 때: 변화가 잦은 비즈니스 로직

반면, 화면(UI)에서 사용자의 입력을 받아 처리하는 OLTP(On-Line Transaction Processing) 성격의 업무는 Java 레이어에서 처리하는 것이 유리합니다.

  1. 디버깅과 테스트: Java 코드는 로컬에서 중단점(Breakpoint)을 찍거나 JUnit 테스트를 작성하기 쉽습니다. 반면 프로시저는 디버깅이 까다롭고 형상 관리가 어렵습니다.
  2. 확장성(Scale-out): WAS(Web Application Server)는 서버를 늘려 부하를 분산하기 쉽지만, DB 서버는 스케일 아웃 비용이 매우 비쌉니다. CPU 연산이 많은 로직은 WAS로 가져와야 DB 부하를 줄일 수 있습니다.
  3. 복잡한 조건 분기: if-else 로직이 거미줄처럼 얽혀있거나, 외부 API를 호출해야 한다면 당연히 Java가 정답입니다.

절충안: MyBatis Dynamic Query의 활용

운영 업무를 하다 보면, 순수 쿼리만으로는 부족하고 프로시저를 짜기엔 부담스러운 애매한 상황이 옵니다. 이때 제가 즐겨 사용하는 것이 MyBatis의 동적 쿼리(Dynamic SQL) 기능입니다.

Java에서 로직을 제어하되, DB의 집합 처리 능력을 활용하는 방식입니다.

1
2
3
4
5
6
7
8
9
<!-- 다중 업데이트 처리 예시 -->
<update id="updateAssetStatusBatch" parameterType="java.util.List">
    <foreach collection="list" item="item" separator=";" open="DECLARE BEGIN" close="; END;">
        UPDATE T_ASSET
           SET STATUS = #{item.status}
             , UPD_DATE = SYSDATE
         WHERE ASSET_ID = #{item.assetId}
    </foreach>
</update>

Oracle의 경우 위와 같이 DECLARE BEGIN ... END 블록으로 감싸서 한 번의 트랜잭션으로 여러 건의 업데이트를 효율적으로 처리할 수 있습니다. (단, JDBC Batch 설정도 고려해야 합니다.)

정리: 나의 선택 기준

시스템을 재구축하거나 신규 기능을 개발할 때, 저는 다음과 같은 기준으로 설계합니다.

  1. 트랜잭션의 범위가 단일 테이블이거나 단순한가? -> Java
  2. 외부 시스템(ERP 등) 연계가 필요한가? -> Java
  3. 수만 건 이상의 데이터를 가공/집계해야 하는가? (Batch) -> PL/SQL
  4. 로직이 자주 변경되는가? -> Java (재배포가 프로시저 수정보다 안전한 프로세스를 갖춘 경우)

마무리하며

자바 개발자라고 해서 SQL을 등한시해서는 안 되며, 반대로 DB 성능만 믿고 비즈니스 로직을 모두 프로시저에 숨겨서도 안 됩니다.

중요한 것은 “어디서 처리하는 것이 전체 시스템의 자원을 가장 효율적으로 쓰는가?”를 판단하는 엔지니어의 시각입니다. 특히 제가 지원하는 분야처럼 데이터 무결성이 중요한 시스템에서는 DB의 기능을 십분 활용하는 능력이 필수적입니다.

자, 이제 우리 시스템 내부의 데이터 처리는 어느 정도 정리가 되었습니다. 하지만 기업 시스템은 섬처럼 홀로 존재하지 않습니다. 인사 시스템에서 사원 정보를 가져와야 하고, 재무 시스템으로 전표를 날려야 합니다.

다음 글에서는 [이기종 시스템 통합의 핵심, EAI 인터페이스 구축 전략]을 주제로, 외부 시스템과의 험난한 소통 과정을 다뤄보겠습니다.

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