~/tech-blog/posts/retrospective-2024 — zsh

2024년 회고

24년도는 신규 프로젝트 및 다양한 경험을 하였던 한 해였습니다. 운이 좋게 신규 프로젝트를 처음부터 끝까지 기일에 맞춰 오픈하였고, 중간중간의 문제를 해결하기 위해 공부했던 내용들도 많았습니다. 각각의 내용에 대해 간단하게 작성하고 24년도에 정리한 내용을 링크로 남겨 기록하도록 하겠습니다.

1.1 신규 구축한 어드민 웹 서비스

처음으로 프로젝트를 맡아 신규로 오픈한 웹 사이트입니다.


1.1.0 패키지 분리

Java
├── cache
└── core
│		└── domain
│				└── domainA
│				│		└──repository
│				│		│		└──domainRepository
│				│		│		└──domainRepository
│				│		└──service
│				│				└──domainService
│				└── domainB
│				└── common
├── storeage
└── web
  1. 패키지 분리 기준

    • cache, storage 등 기술/기능 중심 패키지를 최상단에 배치합니다.
    • core는 도메인과 공통 비즈니스 로직을 포함하도록 설계합니다.
  2. 도메인 패키지 하위 분리

    도메인(domainA, domainB)을 개별적으로 구성하고, 각 도메인의 엔티티, 리포지토리, 서비스, DTO 등을 세분화했습니다.

    예를 들어, domainA 안에서 엔티티와 관련된 모든 파일을 쉽게 찾을 수 있습니다.

  3. 웹 계층 분리

    • web컨트롤러와 **웹 전용 로직(DTO, 예외 처리)**을 한곳에 모아두었습니다.
    • Interceptor, ArgumentResolver를 별도로 관리하여 구조를 명확히 했습니다.
  4. 공통 코드 위치

    • 모든 도메인에서 공통적으로 사용하는 코드는 core/common으로 분리했습니다.
    • 공통 유틸, 상수, 예외 등은 여기에 위치합니다.

1.1.1 종속성 문제 해결

Java
├── cache
└── core
│		└── domain
│				└── domainA
│				│		└──repository
│				│		│		└──domainRepository
│				│		│		└──domainRepository
│				│		└──service
│				│				└──domainService
│				└── domainB
│				└── common
├── storeage
│		└── coroe
│				└── jpa
│				│		└── domainA
│				│		│		└── domainJpaEntity
│				│		│		└── domainJpaEntity
│				│		└── domainB
│				│				└── domainJpaEntity
│				│				└── domainJpaEntity
│				└── mybatis
│						└── domainB
│								└── domainMybatisDTO
│								└── domainMybatisDTO
├── web                  
│   ├── controller       
│   ├── dto              
│   └── exception        
└── external        
  1. 도메인 중심의 계층화
  • core/domain 아래에 도메인별로 repositoryservice를 포함하여 도메인 중심 설계를 유지했습니다.
    • repository는 도메인 로직에서 직접 호출 가능하며, 저장소의 세부 사항은 storeage에 위임합니다.
    • service는 도메인별 비즈니스 로직의 집합체로, 핵심 기능을 캡슐화합니다.
  1. 저장소 세부 사항 분리
  • storeage/coroe 아래에 JPA 및 MyBatis를 명확히 분리하여 구현 기술별로 관리했습니다.
    • jpamybatis가 공존하더라도 서로 영향을 주지 않습니다.
    • 도메인 모델과 DB 엔티티, DTO 간의 변환은 각 계층에서 관리할 수 있습니다.
  1. 웹 계층 독립
  • web 패키지에서 요청/응답 DTO, 컨트롤러, 예외 처리를 관리합니다.
    • 도메인 계층과 별도로 분리하여, 확장성과 유지보수성을 높였습니다.
  1. 확장성 확보
  • external 패키지를 통해 외부 API 연동 코드를 분리하여, 다양한 외부 서비스와의 통합이 가능하도록 했습니다.
  • cache 패키지로 캐시 관련 로직을 통합 관리해 성능 향상을 도모했습니다.

1.1.2 낙관적 락, 비관적 락, 분산 락

1. 낙관적 락 (Optimistic Lock)

  • 개념

    데이터 충돌 가능성이 낮다고 가정하고, 트랜잭션이 완료될 때까지 잠금을 걸지 않으며 데이터를 처리하는 방법입니다.

    트랜잭션이 끝날 때 데이터 변경 여부를 검증하여 충돌이 없을 경우에만 데이터를 갱신합니다.

    일반적으로 **버전 관리(versioning)**를 통해 구현합니다.

  • 특징

    • 잠금을 걸지 않기 때문에 성능이 좋습니다.
    • 충돌 가능성이 높은 환경에서는 실패율이 증가할 수 있습니다.
  • 사용 사례

    • 데이터 갱신 빈도가 낮고 충돌 가능성이 적은 환경입니다.
    • 데이터 일관성을 트랜잭션 종료 시점에만 보장하면 되는 경우입니다.
  • 관련 정리 링크

DB 낙관적 락

2. 비관적 락 (Pesimistic Lock)

  • 개념

    데이터 충돌 가능성이 높다고 가정하고, 트랜잭션이 시작되자마자 잠금을 걸어 다른 트랜잭션이 접근하지 못하도록 하는 방법입니다.

    락을 통해 강제적으로 자원을 보호하며, **공유 락(Shared Lock)**과 **배타 락(Exclusive Lock)**으로 나뉩니다.

  • 특징

    • 충돌이 발생하지 않도록 보장하지만, 잠금으로 인해 성능 저하가 발생할 수 있습니다.
    • 데드락(Deadlock)이 발생할 가능성이 있습니다.
  • 사용 사례

    • 데이터 갱신 빈도가 높고 충돌 가능성이 큰 환경입니다.
    • 자원의 일관성을 트랜잭션 실행 중에도 보장해야 하는 경우입니다.
  • 관련 정리 링크

DB 비관적 락

3. 분산 락 (Distributed Lock)

  • 개념

    멀티스레드 또는 멀티 프로세스 환경에서 공유 자원에 대한 동시 접근을 제어하기 위해 사용되는 락입니다.

    주로 분산 시스템에서 데이터 정합성을 보장하기 위해 사용합니다.

  • 구현 방법

    • Redis: SETNX 명령어와 EXPIRE를 활용해 락을 설정합니다.
    • ZooKeeper: 분산 락 구현에 사용되는 대표적인 툴로, ephemeral znode를 활용합니다.
    • Database: 특정 테이블을 활용하여 락 정보를 관리합니다.
  • 특징

    • 분산 환경에서 데이터 일관성을 보장합니다.
    • 구현이 복잡하고, 네트워크 지연 및 장애 상황에 대비해야 합니다.
  • 사용 사례

    • 분산 시스템에서 자원 접근 동기화가 필요한 경우입니다.
    • 클러스터 환경에서 동일 데이터에 대한 경쟁 조건을 방지해야 하는 경우입니다.
  • 관련 정리 링크

DB 분산락


1.1.3 Test Code 도입

  • TestCode 설명

[인프런 워밍업 클럽 백엔드 스터디 2기] 4주차 발자국 - 박태영님의 블로그 - 인프런 | 커뮤니티


1.1.4 코드리뷰 도입

  • 신규 프로젝트를 구축하면서 기존 운영 시스템에서 발생했던 문제를 개선하고자 코드 리뷰를 진행했습니다.
  • 각자 맡고 있는 개발을 설명하고, 신규 프로젝트에 적용하고 있는 코드 규칙과 문제점은 없는지 확인했습니다.
  • 코드리뷰를 새로 도입하면서 제일 중요한 부분은 개발자들 간의 신뢰였습니다.
  • 신뢰 속에서 더 좋은 방법이라 생각될 때 편하게 말하는 분위기가 중요합니다.

1.1.5 DBCP

DBCP는 데이터베이스와의 연결을 미리 생성해 풀(Pool)에 보관함으로써, 클라이언트 요청 시 매번 TCP 연결을 생성/해제하는 과정을 생략해 성능을 개선하는 기술입니다.

DBCP가 없는 경우

  1. 클라이언트 요청 → 애플리케이션에서 TCP 연결 생성 (3-way Handshake)
  2. 데이터 조회 후 TCP 연결 해제 (4-way Handshake)
  3. 반복적으로 연결 생성/해제가 발생해 성능이 저하됩니다.

DBCP가 있는 경우

  • 미리 생성된 연결 풀에서 Connection을 가져와 재사용합니다.
  • 3-way 및 4-way Handshake 과정을 최소화해 빠른 응답을 제공합니다.

DB 설정에서 중요한 항목

  1. MaxConnections
    • DB가 처리할 수 있는 최대 연결 수를 적절히 설정합니다.
  2. wait_timeout
    • 유휴 상태 연결을 자동 종료하여 불필요한 연결 점유를 방지합니다.

DBCP 주요 설정

  1. minimumIdle
    • 유지할 유휴 연결의 최소 개수입니다.
  2. maximumPoolSize
    • 동시에 열 수 있는 최대 연결 수입니다.
    • 최소/최대 값을 동일하게 설정하는 것이 권장됩니다.
  3. maxLifetime
    • 연결 유효 시간입니다.
    • DB의 wait_timeout보다 2초 작게 설정할 필요가 있습니다.
  4. connectionTimeout
    • 풀에서 연결을 가져오기 위한 최대 대기 시간입니다.

결론

  • DB 설정(MaxConnections, wait_timeout)과 DBCP 설정(minimumIdle, maximumPoolSize, maxLifetime)을 조화롭게 관리해야 합니다.
  • 부하 발생 시 설정 최적화 또는 장비 증설로 대응합니다. 이를 통해 시스템 안정성과 성능을 유지할 수 있습니다.

관련 링크

[실무]DB 개념 정리


캐시 Stampede 현상과 해결 방안 요약

1. 캐시 Stampede란?

  • 정의: 캐시가 만료될 때 여러 요청이 한 번에 DB로 몰려가면서 DB 부하가 급증하고 성능 저하를 일으키는 현상입니다.

2. 기존 해결 방안과 한계

  • 분산락 방식
    • 장점: 캐시 만료 시점에 단일 요청만 DB를 접근하도록 하여 DB 부하를 감소시킵니다.
    • 단점: 락을 획득하지 못한 요청은 대기하게 되어 응답 지연이 발생합니다.
  • 캐시 웜업 (Cache Warm-up)
    • 의미: 애플리케이션 시작 시나 특정 시점에 캐시를 미리 채워 놓는 방식입니다.
    • 방법: 배치 작업을 통해 자주 사용되는 데이터를 미리 캐시에 적재합니다.
    • 문제점:
      • 리모트 캐시 서버 부하로 인한 응답 지연이 발생할 수 있습니다.
      • 캐시 웜업 대상 누락 시 기존 문제(Stampede)가 재발생할 수 있습니다.

3. 하이브리드 캐시 솔루션

  • 구조: 로컬 캐시와 리모트 캐시를 함께 사용합니다.
    • 데이터 조회 순서: 로컬 캐시 → 리모트 캐시 → DB
  • 데이터 특성별 캐시 활용:
    • 로컬 캐시: 개인화되지 않고 업데이트 빈도가 낮은 데이터입니다.
      • 예: 마케팅 카드, 내비게이터, 리뷰 등입니다.
    • 리모트 캐시: 최신 상태를 유지해야 하는 데이터로 모든 인스턴스에서 동일한 응답을 보장합니다.

핵심 포인트

  • 캐시 Stampede를 방지하기 위한 해결책으로 분산락캐시 웜업 방식이 있지만 한계가 존재합니다.
  • 하이브리드 캐시 솔루션을 도입하면 로컬 캐시와 리모트 캐시의 특성을 잘 활용하여 효율적인 데이터 관리를 할 수 있습니다.

기존에 운영하던 어드민 웹 서비스

기존에 유지보수하고 있는 프로젝트입니다.

  • 중복상품, 중복모니터링 문제의 원인과 해결 방법을 모색했습니다.

문제 분석

  • 기존 상태: 단일 ID(Long)를 사용하고, @GeneratedValue(strategy = GenerationType.IDENTITY)로 ID를 자동 생성했습니다.
  • 변경된 상태: 복합키(ID + createdAt)로 변경되었으나, JPA에서 @GeneratedValue를 제거하고 @Id만 사용하도록 수정했으며, 기존의 조회 메서드들이 이 변경을 반영하지 않아서 중복 데이터가 반환되었습니다.

문제 해결 방법

  • 엔티티로 변환하지 않고 Object로 변환해서 일단 개발을 완료했습니다.
  • 파티셔닝을 변경하여 다시 적용했습니다.

인프런 워밍업 클럽 백엔드 스터디 2기

  • 1주차 발자국

[인프런 워밍업 클럽 백엔드 스터디 2기] 1주차 발자국 - 박태영님의 블로그 - 인프런 | 커뮤니티

  • 2주차 발자국

[인프런 워밍업 클럽 백엔드 스터디 2기] 2주차 발자국 - 박태영님의 블로그 - 인프런 | 커뮤니티

  • 3주차 발자국

[인프런 워밍업 클럽 백엔드 스터디 2기] 3주차 발자국 - 박태영님의 블로그 - 인프런 | 커뮤니티

  • 4주차 발자국

[인프런 워밍업 클럽 백엔드 스터디 2기] 4주차 발자국 - 박태영님의 블로그 - 인프런 | 커뮤니티

2024년 수료 인프런 인터넷 강의

  • 오브젝트 - 기초편

오브젝트 - 기초편 강의 | 조영호 - 인프런

  • [아파치 카프카 애플리케이션 프로그래밍] 개념부터 컨슈머, 프로듀서, 커넥트, 스트림즈까지!

[아파치 카프카 애플리케이션 프로그래밍] 개념부터 컨슈머, 프로듀서, 커넥트, 스트림즈까지! 강의 | 데브원영 DVWY - 인프런

  • Practical Testing: 실용적인 테스트 가이드

Practical Testing: 실용적인 테스트 가이드 강의 | 박우빈 - 인프런

  • Readable Code: 읽기 좋은 코드를 작성하는 사고법

Readable Code: 읽기 좋은 코드를 작성하는 사고법 강의 | 박우빈 - 인프런

  • 스프링 시큐리티 완전 정복 [6.x 개정판]

스프링 시큐리티 완전 정복 [6.x 개정판] 강의 | 정수원 - 인프런

  • Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트

Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 강의 | 김우근 - 인프런

경험 및 인사이트

2024년에는 프로젝트를 진행하면서 다양한 방법을 고민하고, 어떻게 하면 과거 프로젝트보다 좀 더 나은 프로젝트로 만들 수 있을까 고민을 많이 하면서 작업했습니다. 그럼에도 불구하고 아직 많이 부족하다고 생각하며, "왜?"라는 질문에 명확하고 쉽게 설명할 수 있는 개발자로 방향을 잡을 것입니다.

24년도에 아쉬운 점

  1. 프로젝트를 진행하면서 문서화를 많이 못했습니다.
  2. 인프라에 대해 쉽게 설명하지 못했습니다.
  3. 개인 프로젝트 1개는 올렸지만, 너무 간단한 프로젝트였습니다.
    1. MBTI 롤 포지션 — GitHub

25년 목표

  1. 개발 책을 8권 이상 읽겠습니다.
  2. 인프라를 공부하겠습니다.
  3. 사용자가 유입될 수 있는 개인 프로젝트를 2개 이상 진행하겠습니다.
  4. 다른 사람들에게 설명할 수 있는 자리를 적극적으로 마련하겠습니다.

25년에도 속도보다 방향으로 발전하는 사람이 되겠습니다.

댓글

GitHub 계정으로 로그인하시면 댓글과 질문을 남기실 수 있습니다. 남겨 주신 글은 이 저장소의 Discussions에 쌓입니다.

● main 214 posts UTF-8 LF © 2026 코징 RSS · GitHub