§01Contributions
MyBatis → JPA 마이그레이션
  • MyBatis -> JPA로 마이그레이션
  • DB의 테이블·컬럼·연관관계 정보를 분석하여 JPA Entity 파일을 자동 생성하여 마이그래이션 생산성 향상
쿼리 튜닝을 통한 조회 성능 향상
  • 다중 조건 검색 쿼리에서 조회 성능 저하 원인 파악 및 개선
  • 인덱스를 타지 못하는 쿼리를 인덱스를 탈 수 있게 개선해 조회 속도 개선
MongoDB 인덱스 최적화
  • 조회 패턴을 고려한 인덱스를 설계하여 검색 성능 개선
에러 코드 표준화 및 다국어(i18n) 지원
  • 오류 메시지를 오류 코드 체계로 표준화하고, i18n을 적용해 다국어 메시지를 지원
JNI 기반 네이티브 라이브러리 성능 분석 및 데드락 모니터링
  • Java에서 호출하는 JNI 기반 C 라이브러리의 성능을 VisualVM으로 모니터링
  • 데드락 발생 시 스레드 상태를 분석하여 원인 추적 및 문제 해결 지원
통합 테스트 기반 품질 검증
  • 운영 환경 배포 전 테스트 절차서를 기반으로 기능별 통합 테스트 수행 및 문서 작성

대규모 코드 리팩토링

§02Problem Solving
PS-01쿼리 튜닝을 통한 다중 조건 검색 속도 향상
문제 상황검색 조건과 대상 데이터가 늘어날수록 다중 조건 검색의 응답 속도가 급격히 저하.
원인
  • 인덱스 미적용: 서브쿼리로 컬럼을 가공해 조회하면서, 인덱스가 걸린 원본 컬럼 대신 가공된 결과로 비교가 일어나 인덱스를 타지 못해 풀스캔 진행.

  • LIKE의 확장성 한계: 문자열 부분 매칭(LIKE '%...%')은 선행 와일드카드라 인덱스를 못 쓰고, 데이터 건수에 비례해 비용이 선형 이상으로 증가.

  • 매 조회 시 반복 계산: 검색에 필요한 값이 별도 컬럼으로 존재하지 않고 원본 문자열 안에 포함돼 있어, 조회할 때마다 문자열을 가공해 그 값을 추출·비교.
  • 해결 방법

    1. 서브쿼리 가공 → 애플리케이션 레벨 처리로 이관

    2. LIKE절을 equality 비교로 전환: 매 조회마다 원본 문자열에서 추출하던 파생 값을 별도 컬럼으로 미리 저장. 조회 시점의 반복 파싱을 없애고, 이 컬럼에 인덱스를 걸어 equality 기반 인덱스 탐색으로 전환. 저장 공간과 정합성 관리 부담을 지는 대신, 조회를 단순화하고 고속화.

    3. 정합성 확보 조치: 추가한 컬럼에 기존 데이터를 채우는 일괄 update 쿼리를 작성해 초기 데이터를 정합화. 지속적 정합성은 원본 변경 시점에 동기화되도록 관리.

    해결 결과2만건 이상의 데이터가 있는 상태에서 3~4개의 검색 조건 조회시 기존에는 8초 이상 걸렸던 로직이 2~3초 이내로 단축 됨.
    핵심 교훈
  • 컬럼을 함수·서브쿼리로 가공하면 인덱스를 활용할 수 없어 성능이 중요하면 지양해야 함.
  • 조회 시마다 반복되는 계산은 파생 컬럼으로 사전 저장해 조회 비용으로 전가하지 않을 수 있음. 단, 원본과의 정합성과 유지 비용을 함께 고려해야 함.
  • PS-02 MongoDB 인덱스 최적화
    문제 상황로그성 컬렉션인 Report는 실시간으로 끊임없이 적재되는 insert가 극도록 잦은 컬렉션인데 운영 중 이 컬렉션을 조회하는 API의 응답이 점차 느려짐.
    분석 조회 쿼리의 검색 조건은 iedId 리스트, reason, 생성 날짜 범위 인데, 생성 날짜 범위를 제외하고는 모두 optional 조건이라 인덱스 하나로 모든 케이스를 최적화 할 수 없었음. 또한 iedId는 매번 다른 값 조합의 리스트($in)로 검색 조건(ex : [IED001, IED002, ….])이 들어와서 이 필드를 기준으로 인덱스를 설정해도 재사용성이 낮아 보였음. 따라서 값의 종류가 적은 enum성 필드인 reason를 기준으로 인덱스 생성을 결정.
    해결 과정

    1. {insertDt: -1, _id: -1}  복합 인덱스 생성 — reason 필터가 없는 쿼리 담당. insertDt 범위 조건과 insertDt / _id 정렬을 인덱스 순서 그대로 스캔해, 정렬을 위한 in-memory sort를 제거.

    2. {reason: 1, insertDt: -1, _id: -1} 복합 인덱스 생성 — reason 필터가 있는 쿼리 담당. 등치 조건인 reason을 선두에 두고 정렬 필드를 이어 붙여, 필터 + 정렬을 하나의 인덱스로 처리.

    3. _id는 insertDt가 같을 때 커서를 안정적으로 확정하기 위한 tie-breaker.

    4. iedId는 의도적으로 인덱스에서 제외 : 이 컬렉션은 insert가 매우 잦아 인덱스가 늘 수록 쓰기 비용이 커짐. 따라서 재사용성이 높은 reason필드 기준으로 인덱스를 생성하고, iedId는 첫번째 복합 인덱스(insertDt + _id )로 좁혀진 후보군에서 필터링하는 것으로 충분하다고 판단.

    결과
  • 복합 인덱스 설정으로 COLLSCAN + blocking sort → IXSCAN으로 전환, 검사 문서 수(examined docs)를 200,000 → 709로 약 99.6% 절감하고 in-memory sort를 제거해 응답 시간을 46ms → 1ms로 단축.
  • 무분별하게 인덱스를 추가하는 대신 필드 특성을 고려해 최소 인덱스만 설정하여 insert 쓰기 오버헤드를 최소화함.
  • PS-03대규모 코드 리팩토링
    문제 상황새로운 기능을 추가해야 하는데, 관련 코드가 하나의 클래스 파일에 약 3,000줄 이상 분량으로 구현되어 있어 유지보수에 어려움이 생김.
    해결 방법
  • 여러 곳에 흩어진 중복 코드는 공통 메서드로 분리해 재사용하도록 정리.
  • 각 기능마다 유틸리티 클래스를 따로 만들어 책임을 분산시킴 (예: 데이터 타입 관련 로직을 DataTypeUtils로 추출).
  • 거대한 단일 클래스를 기능·책임 단위로 쪼개, 새 기능은 해당 책임을 가진 클래스에만 손대면 되도록 구조를 개선.
  • 핵심 교훈
  • 하나의 클래스가 너무 많은 책임을 가지면 유지보수와 확장이 급격히 어려워진다. 단일 책임 원칙(SRP)에 따라 기능·책임 단위로 나누는 것이 정말 중요하다고 느꼈다.
  • 중복 제거와 유틸 추출은 단순히 코드량을 줄이는 것을 넘어, 변경 지점을 한곳으로 모아 수정 시 실수와 버그 가능성을 낮춰준다.
  • PS-04 스레드 무한 대기 문제 예방
    문제 상황VisualVM으로 모니터링 중 여러 요청을 동시에 처리하는 서버에서 워커 스레드 두 개가 무한대기하여 CPU를 100%를 점유하며 멈춰 있는 상황 발생
    원인 정확한 원인은 밝히지 못했고, 추측상 아래 두 가지가 원인으로 추정

    1. 비동기 read를 동기처럼 감싼 래퍼가 외부 전력 감시 장비의 응답을 폴링 방식으로 기다리는데, 통신이 직렬 연결 기반인 버퍼에서 동시에 두 스레드가 통신 요청을 보냈으므로 오류 발생

    2. 외부 장비의 문제가 생겨 외부 응답을 받지 못해 스레드가 무한 대기

    해결 방법 연결에 대한 응답이 오지 않을 때 타임아웃을 둬서 스레드가 무한 대기 하는 문제를 방지했다.
    핵심 교훈외부 응답에 의존하는 호출은 반드시 타임아웃을 둬서 스레드가 묶이는 것을 방지 해야함.
    PS-05 Mybatis -> JPA 마이그레이션 작업 효율 향상
    상황MyBatis 사용 시 코드 가독성이 떨어지고 사소한 스키마 변경에도 관련 XML Mapper 파일을 전부 찾아서 수정해야 하는 등 유지 보수에 어려움이 있었음. 또한 현재 사용하고 있는 DB를 추후 다른 DB로 변경할 가능성이 있어 DB 의존성을 낮추기 위해 JPA로의 마이그레이션을 결정함.
    문제 약 60개가 넘는 테이블들을 엔티티 파일로 변경해야 하고 이를 수작업으로 진행 시 오타등 휴먼 에러가 발생할 확률이 높음. 따라서 테이블의 메타데이터를 조회해 엔티티 및 repository, mapstruct 파일 생성을 자동화 하여야겠다고 판단.
    해결 방법 마이그레이션 과정

    1. DB 스키마 메타데이터를 분석해 Entity 파일 자동 생성 — 컬럼 타입·nullable·unique·PK·기본값 등을 규칙 기반으로 매핑하여 반복적인 엔티티 작성을 자동화

    2. 엔티티별 Repository 및 MapStruct 기반 DTO ↔ Entity 매핑 파일 자동 생성

    3. 연관관계는 선별적으로 적용 — JPA를 처음 도입하는 환경임을 고려해, 생명주기를 공유하고 있는 엔티티간에만 선별적으로 연관관계를 매핑하고 나머지는 ID 참조로 유지. 자동 조인·캐스케이드의 편의를 포기하는 대신, N+1 등 예측하기 어려운 문제를 예방하고 조회 성능을 확보

    4. 서비스 계층의 MyBatis 의존을 JPA 의존으로 전환 — 기존 매퍼 호출을 Repository 기반으로 이관

    5. 복잡한 동적 쿼리는 QueryDSL로 구현 — 문자열 기반 쿼리 대비 타입 안전성과 가독성 확보

    6. 쿼리 성격에 따라 JPQL과 Native Query로 구현 범위를 분리 — 간단한 쿼리는 JPQL로, Bulk 연산은 Native Query로 구현하여 DB 독립성을 포기하는 대신 성능을 확보

    결과약 180개 이상의 파일을 일일이 수작업으로 생성하는 대신 코드를 통해 자동화 하여 작업 효율을 크게 향상 시켰고, 연관관계 선별 적용으로 JPA로 전환시 생길 수 있는 성능 저하와, N+1 문제를 예방함.