Skip to content

Commit

Permalink
Update BACK.md
Browse files Browse the repository at this point in the history
  • Loading branch information
ji-hunc authored May 30, 2024
1 parent 00d3785 commit c6a6cb5
Showing 1 changed file with 7 additions and 7 deletions.
14 changes: 7 additions & 7 deletions back/BACK.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@

현재 크롤링은 학식과 공지사항 분야에서 실시되고 있다. 크롤링 할 때도 몇가지 고려할 사항이 존재한다.

첫번째, 정적 크롤링과 동적 크롤링 어떤 것이 필요한가 고려해봐야 한다. 공지사항과 학식은 굳이 로그인을 하지 않아도 누구나 접근할 수 있기 때문에 굳이 동적 크롤링이 필요하지 않았다. 또한, 동적 크롤링은 Selenium 라이브러리를 이용하고, Chrome Driver를 까는 등 추가적인 설정이 매우 복잡하다. 따라서, 사용이 간단한 Jsoup을 이용해서 정적 크롤링을 진행하기로 결정했다.
첫번째, 정적 크롤링과 동적 크롤링 어떤 것이 필요한가 고려해봐야 한다. 공지사항과 학식은 굳이 로그인을 하지 않아도 누구나 접근할 수 있기 때문에 굳이 동적 크롤링이 필요하지 않았다. 또한, 동적 크롤링은 Selenium 라이브러리를 이용하고, Chrome Driver를 설치하는 등 추가적인 설정이 매우 복잡하다. 따라서, 사용이 간단한 Jsoup을 이용해서 정적 크롤링을 진행하기로 결정했다.

두번째, 크롤링은 외부 네트워크와 통신하여 이루어진다. 그래서 네트워크 I/O 병목지점을 어떻게 해결할것이고 이 때 자원을 최대한 아낄 수 있는지 고려해봐야 한다. 따라서, 크롤링은 Async를 이용하여 비동기적으로 처리되도록 하여 병목 현상을 줄이도록 하였다.

세번째, 공격적인 크롤러는 상대 서버에게 무리를 줄 수 있다. 따라서, 크롤링을 진행할 때 상대 서버에 부담을 줄일 수 있도록 고려해야한다. 그래서, 학식같은 경우는 국민대 학식 API가 존재하여 이를 이용하여 정보를 가져왔다. 하지만, 공지사항은 이런 API가 없기 때문에 다른 방법이 고려해야된다. 그래서 크롤링 횟수를 하루에 1~2번으로 줄였고, 되도록 사람이 적게 몰리는 새벽 또는 식사 시간대에 크롤링을 진행하도록 구성했다. 또한, 웹사이트의 robots.txt을 준수하여 크롤링이 허용되지 않은 영역은 진행하지 않았다.
세번째, 공격적인 크롤러는 상대 서버에게 무리를 줄 수 있다. 따라서, 크롤링을 진행할 때 상대 서버에 부담을 줄일 수 있도록 고려해야한다. 그래서, 학식같은 경우는 국민대 학식 API가 존재하여 이를 이용하여 정보를 가져왔다. 하지만, 공지사항은 이런 API가 없기 때문에 다른 방법을 고려해야된다. 따라서 크롤링 횟수를 하루에 2번으로 줄였고, 되도록 사람이 적게 몰리는 새벽 또는 식사 시간대에 크롤링을 진행하도록 구성했다. 또한, 웹사이트의 robots.txt을 준수하여 크롤링이 허용되지 않은 영역은 진행하지 않았다.

네번째, Jsoup를 이용하여 크롤링을 진행하였는데, Jsoup은 정적 메소드만 제공해서 테스트가 어렵다는 점을 고려해야 . 그래서 이를 해결하기 위해서 단위테스트시 Mockito같은 라이브러리를 이용하여 테스트를 진행하면 된다. 하지만, 이 부분은 시간이 부족하여 고려만하고 진행하지 못하였다. 추후, 다른 프로젝트를 진행할 때는 이 점을 고려하여 테스트를 진행할 예정이다.
네번째, Jsoup를 이용하여 크롤링을 진행하였는데, Jsoup은 정적 메소드만 제공해서 테스트가 어렵다는 점을 고려해야 한다. 그래서 이를 해결하기 위해 단위테스트시 Mockito같은 라이브러리를 이용하여 테스트를 진행하면 된다. 하지만, 이 부분은 시간이 부족하여 고려만하고 진행하지 못하였다. 추후, 다른 프로젝트를 진행할 때는 이 점을 고려하여 테스트를 진행할 예정이다.

<br>

Expand Down Expand Up @@ -88,7 +88,7 @@ API 문서는 Swagger를 사용하였다. 소프트웨어공학에서 Agile Proc

### **배포**

AWS EC2 인스턴스에 배포한다던지, 컴퓨터와 노트북을 번갈아가며 개발하다던지, 이러면 각각 개발 환경이 틀려 매번 설정을 해주어야 한다. 하지만 Docker가 있다면? 귀찮게 그럴 필요 없이 쉽게 배포를 진행할 수 있다. 따라서, 실행하는 OS 환경에 상관없이, 언제나 같은 환경에서 결과를 낼 수 있도록 Docker Container화를 진행했다. Ruby On Rails나 AI서버의 Docker Container화는 다른 것과 별반 다를게 없는데, Spring Container화를 좀 특이하게 진행하였다. 일반적인 Docker 배포 방식으로 Spring 서버를 배포한다면, jar 파일이 무거워질수록 docker image를 만드는 과정이 비효율적으로 된다. 그런데, Docker의 장점이 레이어마다 캐쉬를 사용하고, 이를 통해 빠르게 이미지를 만들 수 있다는 것이다. 하지만, 해당 방식으로 진행하면 소스 코드가 한줄만 바꿔도 캐쉬가 깨지기 때문에 다시 연산을 해야되가지고, Docker를 사용하는 장점이 없어진다. 왜냐하면, Spring의 모든 애플리케이션 코드와 라이브러리가 Single layer에 배치되기 때문이다. 결국 컨테이너 환경에서의 시작 시간에도 영향을 미친다.
AWS EC2 인스턴스에 배포한다던지, 컴퓨터와 노트북을 번갈아가며 개발하다던지, 이러면 각각 개발 환경이 틀려 매번 설정을 해주어야 한다. 하지만 Docker가 있다면? 귀찮게 그럴 필요 없이 쉽게 배포를 진행할 수 있다. 따라서, 실행하는 OS 환경에 상관없이, 언제나 같은 환경에서 결과를 낼 수 있도록 Docker Container화를 진행했다. Ruby On Rails나 AI서버의 Docker Container화는 다른 것과 별반 다를게 없는데, Spring Container화를 좀 특이하게 진행하였다. 일반적인 Docker 배포 방식으로 Spring 서버를 배포한다면, jar 파일이 무거워질수록 docker image를 만드는 과정이 비효율적으로 된다. 그런데, Docker의 장점이 레이어마다 캐쉬를 사용하고, 이를 통해 빠르게 이미지를 만들 수 있다는 것이다. 하지만, 해당 방식으로 진행하면 소스 코드를 한줄만 바꿔도 캐쉬가 깨지기 때문에 다시 연산을 해야되서, Docker를 사용하는 장점이 없어진다. 왜냐하면, Spring의 모든 애플리케이션 코드와 라이브러리가 Single layer에 배치되기 때문이다. 결국 컨테이너 환경에서의 시작 시간에도 영향을 미친다.

그래서 Multistage Build와 Jar 파일의 특성 두가지를 고려해서 Dockerfile을 작성했다. 우선, 일반적인 방식으로 Docker Image를 생성시, 우리는 jar 파일만 필요한데 쓸데없는 소스 코드까지 다 포함하게 되어서 용량이 지나치게 커지고, 업로드하는데도 오래 걸리게 된다. 따라서, MultiStage Build를 이용하여 Build할 때만 필수적인 코드로 jar 파일을 생성하고, 최종 Docker image에는 이것들이 포함되지 않도록 설정했다.

Expand Down Expand Up @@ -137,15 +137,15 @@ Write 전략은 Write Around 전략을 활용했다. 데이터를 쓸 때, Redis

그런데 캐싱된 데이터를 받으면, class 정보에 대한 field가 json에 추가되는 현상이 있어서 추가 보류를 하였다.

또한, 이미지를 받아오는 S3도 캐싱을 무조건 해야한다. 아니면 S3 비용이 굉장히 많아질 것이고 응답시간이 길어진다. 그래서 AWS CloudFront를 S3 앞단에 두어 캐싱될 수 있도록 구성했다.
또한, 이미지를 받아오는 S3도 캐싱을 무조건 해야한다. 그렇지 않으면 S3 비용이 굉장히 많아질 것이고 응답시간이 길어진다. 그래서 AWS CloudFront를 S3 앞단에 두어 캐싱될 수 있도록 구성했다.

<br>

### **비동기 처리**

모든 요청을 동기적으로 처리하는 것은 매우 비효율적이다. 특히, 이미지를 업로드하거나, 번역 API 같은 다른 API에 요청을 보내거나, 크롤링 등은 매우 오래걸리는 작업이다. 이 모든 작업을 동기적으로 처리하면, 성능에 매우 치명적일 것이다. 그래서, 해당 기능들을 사용하는 경우에는 Async를 통한 비동기 처리를 실시하였다.

이 비동기처리를 위해서 Spring에서 ThreadExecutor를 이용한다. 그런데, 기본 Async Executor SimpleAsyncTaskExecutor인데, 이는 비동기 작업마다 새로운 스레드를 생성한다. 이로 인해 리소스 낭비, 성능 저하, 스케일링 문제 등이 발생할 수 있다. 왜냐하면 Thread Pool 방식 Executor가 아니라서 스레드 재사용을 하지 않기 때문이다. 그래서 실행시간이 짧은 많은 량의 Task를 처리할 때 불리하다. 리소스 측면에서는 각 비동기 작업마다 새로운 스레드를 생성하므로, 동시에 많은 비동기 작업이 요청되면 매번 많은 스레드가 생성된다. 따라서 CPU와 메모리 리소스의 사용량이 과도하게 증가하는 문제가 발생할 수 있다. 성능 저하 면에서는 스레드를 생성하고 소멸시키는 데는 많은 시간과 리소스가 소요된다. 각 작업마다 스레드를 생성하면 이런 오버헤드가 계속 발생하게 되고, 전체적인 시스템 성능에 영향을 끼칠 수 있다. 또한, SimpleAsyncTaskExecutor는 스레드 수에 대한 제한이 없다. 따라서 동시에 많은 요청이 들어올 경우 스레드 수가 무한정으로 제어할 수 없는 수준으로 증가할 수 있으며, 이는 곧 OutOfMemoryError 등의 문제를 일으킬 수 있다.
이 비동기처리를 위해서 Spring에서 ThreadExecutor를 이용한다. 그런데, 기본 Async Executor SimpleAsyncTaskExecutor인데, 이는 비동기 작업마다 새로운 스레드를 생성한다. 이로 인해 리소스 낭비, 성능 저하, 스케일링 문제 등이 발생할 수 있다. 왜냐하면 Thread Pool 방식 Executor가 아니라서 스레드 재사용을 하지 않기 때문이다. 그래서 실행시간이 짧은 많은 양의 Task를 처리할 때 불리하다. 리소스 측면에서는 각 비동기 작업마다 새로운 스레드를 생성하므로, 동시에 많은 비동기 작업이 요청되면 매번 많은 스레드가 생성된다. 따라서 CPU와 메모리 리소스의 사용량이 과도하게 증가하는 문제가 발생할 수 있다. 성능 저하 면에서는 스레드를 생성하고 소멸시키는 데는 많은 시간과 리소스가 소요된다. 각 작업마다 스레드를 생성하면 이런 오버헤드가 계속 발생하게 되고, 전체적인 시스템 성능에 영향을 끼칠 수 있다. 또한, SimpleAsyncTaskExecutor는 스레드 수에 대한 제한이 없다. 따라서 동시에 많은 요청이 들어올 경우 스레드 수가 무한정으로 제어할 수 없는 수준으로 증가할 수 있으며, 이는 곧 OutOfMemoryError 등의 문제를 일으킬 수 있다.

<img src="https://github.com/kookmin-sw/capstone-2024-30/assets/55117706/015fe505-6317-4994-8bcb-29b131bdda8c">

Expand Down Expand Up @@ -285,7 +285,7 @@ JWT Secret, HMAC Secret, API Key 등등을 우리 Code에 Hard Coding해서는

<img src="https://github.com/Alpha-e-Um/Frontend/assets/55117706/26a39b50-e5a9-45ca-87e5-c5f64aaefa5d">

기존 아키텍처는 위와 같이 구성했다. 하지만, 위의 아키텍처는 여러가지 문제점이 있었다. 우선, 우리 메인 Spring 비즈니스 서버는 다른거 처리하기도 바쁘다. 게다가, 저희 서비스는 채팅을 Long Polling으로 개발하였는데, 여러 사용자가 동시 다발적으로 요청을 보낸다? 그럼 메인 비즈니스 서버의 성능을 매우 떨어질 것이며, 응답시간은 길어질 것이다. 결국 Software Quality가 떨어질 것이다. 따라서 채팅 서버를 분리할 필요가 있다.
기존 아키텍처는 위와 같이 구성했다. 하지만, 위의 아키텍처는 여러가지 문제점이 있었다. 우선, 우리 메인 Spring 비즈니스 서버는 다른거 처리하기도 바쁘다. 게다가, 채팅을 Long Polling으로 개발하였는데, 여러 사용자가 동시 다발적으로 요청을 보낸다? 그럼 메인 비즈니스 서버의 성능을 매우 떨어질 것이며, 응답시간은 길어질 것이다. 결국 Software Quality가 떨어질 것이다. 따라서 채팅 서버를 분리할 필요가 있다.

뿐만 아니라, api 요청을 보낼때 https가 아닌 http로 보내기 때문에 데이터의 Integrity를 보장할 수 없다. 그래서 요청을 https로 암호화해서 보낼 필요가 있다.

Expand Down

0 comments on commit c6a6cb5

Please sign in to comment.