|
| 1 | +| 최은소 (INFRA) | 오예린 (INFRA) | 권민정 (BE) | 선우예림 (BE) | 황지민 (BE) | |
| 2 | +| :---: | :---: | :---: | :---: | :---: | |
| 3 | +| <img src="https://avatars.githubusercontent.com/u/93801149?v=4" alt="최은소 프로필" width="180" height="180"> | <img src="https://avatars.githubusercontent.com/YelynnOh" alt="오예린 프로필" width="180" height="180"> | <img src="https://avatars.githubusercontent.com/u/145860909?v=4" alt="권민정 프로필" width="180" height="180"> | <img src="https://avatars.githubusercontent.com/u/54367532?v=4" alt="선우예림 프로필" width="180" height="180"> | <img src="https://avatars.githubusercontent.com/u/88023963?v=4" alt="황지민 프로필" width="180" height="180"> | |
| 4 | +| [esc-beep](https://github.com/esc-beep) | [YelynnOh](https://github.com/YelynnOh) | [mjttong](https://github.com/mjttong) | [yerimsw](https://github.com/yerimsw) | [Jimin-Hwang00](https://github.com/Jimin-Hwang00) | |
| 5 | + |
| 6 | +# 프로젝트 소개 |
| 7 | + |
| 8 | +## 파일 공유 시스템 **설계** |
| 9 | + |
| 10 | +- 파일을 업로드하고 링크를 통해 공유할 수 있는 기능을 구현한다. |
| 11 | +- 업로드된 파일의 메타데이터를 처리하여 별도로 저장한다. |
| 12 | + |
| 13 | +**요구 조건** |
| 14 | + |
| 15 | +- 매일 약 100-200개의 새로운 파일 업로드 예상 |
| 16 | +- 파일 당 평균 크기는 50MB 예상 |
| 17 | +- 최대 50GB의 파일 저장 용량 필요 |
| 18 | +- 파일 업/다운로드 지연 시간은 1분 이내 |
| 19 | + |
| 20 | +**심화 조건** |
| 21 | + |
| 22 | +> 심화 조건은 기본 요구 조건을 모두 만족한 후 생각해볼만한 추가적인 구현에 대한 내용입니다. |
| 23 | +> |
| 24 | +- 대용량 파일 전송: 최대 1GB 크기의 파일 공유 링크 생성 기능 |
| 25 | +- 접근 제어: 파일 및 폴더 단위의 세부적인 접근 권한 설정 기능 |
| 26 | +- 버전 관리: 주요 파일에 대한 최대 5개 버전 히스토리 관리 |
| 27 | +- 파일 검색: 파일명, 내용 기반 검색 기능 제공 (검색 결과 응답 시간 5초 이내) |
| 28 | + |
| 29 | +# 유저 시나리오 |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | +# 아키텍처 설계 |
| 34 | + |
| 35 | +## 발표 시점 |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +1. 전체 아키텍처 설명 |
| 40 | + 1. 전체 VPC에서 Public 서브넷과 Private 서브넷을 분리하여 사용자가 접근할 수 있는 부분과 DB를 저장할 수 있는 부분으로 아키텍처를 분리 |
| 41 | + 2. WAS 역할을 하는 EC2 인스턴스를 Public 서브넷에 배치, DB 역할을 하는 RDS 인스턴스를 Private Subnet에 배치함 |
| 42 | +2. IGW |
| 43 | + 1. 인터넷과 Public Subnet을 연결해주는 관문 역할을 하기 위해 배치 |
| 44 | +3. ALB |
| 45 | + 1. WAS의 부하를 분산 시킬 수 있는 용도로 ALB를 Public 서브넷에 배치함 |
| 46 | + 2. HTTP 8080 포트로 들어오는 요청에 대해 지정된 2개의 EC2 인스턴스로 트래픽 분산 |
| 47 | +4. EC2 |
| 48 | + 1. 도커를 이용해 컨테이너화 시킨 SpringBoot 서버(WAS)를 띄우기 위한 인스턴스 |
| 49 | + 2. 총 2개의 인스턴스가 ALB에 연결되어 트래픽을 분산하여 담당하고 있음 |
| 50 | +5. RDS |
| 51 | + 1. RDS Primary - RDS Read Replica 구조 확립 |
| 52 | + 1. 읽기 작업이 많은 프로젝트 요구 사항을 반영하기 위해 RDS Primary 이 외에 RDS Read Replica를 추가해 읽기 작업 퍼포먼스 향상 |
| 53 | + 2. RDS Primary와 RDS Read Replica를 서로 다른 가용 영역에 배치 |
| 54 | +6. Gateway endpoint |
| 55 | + 1. EC2 인스턴스와 VPC 외부에 위치한 S3를 연결하기 위해 활용 |
| 56 | + 2. 비용 효율성과 S3와의 호환성을 고려해 해당 서비스 선정 |
| 57 | +7. S3 |
| 58 | + 1. 파일 원본 저장소 |
| 59 | + 2. EC2와 Gateway Endpoint로 연결 되어 있음 |
| 60 | +8. Monitoring |
| 61 | + - 서비스 모니터링을 위해 CloudWatch와 CloudTrail 활용 |
| 62 | +9. CI/CD |
| 63 | + 1. CI/CD의 경우 아래의 단계를 거쳐 진행됨 |
| 64 | + 2. 개발자가 GitHub 레포지토리에 코드를 푸시함 |
| 65 | + 3. GitHub Action이 Docker Hub에 도커 이미지를 업로드하고 S3에 프로젝트 압축 파일을 업로드함 |
| 66 | + 4. 이후 Code Deploy는 S3에 게시된 압축 파일을 이용해 EC2 인스턴스에 배포 진행 |
| 67 | + |
| 68 | +## 추가 진행 시점 |
| 69 | + |
| 70 | + |
| 71 | + |
| 72 | +1. 전체 아키텍처 설명 |
| 73 | + 1. AWS VPC (10.0.0.0/16) 내에서 Public 서브넷과 Private 서브넷을 명확히 분리하여 보안성을 강화 |
| 74 | + 2. WAS 역할을 하는 EC2 인스턴스를 Private 서브넷에 배치하여 직접적인 외부 접근을 차단하며, DB 역할을 하는 RDS 인스턴스 역시 Private Subnet에 배치함 |
| 75 | +2. Route53 |
| 76 | + 1. `acc6-hotsix.shop` 도메인 사용 |
| 77 | +3. NAT |
| 78 | + 1. Private 서브넷의 EC2 인스턴스들이 인터넷과 통신할 수 있도록 NAT Gateway 배치 |
| 79 | +4. RDS |
| 80 | + 1. 해커톤 당시 Read Replica는 성능을 위한 서비스이지 가용성을 위한 것은 아니라는 피드백을 받음 |
| 81 | + 2. Multi-AZ 구성으로 설정하여 RDS의 고가용성 보장 |
| 82 | + 3. RDS Primary - RDS Read Replica 구조를 유지하여 읽기 작업 퍼포먼스 향상 |
| 83 | +5. S3 |
| 84 | + 1. S3 Glacier를 도입하여 장기 보관된 데이터를 비용 효율적으로 저장 |
| 85 | +6. EC2 |
| 86 | + 1. EC2 인스턴스를 private subnet으로 이동하여 보안 강화 |
| 87 | +7. Auto Scaling Group |
| 88 | + 1. 트래픽 변화에 따라 EC2 인스턴스의 수를 자동으로 조절 |
| 89 | + 2. EC2 인스턴스는 시작 템플릿을 통해 생성됨 |
| 90 | +8. AWS Session Manager |
| 91 | + 1. 관리자가 EC2 인스턴스에 SSH 키 없이 안전하게 접근할 수 있음 |
| 92 | +9. 네트워크 접근 제어 |
| 93 | + 1. Security Group을 통해 서브넷 안에서 인스턴스의 트래픽을 제어함 |
| 94 | + 2. NACL을 통해 서브넷에 오가는 트래픽을 제어함 |
| 95 | +10. CI/CD |
| 96 | + 1. 개발자가 GitHub 레포지토리에 코드를 푸시함 |
| 97 | + 2. GitHub Action이 Docker 이미지를 ECR에 배포 |
| 98 | + 3. 배포 스크립트를 S3에 업로드 |
| 99 | + 4. CodeDeploy를 통해 애플리케이션 자동 배포 |
| 100 | + |
| 101 | +## CI/CD |
| 102 | + |
| 103 | +- github action + Docker + ECR + Codedeploy를 사용한 방식 |
| 104 | + |
| 105 | +  |
| 106 | + |
| 107 | + - github action이 docker 이미지를 생성해 ECR에 업로드, codedeploy 스크립트를 생성해 S3에 업로드 |
| 108 | + - S3에 업로드한 스크립트를 codedeploy가 실행하도록 githubaction에서 트리거 전송 |
| 109 | + - codedeploy 스크립트 실행 후 배포 진행 |
| 110 | + |
| 111 | +# 서버 설계 |
| 112 | + |
| 113 | +## 사용한 기술 스택 |
| 114 | + |
| 115 | +- spring boot |
| 116 | +- mysql |
| 117 | + |
| 118 | +## ERD |
| 119 | + |
| 120 | +- 파일 메타데이터 저장 |
| 121 | +- 로그 저장 |
| 122 | + |
| 123 | +## 발표 이후 변화 |
| 124 | + |
| 125 | +- SDK v1에서 v2로 버전 업 |
| 126 | +- 기존의 multipart 및 정적 S3 URL를 사용한 업로드, 다운로드를 모두 presigned URL 방식으로 변경 |
| 127 | +- CI/CD 재구축 |
| 128 | +- 예외 처리 전략 변경 |
| 129 | + |
| 130 | +## 기능 명세서 |
| 131 | + |
| 132 | +| 기능 | 설명 | 예외처리 | |
| 133 | +| --- | --- | --- | |
| 134 | +| 파일 업로드 | - 업로드 불가능한 파일 타입: 실행 파일, 셸 스크립트, 자바 아카이브 파일 제외 |
| 135 | + |
| 136 | +- 파일 경로: ’/’로 시작, 알파벳, 숫자, 밑줄만 포함 | - 유효하지 않은 비밀번호 |
| 137 | +- 파일 중복: 같은 경로, 동일한 파일명 존재 | |
| 138 | +| 파일 다운로드 | - 파일 다운로드 | - 유효하지 않은 비밀번호 |
| 139 | +- 파일 없음 | |
| 140 | +| 파일 수정 | - 기존 파일과 동일한 파일 타입을 가진 파일로 수정 가능 |
| 141 | + |
| 142 | +- 수정 범위: 파일명, 파일 경로, 파일 | - 유효하지 않은 비밀번호 |
| 143 | +- 파일 없음 |
| 144 | +- 파일 중복: 같은 경로, 동일한 파일명 존재 |
| 145 | +- 파일 타입 변화: 기존 파일과 수정 파일의 타입이 다름 | |
| 146 | +| 파일 조회 |
| 147 | +(메타 데이터) | - 조회 결과: 파일 ID, 파일 이름, 파일 생성시간, 파일 확장자, 파일 경로, 수정일자, 원본 리소스 위치 | - 유효하지 않은 비밀번호 |
| 148 | +- 파일 없음 | |
| 149 | +| 파일 조회 |
| 150 | +(파일) | - 조회 결과: 파일 미리보기 | - 유효하지 않은 비밀번호 |
| 151 | +- 파일 없음 | |
| 152 | +| 파일 공유링크 생성 | - 공유 링크 생성: GET Presigned URL 생성 | - 유효하지 않은 비밀번호 |
| 153 | +- 파일 없음 | |
| 154 | +| 파일 삭제 | - 파일 삭제 | - 유효하지 않은 비밀번호 |
| 155 | +- 파일 없음 | |
| 156 | +| 파일 검색 | - 조회 결과: 파일 ID, 파일 이름, 파일 생성시간, 파일 확장자, 파일 경로 | - 조회 가능한 값 없음 |
| 157 | +- 검색조건 없음 | |
| 158 | +| 파일 조회 |
| 159 | +(페이지) | - 조회 결과: 파일 ID, 파일 이름, 파일 생성시간, 파일 확장자, 파일 경로 | - 조회 가능한 값 없음 |
| 160 | +- name, time 파라미터에 asc, desc 외의 다른 값 입력 | |
| 161 | +| 파일 조회 |
| 162 | +(전체) | - 조회 결과: 파일 ID, 파일 이름, 파일 생성시간, 파일 확장자, 파일 경로 |
| 163 | + |
| 164 | +- 파일 정렬: 파일 이름, 파일 생성 시간 오름차순 정렬 | - 조회 가능한 값 없음 | |
| 165 | +| 로그 조회 | - 로그 조회 | - 파일 없음 | |
| 166 | + |
| 167 | +## API |
| 168 | + |
| 169 | +| 기능명 | URL | Http Method | body | |
| 170 | +| --- | --- | --- | --- | |
| 171 | +| 파일 업로드 | /files | `POST` | file |
| 172 | +directory |
| 173 | +password | |
| 174 | +| 파일 다운로드 | /files/download/{file_id} | `POST` | password | |
| 175 | +| 파일 수정 | /files/{file_id} | `PATCH` | file |
| 176 | +directory |
| 177 | +password | |
| 178 | +| 파일 상세 조회(메타데이터) | /files/detail-meta/{file_id} | `POST` | password | |
| 179 | +| 파일 상세 조회(파일) | /files/detail-file/{file_id} | `POST` | password | |
| 180 | +| 파일 공유링크 | /files/share/{file_id} | `POST` | password | |
| 181 | +| 삭제 | /files/delete/{file_id} | `POST` | password | |
| 182 | +- presigned URL 방식으로 변경한 API |
| 183 | +- 모두 비밀번호가 필요한 API이며, 비밀번호를 form-data로 전달 |
| 184 | + |
| 185 | +| 기능명 | URL | Http Method | params | |
| 186 | +| --- | --- | --- | --- | |
| 187 | +| 전체 조회 | /files/all | `GET` | | |
| 188 | +| 조회(페이지 단위) | /files | `GET` | page |
| 189 | +name |
| 190 | +time | |
| 191 | +| 검색 | /files/search | `GET` | name |
| 192 | +path |
| 193 | +before, after |
| 194 | +type |
| 195 | +page | |
| 196 | +| 로그 조회 | /files/{file_id}/logs | `DELETE` | type |
| 197 | +page |
| 198 | +size | |
| 199 | +- 비밀번호가 필요없으며, 수정하지 않은 API |
| 200 | + |
| 201 | +# 부하 테스트 |
| 202 | + |
| 203 | +## 파일 업로드 테스트 |
| 204 | + |
| 205 | + |
| 206 | + |
| 207 | +- 5명의 사용자가 동시 접속 |
| 208 | +- 각 사용자는 3분 동안 계속해서 파일 업로드를 반복 실행 |
| 209 | +- 각 반복 사이에는 1초의 휴식(`sleep(1)`)이 있음 |
| 210 | +- 파일은 .txt(1MB), .pdf(50MB), .zip(100MB)로 구성되었으며, 실제로 S3에 업로드를 진행 |
| 211 | +- 3분 동안 이루어진 45개의 요청에 대해 100% 성공 |
| 212 | + |
| 213 | +## 파일 다운로드 테스트 |
| 214 | + |
| 215 | + |
| 216 | + |
| 217 | +- 2명의 사용자가 동시 접속 |
| 218 | +- 각 사용자는 3분 동안 계속해서 파일 다운로드를 반복 실행 |
| 219 | +- 각 반복 사이에는 1초의 휴식(`sleep(1)`)이 있음 |
| 220 | +- 파일은 .txt(1MB), .pdf(50MB), .zip(100MB)로 구성 |
| 221 | +- 3분 동안 이루어진 37개의 요청에 대해 75.67% 성공 |
| 222 | + |
| 223 | +## 파일 업로드/다운로드 동시 테스트 |
| 224 | + |
| 225 | + |
| 226 | + |
| 227 | +- 3명의 사용자가 동시 접속 |
| 228 | +- 각 사용자는 3분 동안 계속해서 파일 업로드/다운로드를 반복 실행 |
| 229 | +- 각 반복 사이에는 1초의 휴식(`sleep(1)`)이 있음 |
| 230 | +- 파일은 .txt(1MB), .pdf(50MB), .zip(100MB)로 구성 |
| 231 | +- 3분 동안 이루어진 27개의 요청에 대해 86.66% 성공 |
| 232 | + |
| 233 | +## 파일 검색 테스트 |
| 234 | + |
| 235 | + |
| 236 | + |
| 237 | +- 2명의 사용자가 동시 접속 |
| 238 | +- 각 사용자는 3분 동안 계속해서 검색 반복 실행 |
| 239 | +- 각 반복 사이에는 1초의 휴식(`sleep(1)`)이 있음 |
| 240 | +- 파일은 .txt(1MB), .pdf(50MB), .zip(100MB)로 구성 |
| 241 | +- 3분 동안 이루어진 351개의 요청에 대해 100% 성공 |
| 242 | + |
| 243 | +## 메타데이터 조회 테스트 |
| 244 | + |
| 245 | + |
| 246 | + |
| 247 | +- 1분 동안 사용자가 10명으로 증가 → 5분 동안 사용자가 10명으로 유지 → 1분 동안 사용자가 0명으로 감소 |
| 248 | +- 각 사용자는 계속해서 메타데이터 조회 반복 실행 |
| 249 | +- 각 반복 사이에는 1초의 휴식(`sleep(1)`)이 있음 |
| 250 | +- 파일은 .txt(1MB), .pdf(50MB), .zip(100MB)로 구성 |
| 251 | +- 7분 동안 이루어진 3133개의 요청에 대해 98.65% 성공 |
| 252 | + |
| 253 | +## 파일 조회 테스트 |
| 254 | + |
| 255 | + |
| 256 | + |
| 257 | +- 1분 동안 사용자가 5명으로 증가 → 5분 동안 사용자가 5명으로 유지 → 1분 동안 사용자가 0명으로 감소 |
| 258 | +- 각 사용자는 계속해서 파일 조회 반복 실행 |
| 259 | +- 각 반복 사이에는 1초의 휴식(`sleep(1)`)이 있음 |
| 260 | +- 파일은 .txt(1MB), .pdf(50MB), .zip(100MB)로 구성 |
| 261 | +- 7분 동안 이루어진 3133개의 요청에 대해 95.45% 성공 |
| 262 | + |
| 263 | +## 부하 테스트 정리 |
| 264 | + |
| 265 | +| | 파일 |
| 266 | +업로드 | 파일 |
| 267 | +다운로드 | 업로드/ |
| 268 | +다운로드 | 파일 검색 | 메타데이터 조회 | 파일 조회 | |
| 269 | +| --- | --- | --- | --- | --- | --- | --- | |
| 270 | +| 테스트 |
| 271 | +진행 시간 | 3분 | 3분 | 3분 | 3분 | 7분 | 7분 | |
| 272 | +| 평균 사용자 | 5명 | 2명 | 3명 | 2명 | 10명 | 10명 | |
| 273 | +| 총 발생 요청 | 45개 | 37개 | 27개 | 351개 | 3133개 | 3133개 | |
| 274 | +| 성공률 | 100% | 75.67% | 86.66% | 100% | 98.65% | 95.45% | |
| 275 | +- 매일 약 100-200개의 새로운 파일 업로드 예상 |
| 276 | +- 파일 당 평균 크기는 50MB 예상 |
| 277 | + |
| 278 | +상기된 조건을 충족시키는 것을 확인할 수 있음 |
0 commit comments