Skip to content

chanyoungit/devita-app-backend

Repository files navigation

DEVITA: 개발자 맞춤 성장 미션 제공 서비스

팀원 및 역할

image image image image image image
Jinho.Hong(홍진호) Ayaan.Park(박찬영) Jully.Han(한주리) Milo.Kim(김민제) Winter.Park(박현혜) Dorothy.Kim(김도윤)
팀장
Cloud
Cloud Front-end Back-end AI AI

💁‍♂️ Detail Role

  • Jinho.Hong(홍진호)
    • 팀장
    • 클라우드
    • 아키텍처 설계
    • 젠킨스를 활용한 CI/CD 구축
    • 개발 편의를 위한 디스코드 봇 제작

  • Ayaan.Park(박찬영)
    • Back-end & DevOps
    • 인프라 아키텍쳐 설계
    • Back-end API 구현
    • Redis 관리
    • 단위 테스트 코드 작성
    • Swagger를 활용한 API 문서화

  • Jully.Han(한주리)
    • Front-end
    • React Native로의 앱 전환 및 크로스 플랫폼 환경 구축
    • Styled-components와 Framer Motion을 활용한 UI/UX 최적화 및 화면 디자인
    • Recoil과 React Query를 활용한 상태 관리 및 API 통신 성능 개선

  • Milo.Kim(김민제)
    • Back-end API 구현
    • Back-end 최적화 작업
    • MySQL 관리
    • Jmeter 부하 테스트

  • Winter.Park(박현혜)
    • AI
    • GPT-4o-mini와 Few-shot Prompting으로 맞춤형 미션 생성
    • LangChain, ChromaDB 기반 RAG 데이터 검색 및 미션 생성
    • 프롬프트 설계 최적화로 AI 응답 품질 및 성능 개선

  • Dorothy.Kim(김도윤)
    • AI
    • FastAPI를 활용한 미션 생성 API 설계 및 구현
    • LangChain과 ChromaDB를 활용한 데이터 검색 및 중복 방지 시스템 구축
    • 코사인 유사도를 활용한 중복된 미션 생성 방지 로직 설계


💁🏻‍♂️ Team Introduce

본문 확인 (👈 Click)

팀 이름

일취월장


팀 문화

스크린샷 2024-12-20 오후 3 53 22

  • 매주 3번 회의
    • 이전 회의 이후로 진행된 사항 공유
    • 현재 팀 전체적으로 논의할 사항 논의
    • 다음 회의까지 각자 작업할 사항 결정

image

  • 프로젝트 관리
    • 팀 디스코드 채널을 개설하여 정보 공유 및 소통
    • Jira를 활용하여 일정 및 스프린트 관리

🚩 프로젝트

본문 확인 (👈 Click)

Devita (develop + vitamin)

프로젝트 한줄 소개

개발자 맞춤 성장 미션 제공 서비스

프로젝트 목표

  • 매일 비타민을 섭취하듯 좋은 개발 습관 형성
  • 사용자 맞춤형 미션 추천
  • To-do list를 통한
  • 효율적이고 체계적인 일정 관리
  • SNS를 통한 자신의 학습 성과 공유
  • 게이미피케이션 기반 동기 부여

타 서비스 대비 차별점

image


☁️ 아키텍처

본문 확인 (👈 Click)

image

  • 초창기 웹으로 개발을 진행하다 여러 논의후 앱으로 전환
  • 젠킨스를 활용한 CI/CD 구축
  • Nginx을 활용한 리버스프록시
  • S3 버킷을 활용한 파일 관리
  • VPC로 논리적으로 격리된 공간을 만들고 외부 접근 제한
    • VPC가 외부와 통신이 가능하도록 Internet Gateway 를 구성하고 라우팅 테이블에서 Public Subnet(10.0.1.0/24, 10.0.2.0/24)과 연결
    • NAT Gateway를 구성하여 나머지 Private Subnet 리소스가 인터넷으로 트래픽이 통할 수 있도록 연결

🎁 결과물

본문 확인 (👈 Click)

1. 로그인

Image 1 Image 2 Image 3


2. 관심 개발 분야 선택

Image 1 Image 2 Image 3


3. To do list

Image 1 Image 2 Image 3 Image 4 Image 5


4. 카테고리

Image 1 Image 2 Image 3


5. AI 자율 미션 생성

Image 1 Image 2 Image 3 Image 4 Image 5 Image 6 Image 7 Image 8 Image 9


6. SNS

Image 1 Image 2 Image 3 Image 4 Image 5 Image 6 Image 7 Image 8 Image 9 Image 10 Image 11 Image 12 Image 13


7. 캐릭터

Image 1 Image 2 Image 3


8. 로그아웃

Image 1


🔨 Back-end


Virtual Thread

MVP를 빠르게 구현해야 하는 상황에서 Spring WebFlux의 높은 학습 곡선을 고려해 사용하지 않고, 대신 JDK 17에서 JDK 21로 마이그레이션하면서 새로운 Virtual Thread 기능을 도입하였습니다.

Virtual Threads는 경량 스레드로, 기존 OS 스레드에 비해 메모리 사용량이 적고, 수천에서 수만 개의 동시 스레드를 효율적으로 처리할 수 있습니다. 이를 활용하여 AI 서버와의 요청 처리를 최적화함으로써, 동시성 문제를 해결하고 성능을 크게 개선할 수 있었습니다.

마이그레이션 이후 수행한 테스트 결과, Virtual Thread를 활용한 방식은 기존에 사용하던 RestTemplate 방식과 비교했을 때 초당 throughput이 4배 이상 증가하는 성과를 보였습니다. 이는 Virtual Thread가 요청 처리 중 발생하는 대기 시간을 효과적으로 처리하여, 더 많은 요청을 동시에 처리할 수 있게 해준 결과입니다.

기존 RestTemplate처럼 동기적인 요청 방식이지만, Virtual Thread를 통해 병렬 처리가 가능해졌습니다. 이를 통해 개발 초기 단계에서 빠른 속도로 높은 성능을 구현할 수 있었으며, 복잡한 WebFlux를 학습하지 않아도 JDK 표준 라이브러리를 활용한 간단하고 직관적인 동시성 처리 모델을 구축할 수 있었습니다.


OAuth2, JWT 기반 로그인 시스템 구현

해당 프로젝트는 보안성과 사용자 편의성을 강화하기 위해 OAuth2를 기반으로 소셜 로그인 시스템을 설계하였습니다. OAuth2를 채택함으로써 외부 인증 제공자와의 연동을 통해 사용자 로그인 절차를 간소화하고, 사용자의 개인 정보를 보호하는 동시에 높은 보안성을 유지하도록 했습니다.

이 과정에서 무상태성을 유지하며 인증 정보를 효율적으로 처리하기 위해 JWT를 사용하였습니다. JWT는 토큰 기반 인증 방식으로, 서버에서 인증 상태를 유지하지 않아도 되기 때문에 서버의 부하를 줄이고 확장성을 높이는 데 유리합니다.

Refresh 토큰 관리에서는 보안과 성능을 고려하여 Redis와 쿠키를 활용하였습니다. Redis는 고속의 데이터 처리 속도를 제공하며, 중앙 집중식 데이터 관리가 가능하다는 장점이 있습니다. 특히 Redis의 TTL 기능을 활용하여 Refresh 토큰의 만료 및 자동 삭제를 구현함으로써 토큰 관리의 복잡성을 줄이고 보안을 강화할 수 있었습니다.

또한, Refresh 토큰 로테이션 방식을 적용하여 보안성을 한층 더 높였습니다. Refresh 토큰 로테이션 방식은 Refresh 토큰이 사용될 때마다 새로운 Refresh 토큰을 발급하고 기존의 토큰을 무효화하는 방식으로, 만약 토큰이 유출되더라도 악의적인 사용을 방지할 수 있습니다.

결과적으로, JWT와 Redis를 활용한 인증 구조는 사용자 인증 정보를 안전하고 효율적으로 관리할 수 있는 환경을 제공하였습니다. 또한, 토큰 기반 인증 방식과 Refresh 토큰 로테이션 방식을 조합하여 보안 위협을 최소화하는 동시에 사용자 경험을 개선하였습니다.


Spring Batch & Redis를 활용한 동시성 이슈 로직 처리

SNS 기능 중 하나인 좋아요 기능의 동시성 처리를 위해 낙관적 락, 비관적 락, 그리고 Redis의 성능을 JMeter를 사용해 비교해보았습니다.

낙관적 락은 충돌 가능성을 허용한 뒤 커밋 시점에서 충돌을 검증하는 방식으로 동작하기 때문에 소요 시간을 고려하여 100번 이상 반복되는 테스트는 실패하도록 설정했습니다. 테스트 결과, 성능 저하와 높은 에러율이 확인되었습니다. 50000개의 요청 중 8595개만 성공적으로 처리되었으며, 나머지 요청은 충돌로 인해 실패했습니다. 테스트는 총 1분 13초가 소요되었고, 82.81%의 에러율과 초당 Throughput 678.5라는 결과가 나왔습니다. 낙관적 락 방식에서는 잦은 충돌로 인해 성능이 크게 저하되는 것을 확인할 수 있었습니다.

비관적 락은 데이터 충돌 가능성을 미리 차단하며, 순차적으로 데이터를 처리하기 때문에 50000개의 요청이 모두 성공적으로 처리되었으며, 테스트 시간은 31초로 낙관적 락보다 약 2배 이상 빠른 성능을 보여주었습니다. 에러율은 0%로 낮은 에러율이 나왔고, 초당 Throughput 1582.6이라는 결과를 확인할 수 있었습니다. 그러나 비관적 락은 동시성 처리를 차단하기 때문에 많은 요청이 몰릴 경우 대기 시간이 증가할 가능성이 있었습니다.

Redis는 단일 스레드로 동작하지만, In-Memory DB로써 데이터베이스 통신 비용이 적고 빠른 데이터 처리가 가능하기 때문에 50000개의 요청이 모두 성공적으로 처리되었으며, 테스트 시간은 14초로 비관적 락보다 약 2배 빠른 성능을 보였습니다. Throughput은 초당 3417.2라는 결과를 확인할 수 있었습니다. 또한, Redis 6부터는 네트워크 I/O 처리를 위한 추가 스레드(최대 3개)가 도입되어 더 높은 처리량을 제공할 수 있었습니다.이러한 이점을 바탕으로 Redis를 좋아요 기능의 동시성 처리에 적용하기로 결정했습니다.

좋아요 데이터의 TTL은 12시간으로 설정했으며, Spring Batch를 활용하여 Redis와 DB 간의 정합성을 맞추는 동기화 작업을 구현했습니다. 12시간에 한 번씩 실행하여 주기적으로 Redis와 DB 간의 정합성을 맞췄습니다.

데이터를 영구적으로 저장하기 위해서는 RDB 방식을 활용했습니다. 데이터 손실을 최소화하려면 AOF 방식이 적합하지만, 동시성 처리를 중점으로 하며 Spring Batch를 사용하여 정기적으로 DB와의 정합성을 맞춘다면 디스크 I/O가 적은 RDB 방식이 더 효과적일 것이라고 판단하여 선택했습니다. RDB 덤프 주기는 5분에서 15분 사이로 설정해 데이터를 디스크에 저장했습니다.

이러한 설정을 통해 Redis의 빠른 성능을 유지하면서도 DB와의 정합성을 안정적으로 관리할 수 있었습니다.


팔로우 조회 시 DB 서버 부하를 고려한 캐시 전략

팔로우와 같은 데이터는 사용자 간의 상호작용을 나타내는 중요한 정보로, 조회 빈도는 높지만 변경 빈도는 상대적으로 낮은 특성을 가지고 있습니다. 특히, 사용자가 증가함에 따라 이러한 데이터의 조회 요청이 늘어나게 되고, 매번 데이터베이스에서 직접 조회할 경우 DB 서버에 과도한 부하가 발생할 가능성이 매우 높습니다. 이러한 문제는 데이터베이스 성능 저하로 이어질 수 있으며, 사용자 경험에 부정적인 영향을 미칠 수 있습니다.

이를 해결하기 위해 Redis를 캐시로 사용하는 구조를 도입하였습니다. Redis는 인메모리 데이터 저장소로, 데이터베이스보다 훨씬 빠른 속도로 데이터를 읽고 쓸 수 있다는 장점이 있습니다. 이 구조에서는 팔로우 데이터와 같은 자주 조회되는 정보를 미리 캐싱하여, 데이터베이스로의 조회 요청을 크게 줄이고, 저장된 데이터를 고속으로 제공할 수 있습니다.

Redis를 도입한 후, 캐시는 Write-Behind 방식으로 동작하도록 설계했습니다. 이 방식에서는 데이터가 변경될 때, 먼저 Redis 캐시를 업데이트한 후, 배치 작업을 통해 일정 주기마다 DB에 반영하는 방식으로 동기화가 이루어집니다. 이를 통해 빠른 응답을 제공하면서도 DB와의 정합성을 일정 주기마다 맞출 수 있습니다.

  1. 데이터 변경 시 Redis 캐시 업데이트: 사용자가 팔로우 데이터를 변경하면, 즉시 Redis 캐시를 업데이트하여 실시간으로 변경 사항을 반영합니다. 이때 DB에는 바로 반영하지 않고, 캐시를 우선적으로 갱신합니다.
  2. 배치 작업을 통한 DB 동기화: 일정 주기에 따라 Spring Batch 처리 시스템을 사용하여, Redis에 저장된 데이터를 DB와 동기화합니다. 이 과정에서 DB는 주기적으로 캐시된 데이터를 업데이트하거나 반영하여 데이터의 정합성을 유지합니다.
  3. 캐시 조회 시 빠른 응답 제공: 사용자가 팔로우 데이터를 조회하면, 먼저 Redis 캐시에서 데이터를 확인하고, 캐시된 데이터가 있을 경우 빠르게 응답을 제공합니다. 캐시가 없으면 데이터베이스에서 조회한 후, 해당 데이터를 캐시에 저장하여 이후 조회 시 빠른 응답을 제공합니다.

Write-Behind 방식을 통해 캐시와 DB 간의 동기화를 효율적으로 관리하면서도 데이터베이스의 부하를 최소화하고, 사용자에게 빠른 응답을 제공했습니다. 또한, TTL설정을 통해 캐시된 데이터의 유효기간을 관리하여 최신 데이터를 유지했습니다.


일일 미션과 To-do 관리 시스템 설계

사용자가 일일 미션이나 To-do를 완료하면 보상이 지급되는 구조를 설계하였습니다. 이 보상은 최초 완료 시에만 지급되며, 중복 지급을 방지하여 보상의 희소성을 유지하도록 하였습니다. 또한, 일일 보상 지급 횟수에는 제한이 있어, 이를 효과적으로 관리하고 시스템의 성능을 최적화하기 위해 Redis의 TTL 기능을 활용했습니다. TTL은 보상 지급의 유효 기간을 설정하고 자동으로 만료되도록 하여, 데이터 관리의 복잡성을 줄이고 정확성을 유지할 수 있었습니다.


미션 보상 시 데이터 무결성을 위한 트랜잭션 적용

To-do나 미션의 상태가 변경될 때마다 MySQL과 Redis를 동시 사용하여 데이터의 정합성을 관리하였습니다. 보상 지급 여부를 확인하거나 사용자 미션 상태를 업데이트할 때, Redis에서 데이터를 조회하거나 수정한 뒤, 그 결과를 MySQL에 반영하는 과정이 필요합니다.

하지만, 이러한 과정에서 시스템 예외가 발생할 경우, Redis와 MySQL의 데이터 간 불일치 문제가 발생할 가능성이 있습니다. 이를 방지하기 위해 트랜잭션(transaction)을 도입하여 모든 데이터 처리 단계를 하나의 논리적 작업 단위로 묶었습니다. 이를 통해 데이터 일관성을 유지하고, 시스템 안정성을 확보할 수 있었습니다.


스케줄러를 활용한 AI 서버와 통신

오전 9시마다 사용자 맞춤형 미션을 자동으로 생성하는 기능을 구현하였습니다. 이 기능은 스케줄러를 활용하여 특정 시간에 AI 서버와의 연동을 통해 사용자별로 개인화된 미션을 생성하는 구조로 설계했습니다.


Junit5와 Mockito로 고립된 단위테스트 작성

소스코드를 작성하거나 변경 시 오류가 나지 않는지 기능을 하나하나 URL에 데이터를 보내 직접 테스트를 했어야 했습니다. 이런 불편을 개선하기 위해 서비스 로직을 대상으로 Junit5와 Mockito를 활용한 단위 테스트를 작성하였습니다. 의존성이 있는 객체를 대상으로 Mock DAO 객체를 만들어 주입하였고, DB에서 예상되는 결과값들을 반환하도록 설정하였습니다.

그 결과, Service 레이어에 있는 로직을 중심으로 고립된 단위 테스트를 제작할 수 있었습니다. Mockito Framework를 사용하여 Spring container가 올라가지 않고, DB와의 커넥션을 하지 않기 때문에 빠른 테스트가 가능하도록 하였습니다.


JPA 성능 최적화

JPA를 사용하면서 발생할 수 있는 문제점 중 하나로, 사용하기 편리한 만큼 성능적인 부분을 쉽게 간과할 수 있다고 생각했습니다. 이번 프로젝트에서는 Fetch 전략을 LAZY로 설정하여 해당 로직에 반드시 필요한 쿼리만 실행되도록 구현하였으며 N+1 문제가 발생하는 부분을 찾아 리팩토링하는 과정을 진행하여 불필요한 쿼리가 실행되는 것을 최소화할 수 있도록 구현했습니다.


Swagger를 활용한 API 문서화

API 문서를 수동으로 관리하게 된다면 애플리케이션을 수정해야 하는 상황이 발생했을 때 API 문서도 수동으로 변경하는 작업을 거쳐야 합니다. 또한, 이러한 작업은 자동화 작업이 아니기 떄문에 실수할 가능성이 존재합니다. 이러한 문제를 사전에 방지하기 위해 Swagger를 활용한 API 문서의 자동화를 적용하게 되었습니다.

About

개발자 맞춤 성장 미션 제공 서비스

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published