Skip to content
This repository has been archived by the owner on Aug 14, 2022. It is now read-only.

1. 프로젝트 소개

Hyung1Jung edited this page Aug 24, 2021 · 14 revisions

주제

익명성 보장을 기반으로 글을 통해 자신의 일상, 고민을 공유하는 소셜 네트워크 서비스입니다.

핵심

​우리는 자신의 일상, 고민을 이야기하고 싶을 때가 있습니다. 하지만 나를 드러내며 이야기하는 것이 부담스러울 때가 있습니다. 가끔은 내 자신을 숨기고 속 시원하게 여러 이야기를 하고 싶을 때가 있죠. 이를 위해 익명성을 보장하여 다수에게 자신의 일상, 고민을 속 시원하게 공유하며 의견을 들어볼 수 있는 소셜 네트워크 서비스를 만들고 있습니다.

현재까지 서버 구조도

서버 구조도

✅ WAS 1 : [Compact] 2vCPU, 2GB Mem, 50GB Disk [g1]

✅ WAS 2 : [Compact] 2vCPU, 2GB Mem, 50GB Disk [g1]

✅ Nginx : [Standard] 2vCPU, 4GB Mem, 50GB Disk [g1]

✅ MySQL Master : [Compact] 1vCPU, 2GB Mem, 50GB Disk

✅ MySQL Slave : [Compact] 1vCPU, 2GB Mem, 50GB Disk

✅ Login Session 저장소(Redis) : [Compact] 1vCPU, 2GB Mem, 50GB Disk

✅ Controller Server : [Standard] 2vCPU, 4GB Mem, 50GB Disk [g1]

✅ Agent Server : [Standard] 4vCPU, 8GB Mem, 50GB Disk [g1]

✅ Jenkins : [Compact] 1vCPU, 2GB Mem, 50GB Disk

✅ AWS S3 : AWS 프리 티어 (이미지 저장 기본 50GB)

기능 정의

  • 회원가입
  • 로그인
  • 사용자 역할(비사용자, 사용자, 관리자)에 따른 접근 제한
  • 로그아웃
  • 회원탈퇴
  • 회원 정보 수정 - 프로필 사진 등록, 삭제 / 비밀번호 수정 / 닉네임 수정 / 주소 수정
  • 자신의 피드에 글 작성
  • 전체 사용자 글 최신순으로 조회
  • 댓글, 대댓글 작성
  • 팔로잉, 팔로잉 취소, 팔로우
  • 1대1 대화 신청시 결제 기능

프로젝트 주요 관심사

  • 여러 기술들의 트레이드 오프를 고려한 후, 어떤 기술을 도입하는 것이 가장 서비스에 가장 적합할까?
  • 다른 개발자가 내 코드를 보았다고 가정할 때, 어떻게 코드를 작성하는 것이 이해하기 쉬운 코드이며 유지보수에 용이한 코드일까?
  • 꾸준히, 깊은 이론 학습을 통해 내가 작성한 코드를 어떻게 더 좋은 코드로 만들까?
  • 대용량 트래픽의 상황에서 지속적인 서버 개선을 위해 어떻게 성능을 튜닝할까?
  • 위의 관심사들을 기반으로 고민한 이슈들을 기술블로그 에 작성하고 팀 내 공유하여 함께 성장해가는 개발 문화 추구

🔍 객체지향적인 코드를 위한 지속적인 리팩토링

메서드 하나당 하나의 관심사만 갖도록하고, 관심사에 맞는 명확한 이름을 가지도록 계속 리팩토링 하였습니다. 다른 개발자가 와서 제 코드를 보더라도, 이해하기 쉽고 유지보수에 용이하도록 작성하여 유지보수 비용을 줄이기 위해 계속 고민하고 리팩토링 하였습니다.

🔍 명확한 이유와 근거를를 기반으로 각 기술의 트레이드 오프를 고려

항시 어떤 기능을 구현함에 있어 도입할 수 있는 기술들이 여러가지가 있습니다. 각 기술에는 탄생 이유, 동작원리, 장단점이 존재합니다. 항시 이러한 것들에 관해 이론을 깊게 학습한 뒤, 명확한 이유와 근거를 가지고 각 기술의 트레이드 오프를 고려하였습니다.

본 프로젝트에서는

  • 세션 문제를 해결하기 위한 Sticky session vs Tomcat clustering, vs Session Storage
  • 어떤 Session Storage를 사용할지에 관해 Redis vs memcached vs RDBMS
  • 지속적 통합 서비스를 위해 CI 구축 결정 후 CI 도구를 위해 Jenkin, vs Travis CI, vs GitLab CI
  • 로깅을 위한 log4j vs Lockback vs log4j2

등의 이슈에 관해 깊은 이론을 학습 후 명확한 이유와 근거를 가지고 트레이드 오프를 고려하여 본 프로젝트에서 제일 최적의 속도를 보장할 수 있는 기술을 사용하였습니다.

🔍 서버의 확장성을 고려한 설계

본 프로젝트는 단순한 기능 구현뿐만이 아닌 애플리케이션의 사용자가 증가함에 따라 발생할 수 있는 트래픽 문제를 고려하여 진행하였습니다.

고 사양 하드웨어 장비를 통해 기존 서버 자원의 성능을 증강하는 Scale-up 방식의 경우 확장의 한계와 서버에 문제가 발생하면 서버가 복구될 때까지 서비스를 중단해야 하는 단점이 있다고 판단하였습니다. 따라서 비슷한 사양의 서버를 추가해 트래픽을 분산시키는 Sacle-out 방식을 고려하여 프로젝트를 진행하였습니다.

🔍 인터페이스를 사용하여 비즈니스 코드의 확장성을 고려한 설계

  • 각종 로그인 방법을 고려해 로그인 인터페이스를 두고 확장하여 설계한 세션 로그인,
  • 파일 업로드 인터페이스를 두고 확장하여 설계한 AWS S3 이미지 업로드,
  • 각종 비밀번호 암호화 방법을 고려해 Encryption 인터페이스를 두고 확장하여 설계한 Sha256encryption 암호화

등의 비즈니스 코드의 확장성을 고려한 코드를 작성하였습니다.

🔍 커뮤니케이션을 위한 코드

코드로 예를 들어보도록 하겠습니다.

int isExistUser(long userId); // 0 = 없다, 1 = 있다
if (isExistUser(userId) == 1) { // 유저가 존재한다 }
boolean isExistUser(long userId);
if (isExistUser(userId)) { // 유저가 존재한다 }

첫 번째 코드는 유지보수를 할 때 1이 어떤 것을 의미하는지 명확하지 않습니다. 즉 저 코드를 계속 유지보수한다면 1이 어떤 것을 의미하는지 대해서 찾아봐야하는 수고가 들 것입니다. 그 의미 값에 대해 상수로 관리한다면 조금은 가독성이 향상되겠지만 그 상수를 관리하는 수고가 들 것이라 생각합니다.

내가 짜는 코드의 의미를 전달하는 것에 대해 더 고민하면서 깔끔하게 짜려고 노력하는 것도 다른 개발자가 내 코드에 대해 이해하고 사용하는데에 드는 시간을 줄여주는 배려라고 생각합니다, 이 또한 상대방이 내 의도를 이해하는데에 걸리는 시간을 줄여주기 때문에 코드가 커뮤니케이션 스킬이 될 수 있도록 계속 고민해보고 있습니다.

🔍 중복되는 부가기능 처리

본 프로젝트는 핵심 서비스에 한하여 사용자 역할에 따른 접근 제한을 두고 있습니다. 따라서 대부분의 서비스를 이용할 때 로그인 여부를 확인하는 작업이 필요하며, 일부 서비스는 현재 로그인 되어있는 사용자를 불러오는 작업까지 필요하게 됩니다.

해당 작업들은 대부분의 비즈니스 로직에서 메소드 전,후로 중복적으로 나타나는 기능으로, 중복되는 부가기능을 따로 처리하기 위해 Interceptor를 활용하여 중복되는 로직들을 제거하였습니다.

🔍 Redis를 활용하여 세션 정합성 문제 해결

분산 서버 환경에서 발생할 수 있는 로그인 정보를 담은 세션 데이터의 정합성 문제를 해결하였습니다. Sticky session, Tomcat clustering 등 여러 가지 해결책들을 후보에 두고 각각의 trade off을 고려하여 가장 적합하다고 판단한 솔루션인 세션 저장소 서버를 구축하는 방법을 선택하였습니다.

본 프로젝트에서는 로그인 정보를 비교적 빠른 속도로 저장하고 조회할 수 있는 In-memory DB 사용을 고려하였으며 Spring에서 간단한 의존성 빌드를 통해 사용할 수 있는 Spring-redis-session을 사용함으로써 개발의 생산성을 증진시킬 수 있었습니다.

🔍 지속적인 통합 서비스를 위해 젠킨스 CI/CD 구축

NCP서버에서 Jenkins 서버를 띄우고 CI를 적용하였습니다. 본 프로젝트는 협업을 통해 진행되는 만큼 코드의 충돌이나 버그가 없도록 구현하는 것이 상당히 중요한 부분이었습니다.

따라서 feature branch를 포함한 모든 branch에 git push가 발생하거나 PR이 진행되었을 때마다 GitHub에서 Webhook을 보내 테스트와 빌드가 자동 실행되도록 멀티 브렌치 파이프라인을 설계하였고,job을 실행하면 원격 서버에 있는 배포 스크립트가 실행되도록 설정하였습니다.

또한 Github에서만 항시 모니터링 하기에는 시간과 공간의 제약이 있으므로, Jenkins에서 발생한 모든 결과는 협업 중인 개발자와 함께 속해 있는 Slack 채널에도 알림을 보내도록 설정하여 즉각적인 피드백을 진행하고 있습니다.

알람을 받음으로써 애자일 개발 과정이 더욱 빨리지는 장점이 있고. 팀원이 빌드, 실패, 통합을 빨리 알아차릴수록 더 빨리 대응할 수 있다는 생각이 들었습니다.

🔍 트레픽 분산을 위한 Master/Slave DataSource 동적 라우팅 설정

master DB 서버와 Replication된 slave DB 서버를 두고

  • master DB 서버는 모든 쓰기, 수정, 삭제 작업만 수행하고,
  • Replication된 slave DB 서버에서는 모든 읽기 작업만을 수행하여 트레픽을 분산시켰습니다.

또한 위의 과정을 동적으로 라우팅하였습니다.

🔍 Nginx 서버에 로드밸런싱 설정

클라이언트 요청에 관한 부하를 분산하기 위해 Nginx 서버에 로드밸런싱을 설정하였습니다. Nginx에 설정된 리버스 프록시 서버가 내부 서버에 대한 정보를 알고 있으므로, 각 서버의 상태에 따라 부하를 분산시키며 요청을 전달할 수 있습니다. 따라서 클라이언트의 요청에 더욱 빠른 응답을 할 수 있게 되었습니다. 또한 외부 사용자로부터 내부 망에 있는 서버의 존재를 숨길 수 있습니다. 모든 요청은 리버스 프록시 서버에서 받으며, 매핑 되는 내부 서버로 요청을 전달합니다.

🔍 nGrinder를 이용한 성능 테스트

저는 본 프로젝트가 실제 서비스로 출시되었다고 가정했을 때, 지속적인 수익 창출을 원하고 이를 위해서는 사용자들에게 서버가 멈추지 않고, 더욱 빠른 서비스를 제공해드려야 한다고 생각합니다. 따라서 nGrinder를 사용하여 성능 테스트를 진행하였습니다.

성능 테스트를 진행하여 얻은 성능 지표로 본 프로젝트의 병목 지점을 찾고, 대용량 트래픽의 상황에서 지속적인 서버 개선을 위해 어떻게 성능을 튜닝할 것인지 고민하여 지속적으로 리팩토링 하였고, 그 결과 이전보다 더 빠른 응답을 보장하는 서비스로 만들 수 있게 되었습니다

🔍 MySQL 쿼리 실행계획 분석을 쿼리 실행 속도 개선

비즈니스 로직에서 사용되는 MySQL 쿼리의 실행계획을 분석하면서 비효율적으로 동작하는 쿼리에 대한 튜닝과 리팩토링 작업을 진행하였습니다. 테이블 풀 스캔이 발생하는 경우 index의 손익 분기점과 선택도를 파악하면서 필요한 경우 index를 추가하였고, 경우에 따라서 비즈니스 로직 자체를 리팩토링하면서 효율적인 애플리케이션을 구성할 수 있도록 노력하였습니다. 따라서, 실행계획 분석을 통해 쿼리의 실행 속도를 더욱 빠르게 개선하였습니다.

🔍 테스트 코드 작성

어떠한 프로젝트를 진행하더라도 완성된 후에 발생할 수 있는 요구 사항의 추가나 변경을 고려해야 합니다. Controller Layer에 모든 case마다 단위 테스트를 작성함으로써 코드의 추가 또는 변경이 있을 경우 다른 코드에 미치는 영향을 빠르게 파악하여 개발의 생산성과 유지보수성을 증진시킬 수 있도록 구현하였습니다.

🔍 API 문서 자동화를 위해 Spring Rest Docs 적용

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

Spring 환경에서는 API 문서 자동화에 주로 Swagger와 Spring Rest Docs가 사용됩니다. 각각 장단점을 비교해본 결과, Swagger를 사용하게 되면 Controller Layer에 비즈니스 로직과는 전혀 관련 없는 문서화를 위한 annotation들이 추가되어 프로덕션 코드의 오염을 야기한다고 판단하여 Spring rest docs로 API의 문서의 자동화를 진행하였습니다.

🔍 코틀린을 사용하여 간결하고 효율적인 코드 지향

자바에서는 형식을 맞추기 위해 무의미하고 반복적인 코드를 작성하게 되어 항상 코드가 비대해지기 마련이었습니다. 저는 이 문제를 해결하기 위해 코틀린을 사용하였습니다.

코틀린은 타입 추론과 더불어 자체적으로 함수형 프로그래밍을 지원해주기 때문에 자바에 비해 훨씬 코드를 간결하게 작성할 수 있었으며 개발 생산성과 유지 보수성 면에서도 훨씬 좋은 성과를 거두었습니다.

또한 코틀린은 별도의 표기가 없는 경우 널 값을 허용하지 않기 때문에 자바에서 흔히 발생하는 널 포인터 예외를 피할 수 있었습니다. 이는 널 포인터 때문에 프로그램이 중단되는 현상을 방지하는 성과를 거둘 수 있었습니다.