24년도는 신규 프로젝트 및 다양한 경험을 하였던 한 해였습니다. 운이 좋게 신규 프로젝트를 처음부터 끝까지 기일에 맞춰 오픈하였고, 중간중간의 문제를 해결하기 위해 공부했던 내용들도 많았습니다. 각각의 내용에 대해 간단하게 작성하고 24년도에 정리한 내용을 링크로 남겨 기록하도록 하겠습니다.
1.1 신규 구축한 어드민 웹 서비스
처음으로 프로젝트를 맡아 신규로 오픈한 웹 사이트입니다.
1.1.0 패키지 분리
├── cache
└── core
│ └── domain
│ └── domainA
│ │ └──repository
│ │ │ └──domainRepository
│ │ │ └──domainRepository
│ │ └──service
│ │ └──domainService
│ └── domainB
│ └── common
├── storeage
└── web
패키지 분리 기준
cache,storage등 기술/기능 중심 패키지를 최상단에 배치합니다.core는 도메인과 공통 비즈니스 로직을 포함하도록 설계합니다.
도메인 패키지 하위 분리
도메인(
domainA,domainB)을 개별적으로 구성하고, 각 도메인의 엔티티, 리포지토리, 서비스, DTO 등을 세분화했습니다.예를 들어,
domainA안에서 엔티티와 관련된 모든 파일을 쉽게 찾을 수 있습니다.웹 계층 분리
web에 컨트롤러와 **웹 전용 로직(DTO, 예외 처리)**을 한곳에 모아두었습니다.- Interceptor, ArgumentResolver를 별도로 관리하여 구조를 명확히 했습니다.
공통 코드 위치
- 모든 도메인에서 공통적으로 사용하는 코드는
core/common으로 분리했습니다. - 공통 유틸, 상수, 예외 등은 여기에 위치합니다.
- 모든 도메인에서 공통적으로 사용하는 코드는
1.1.1 종속성 문제 해결
├── 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
- 도메인 중심의 계층화
core/domain아래에 도메인별로 repository와 service를 포함하여 도메인 중심 설계를 유지했습니다.repository는 도메인 로직에서 직접 호출 가능하며, 저장소의 세부 사항은storeage에 위임합니다.service는 도메인별 비즈니스 로직의 집합체로, 핵심 기능을 캡슐화합니다.
- 저장소 세부 사항 분리
storeage/coroe아래에 JPA 및 MyBatis를 명확히 분리하여 구현 기술별로 관리했습니다.jpa와mybatis가 공존하더라도 서로 영향을 주지 않습니다.- 도메인 모델과 DB 엔티티, DTO 간의 변환은 각 계층에서 관리할 수 있습니다.
- 웹 계층 독립
web패키지에서 요청/응답 DTO, 컨트롤러, 예외 처리를 관리합니다.- 도메인 계층과 별도로 분리하여, 확장성과 유지보수성을 높였습니다.
- 확장성 확보
external패키지를 통해 외부 API 연동 코드를 분리하여, 다양한 외부 서비스와의 통합이 가능하도록 했습니다.cache패키지로 캐시 관련 로직을 통합 관리해 성능 향상을 도모했습니다.
1.1.2 낙관적 락, 비관적 락, 분산 락
1. 낙관적 락 (Optimistic Lock)
개념
데이터 충돌 가능성이 낮다고 가정하고, 트랜잭션이 완료될 때까지 잠금을 걸지 않으며 데이터를 처리하는 방법입니다.
트랜잭션이 끝날 때 데이터 변경 여부를 검증하여 충돌이 없을 경우에만 데이터를 갱신합니다.
일반적으로 **버전 관리(versioning)**를 통해 구현합니다.
특징
- 잠금을 걸지 않기 때문에 성능이 좋습니다.
- 충돌 가능성이 높은 환경에서는 실패율이 증가할 수 있습니다.
사용 사례
- 데이터 갱신 빈도가 낮고 충돌 가능성이 적은 환경입니다.
- 데이터 일관성을 트랜잭션 종료 시점에만 보장하면 되는 경우입니다.
관련 정리 링크
2. 비관적 락 (Pesimistic Lock)
개념
데이터 충돌 가능성이 높다고 가정하고, 트랜잭션이 시작되자마자 잠금을 걸어 다른 트랜잭션이 접근하지 못하도록 하는 방법입니다.
락을 통해 강제적으로 자원을 보호하며, **공유 락(Shared Lock)**과 **배타 락(Exclusive Lock)**으로 나뉩니다.
특징
- 충돌이 발생하지 않도록 보장하지만, 잠금으로 인해 성능 저하가 발생할 수 있습니다.
- 데드락(Deadlock)이 발생할 가능성이 있습니다.
사용 사례
- 데이터 갱신 빈도가 높고 충돌 가능성이 큰 환경입니다.
- 자원의 일관성을 트랜잭션 실행 중에도 보장해야 하는 경우입니다.
관련 정리 링크
3. 분산 락 (Distributed Lock)
개념
멀티스레드 또는 멀티 프로세스 환경에서 공유 자원에 대한 동시 접근을 제어하기 위해 사용되는 락입니다.
주로 분산 시스템에서 데이터 정합성을 보장하기 위해 사용합니다.
구현 방법
- Redis:
SETNX명령어와EXPIRE를 활용해 락을 설정합니다. - ZooKeeper: 분산 락 구현에 사용되는 대표적인 툴로,
ephemeral znode를 활용합니다. - Database: 특정 테이블을 활용하여 락 정보를 관리합니다.
- Redis:
특징
- 분산 환경에서 데이터 일관성을 보장합니다.
- 구현이 복잡하고, 네트워크 지연 및 장애 상황에 대비해야 합니다.
사용 사례
- 분산 시스템에서 자원 접근 동기화가 필요한 경우입니다.
- 클러스터 환경에서 동일 데이터에 대한 경쟁 조건을 방지해야 하는 경우입니다.
관련 정리 링크
1.1.3 Test Code 도입
- TestCode 설명
[인프런 워밍업 클럽 백엔드 스터디 2기] 4주차 발자국 - 박태영님의 블로그 - 인프런 | 커뮤니티
1.1.4 코드리뷰 도입
- 신규 프로젝트를 구축하면서 기존 운영 시스템에서 발생했던 문제를 개선하고자 코드 리뷰를 진행했습니다.
- 각자 맡고 있는 개발을 설명하고, 신규 프로젝트에 적용하고 있는 코드 규칙과 문제점은 없는지 확인했습니다.
- 코드리뷰를 새로 도입하면서 제일 중요한 부분은 개발자들 간의 신뢰였습니다.
- 신뢰 속에서 더 좋은 방법이라 생각될 때 편하게 말하는 분위기가 중요합니다.
1.1.5 DBCP
DBCP는 데이터베이스와의 연결을 미리 생성해 풀(Pool)에 보관함으로써, 클라이언트 요청 시 매번 TCP 연결을 생성/해제하는 과정을 생략해 성능을 개선하는 기술입니다.
DBCP가 없는 경우
- 클라이언트 요청 → 애플리케이션에서 TCP 연결 생성 (3-way Handshake)
- 데이터 조회 후 TCP 연결 해제 (4-way Handshake)
- 반복적으로 연결 생성/해제가 발생해 성능이 저하됩니다.
DBCP가 있는 경우
- 미리 생성된 연결 풀에서 Connection을 가져와 재사용합니다.
- 3-way 및 4-way Handshake 과정을 최소화해 빠른 응답을 제공합니다.
DB 설정에서 중요한 항목
- MaxConnections
- DB가 처리할 수 있는 최대 연결 수를 적절히 설정합니다.
- wait_timeout
- 유휴 상태 연결을 자동 종료하여 불필요한 연결 점유를 방지합니다.
DBCP 주요 설정
- minimumIdle
- 유지할 유휴 연결의 최소 개수입니다.
- maximumPoolSize
- 동시에 열 수 있는 최대 연결 수입니다.
- 최소/최대 값을 동일하게 설정하는 것이 권장됩니다.
- maxLifetime
- 연결 유효 시간입니다.
- DB의
wait_timeout보다 2초 작게 설정할 필요가 있습니다.
- connectionTimeout
- 풀에서 연결을 가져오기 위한 최대 대기 시간입니다.
결론
- DB 설정(
MaxConnections,wait_timeout)과 DBCP 설정(minimumIdle,maximumPoolSize,maxLifetime)을 조화롭게 관리해야 합니다. - 부하 발생 시 설정 최적화 또는 장비 증설로 대응합니다. 이를 통해 시스템 안정성과 성능을 유지할 수 있습니다.
관련 링크
캐시 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개는 올렸지만, 너무 간단한 프로젝트였습니다.
- MBTI 롤 포지션 — GitHub
25년 목표
- 개발 책을 8권 이상 읽겠습니다.
- 인프라를 공부하겠습니다.
- 사용자가 유입될 수 있는 개인 프로젝트를 2개 이상 진행하겠습니다.
- 다른 사람들에게 설명할 수 있는 자리를 적극적으로 마련하겠습니다.
25년에도 속도보다 방향으로 발전하는 사람이 되겠습니다.
댓글
GitHub 계정으로 로그인하시면 댓글과 질문을 남기실 수 있습니다. 남겨 주신 글은 이 저장소의 Discussions에 쌓입니다.