오늘 한 일
- Spring, JPA 엔티티 관계 맵핑 훈련용 MySelectShop 완성
- MySelectShop 엔티티 연관관계 및 사용 어노테이션 정리
- Docker, Git Actions, AWS EC2를 사용한 Server Deploy & CI/Cd PipeLine
- Docker, MySQL을 이용한 DB Server 생성
- Nginx를 사용한 8080포트 없이 서버 요청받아오기
- 메모리 스왑
- Log의 올바른 사용 방법(인프런 - 개발자에게 필요한 로그 관리/이준형(Foo) 강사님
- 고양이 집사 예지 튜터님과 CI/CD 및 MSA 설계 궁금증 해결 Spring, JPA 엔티티 관계 맵핑 훈련용 MySelectShop 완성
작업 내용
분명 다 완성했다고 생각하여 과제 제출을 누르려는 순간 나에게 보인건 자가진단표였다. 너 진짜 진짜 다했냐고 다시 물어보는 것 같은 나 혼자만의 압박감에 조용히 다시 프로젝트에 들어가 진단표에 있는 기능이 모두 잘 작동하는지 테스트해 보기 시작했다. 테스트코드를 모두 작성하고 실제 테스트까지 해보면 좋겠지만 테스트도중 MessageSource가 제대로 주입이 안 되는 문제가 발생해서 이 부분은 나중에 한 번 더 해결해보고 안되면 멘토님에게 물어보자 생각하고 실제 웹에서 테스트만 진행하였다.
아니나 다를까 폴더에 저장한 아이템들을 조회하는 기능이 빠져있었다... 자가 진단표로 한 번더 체크하게 해 준 스파르타에게 감사하며 다시 기능을 확인하고 만들어보았다. 분명 강의에는 이 기능은 넘어갔던 것 같은데 이상하다.
엔티티는 Product, Folder이고 관계는 M:N의 관계이다. 중간 테이블로 ProductFolder 테이블이 있고 Product, Folder는 ProductFolder와 1:M의 관계를 갖는다. 반대로 ProductFolder는 M:1이라는 말이고, 외래키는 ProductFolder가 갖고 있게 된다. 그래서 User가 갖고 있는 폴더의 id를 가져와 ProductFolder를 통해서 해당 폴더의 id를 갖고 있는 Product를 가져오려고 한다.
ProductFolder에 우선 사용할 필드를 만들었다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "folder_id", nullable = false)
private Folder folder;
다음은 폴더에 있는 프로덕트를 리스트로 반환해 줄 API를 Controller에서 호출받을 수 있게 Controller에 API를 만들었다.
GET /folders/{folderId}/products로 API를 만들고 {folderId}를 클라이언트에서 받을 수 있게 작성했다.
우리는 이 folderId값과 현재 로그인한 유저의 UserDetail의 정보를 갖고 테이블에서 정보를 찾아나가야 한다.
@GetMapping("/folders/{folderId}/products")
public List<ProductResponseDto> getProductsInFolder(
@PathVariable Long folderId,
@AuthenticationPrincipal UserDetailsImpl userDetails
) {
return folderService.getProductsInFolder(folderId, userDetails.getUser());
}
다음은 folderService의 getProductsInFolder를 보자. 단순 조회이기 때문에 Transactional은 readOnly로 설정해 줬다.
사용자가 사용하는 폴더를 찾아야 하기 때문에 folderRepository에 사용자 엔티티와 folderId값을 함께 넣어 두 값이 모두 일치하는 정보를 가져오도록 했다.
@Transactional(readOnly = true)
public List<ProductResponseDto> getProductsInFolder(Long folderId, User user) {
// 사용자 소유의 폴더인지 확인
Folder folder = folderRepository.findByIdAndUser(folderId, user)
.orElseThrow(() -> new IllegalArgumentException("해당 폴더를 찾을 수 없거나 접근 권한이 없습니다."));
첫 번째 메서드의 조회 결과로 아래 쿼리가 나가는 것을 볼 수 있다.
select
f1_0.id,
f1_0.name,
f1_0.user_id
from
folder f1_0
where
f1_0.id=?
and f1_0.user_id=?
다음 이어지는 코드들을 봐보자
반환하기 위한 Dto를 만들어서 가져온 folder의 ProductFolders의 데이터를 조회한 후 향상된 for문법으로 productFolder의 Product를 가져와 Dto 생성자에 넣어주는 코드이다.
이 코드를 통해 FolderId값에 대응되는 Product들이 responseDtoList에 삽입되었다.
// ProductFolder 엔티티를 통해 폴더에 속한 상품 조회
List<ProductResponseDto> responseDtoList = new ArrayList<>();
for (ProductFolder productFolder : folder.getProductFolders()) {
responseDtoList.add(new ProductResponseDto(productFolder.getProduct()));
}
return responseDtoList;
그 결과 아래와 같이 폴더별 조회를 성공할 수 있었다.
완성한 프로젝트 서버에 배포하기
이미 다른 포스팅을 통해 진행했던 내용은 넘어가겠다!
AWS EC2 서버를 개설하는 방법
서버 보안 설정
Docker 설치
Docker로 MySQL 설치
GitActions , Docker를 사용한 CI/CD 파이프라인 개설
기존에 포스팅하지 않았던 내용 중 하나는 우리가 흔히 붙이는 :8080 포트 없이 ip 또는 도메인만 적어도 바로 80 포트로 리다이렉션 하는 것이다.
강사님은 직접 커맨드를 입력하는 방법을 알려주셨지만 직접 커맨드를 적는 방식은 매우 위험한 방식이라고 생각하기에 번거롭더라도 Nginx를 설치하고 내부 config파일을 수정할 것이다.
Nginx는 실제 서버의 앞단에 존재하며 들어오는 요청에 대한 우회를 직접 설정할 수 있다. 이를 아마 리버스프록시라고 했던 것 같다.
간단히 명령어만 남겨보겠다.
sudo apt update && sudo apt install -y nginx #설치 패키지를 업데이트하고 nginx를 설치하며 모두 둥의
sudo vi /etc/nginx/sites-available/default # /etc/nginx/sites-available/default 파일을 편집
server {
listen 80;
server_name _;
location / {
proxy_pass http://localhost:8080; # Docker 컨테이너의 8080 포트로 연결
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
sudo systemctl restart nginx #재시작
이미 nginx가 같은 80 포트를 사용 중이라면 아래와 같이 Docker 실행 시 명령어 옵션으로 변경할 수도 있다.
docker run -d -p 80:8080 my-container
주소표시줄을 보면 :8080 포트 입력 없이도 80 포트로 잘 우회되는 것을 볼 수 있다.
메모리 스왑
위 설정들을 통해 순조롭게 mysql 서버와 springboot Application을 배포하였다고 생각했으나 문제가 발생했다.
아무리 EC2에 엔드포인트 또는 pem키를 사용해 접근하려 해도 접근이 되지 않는 것이다... 들어가더라도 정말 오랜 통신요청 끝에 들어가서 멈추는 것이 반복되었다.
이렇게 EC2의 상태에 대한 모니터링을 보았더니 CPU가 70% 이상 사용되고 있었고 free Tier를 사용하다 보니 1GB밖에 되지 않는 메모리 용량에 두 개의 컨테이너를 올려서 문제가 생긴 건 아닌가 싶어 스왑 메모리를 미리 설정하도록 했다.
스왑 메모리는 자주 사용되지 않는 일부 프로세서의 데이터를 보조기억장치에 저장하는 방식으로 알고 있다 이 방식에서는 페이징 또는 세그멘테이션 방식으로 수행된다. 새록새록 올라오는 내부/외부 단편화의 악몽...ㅎㅎ 정처기, 면접 연습할 때 나를 괴롭혔었던 개념들이다.
그래서 본론으로 돌아가 스왑 메모리를 설정하는 명령어를 남겨두려고 한다.
통상적으로 스왑 메모리는 내 메모리의 2배로 설정해둔다고 한다. 이 이유는 나중에 찾아보고 포스팅해 보겠다.
sudo swapoff -a # Swap 사용 중지
sudo rm /swapfile # 기존 4GB Swap 파일 삭제
sudo fallocate -l 2G /swapfile # 2GB 크기의 Swap 파일 생성
sudo chmod 600 /swapfile # 파일 권한 변경 (보안상 중요)
sudo mkswap /swapfile # Swap 영역으로 포맷
sudo swapon /swapfile # Swap 활성화
sudo vi /etc/fstab
/swapfile swap swap defaults 0 0 #해당 설정으로 재부팅되도록 유지되게 설정
free -h 명령어를 통해 Swap 메모리를 확인할 수 있다.
문제 해결
- JPA 프로젝트 CI/CD 배포 중 발생했던 나의 오타 난발...
- 프리티어로 인한 인스턴스 메모리 부족으로 인한 서버 접속 및 요청 지연(매우 많이)
- Value 포맷양식 오타
오늘 실수는 대부분 설정 파일에서 발생했던 나의 오타 문제였다.
GitHub에서 Secret에 설정한 변수의 오타, Value("${}") 포맷 양식 오탈자 입력등... 코드를 하나하나 천천히 읽고 원인을 파악하는 습관을 길러야 하는데 매번 성격만 급해지니 이 부분을 해결하기 위해 독서를 시작해볼까 한다. 최근에 읽은 책은 CleanCode인데 1달 전이라는 게 함정...
메모리 부족으로 스왑 메모리를 적용한 내용은 상단에 포스팅했으므로 추가로 내용을 적진 않겠다. 하지만 스왑 메모리뿐만 아니라 추가로 해결할 수 있는 방법이 있을 거라 생각하기에 더 좋은 방법을 찾는다면 추가로 포스팅할 예정이다.
멘토 상담 내용
내용은 다음과 같다.
1. 프로젝트를 Master bracnh에 Push 하면 자동으로 배포되는 CI/CD PipeLine의 소요시간을 최적화하는 방법, 배포한 스프링 애플리케이션을 셧다운 하고 새로 부팅하는 시간 최적화 방법에 대한 고민이었다. 튜터님에 대한 답변으로는 일단 Docker Image 경량화였다. 고정적으로 들어가는 라이브러리에 대한 영역은 수정할 수 없지만 코드 영역은 내가 직접 관여하기에 이 부분을 최적화하여 이미지에 대한 용량을 줄이고 더 빠르게 배포가 가능하다는 내용이었다. 또한 Graceful shutdown 이란 개념과 Layered Jar라는 개념을 처음 알게 되었다.
이 부분을 공부하면 Gradle과 Application의 shutdown방식에 대해 더 심층적으로 알게 되고, 사용자에게 불편함을 제공할 수 있는 요소를 더 감소시킬 수 있을 것 같다. 자세한 포스팅은 자세히 공부 및 개인 기록 후 포스팅 할 예정이다!
2. MSA환경에서 Error 처리에 대한 Application을 분리하게 되면 이 Error처리 서비스는 모놀리딕 아키텍처의 GlobalExceptionHandler와 동일한 역할을 하는가? 였다. 일단 답은 아니다! 였다. 각각의 서비스 애플리케이션은 별도의 GlobalExceptionHandler를 갖고 있으며 예외 발생에 대한 처리를 별도로 수행해야 하며, 그 이후 내용을 Error 애플리케이션으로 전달하는 것이었다. 이 용도를 생각하면 LogStash와 같은 엘라스틱서치의 기능인가? 생각이 들었는데 이 부분은 맞다! 였다. logstash 뿐만 아니라 다른 방식으로 NoSQL인 MongoDB, RDB 등을 사용해서 로그를 기록하고 관리하는 방식으로도 가능하다고 하셨다.
내가 직접 MSA 아키텍처를 구성해 봐야 더 깊은 이해와 경험이 생길 것 같아 여기까지 질문을 하고 마쳤다!
내일 할 일
1. Graceful Shutdown, Layered Jar 개념 정리하고 실습해 보기
2. MSA 강의 복습하고 개인적으로 실습해 보기
3. Logging 강의 듣고 내용 정리하기
'TIL' 카테고리의 다른 글
[TIL] MSA 각 서비스 모듈 자원 공유하기, Movie Service 개발 (0) | 2025.02.08 |
---|---|
[TIL] MSA Spring Cloud Eureka, Gateway 실습 (0) | 2025.02.06 |
[국비지원과정20] 자바 - 클래스 메서드와 인스턴스 메서드가 헷갈린다면? (1) | 2023.08.07 |
[국비지원과정19] JVM 구조 (0) | 2023.08.06 |
[국비지원과정18] JAVA - FOR문의 조건 (0) | 2023.05.23 |