티스토리 뷰

Chapter 01. 들어가며
1. 코딩을 할 수 있게 된 것일 뿐
1챕터는 사실 에필로그 같은 느낌이라 크게 요약할 내용은 없다.
하지만 개발자로 취업해서 일한 3년 동안 자주 겪은 문제들이 나와있어서 뜨끔했다. 지금 당장 필요한 에러들만 해결하는거 ,,
별 내용은 없지만 에필로그를 보고 느낀건, 이 책 나한테 진짜 도움 많이 될 것 같다는 생각을 했다
백앤드를 개발해도 애매하게 알면서 개발하는 사람들. 자바만 대충 알고 스프링부트만 얼버무리는 사람들
꾸준하게 화이팅~ 회사원 말고 개발자가 되장
Chapter 02. 느려진 서비스. 어디부터 봐야 할까
첫 마주한 챕터부터 내가 궁금해서 알아보던 주제다. 대용량 트래픽! 대박
🔍 처리량과 응답 시간
서버의 성능을 판단하는 다양한 지표 중 가장 중요한 응답 시간과 처리량에 대해 알아본다.
1. 응답 시간
- 응답 시간 ? 사용자의 요청을 처리하는 데 걸리는 시간
- 하나의 API 요청을 처리하는 과정
- API 요청(서버에 연결, 서버로 데이터 전송) → SQL실행, 응답 생성 등(DB 왔다리갔다리) → API 응답 (클라이언트로 데이터 전송)
- 응답시간 측정 기준
두 기준은 응답 데이터의 크기가 작으면 별로 차이 안나지만, 전송할 데이터가 크거나 네트워크 속도가 느리면 차이가 커질 수 있다.
서버 성능을 올바르게 평가하려면 데이터 특성이나 네트워크 환경을 고려해 적절한 지표를 선택해야 한다.- TTFB(Time to First Byte) : 응답 데이터 중 첫 번째 바이트가 도착할 때까지 걸린 시간
- TTLB(Time to Last Byte) : 응답 데이터의 마지막 바이트가 도착할 때까지 걸린 시간
- 서버의 처리 시간
- 로직 수행(if, for 등)
- DB 연동 ←←←←←←←←← 얘네 둘이 제일 많이 차지
- 외부 API 연동 ←←←←←← 둘만 해도 거의 80~95%까지 차지한다
- 응답 데이터 생성(전송)
2. 처리량
- 처리량 ? 단위 시간당 시스템이 처리하는 작업량
- 처리량 측정 기준
- TPS(transaction per second) : 초당 트랜잭션 수
- RPS(request per second) : 초당 요청 수
- TPS를 높일 수 있는 방법
- 서버가 동시에 처리할 수 있는 요청 수를 늘려 대기 시간 줄이기
- 처리 시간 자체를 줄여 대기 시간 줄이기
- TPS를 확인하는 방법
- 모니터링 시스템 활용
- 스카우터, 핀포인트, 뉴렐릭, ...
- 모니터링 시스템 활용
🔍 서버 성능 개선 기초
1. 병목지점 및 수직/수평 확장
TPS를 높이려면 먼저 모니터링 도구(또는 로그)를 사용하여 성능 문제가 발생하는 지점을 찾아야 한다.
성능 문제를 일으키는 원인을 찾았다면, 급한 불을 끄는 방법 중 하나인 수직확장 / 서버를 늘리는 방법인 수평 확장을 고려한다.
- 수직확장
- 수직확장 ? CPU, 메모리, 디스크 등의 자원을 증가시켜서 급한 불을 끈다.
- 더 빠른 CPU로 바꾸거나, CPU 코어수를 늘리기 / 메모리 확장 / 디스크를 HDD → SDD
- 수직확장은 즉각적인 효과를 바로 얻을 수 있지만 트래픽이 지속해서 증가하면 언젠가 결국 또다시 성능 문제가 발생한다.
비용이 많이 들기 때문에 매 번 수직확장을 반복할 수는 없다.
- 수평확장
- 수평확장 ? 서버를 추가로 투입해 TPS를 높인다.
- 그러나 무턱대고 서버를 추가하면 안된다.
DB에서 성능 문제가 발생하는데 서버를 추가로 투입하면 부하가 더 커지고 성능 문제는 악화될 수 있다.
→ DB나 외부 API에 성능 문제가 발생하지 않는 범위 내에서만 수평 확장을 해야 효과가 있다.
2. DB 커넥션풀
DB 커넥션 풀이란,
서버와 DB사이에 네트워크 연결을 생성하고 종료하는 데에만 해도 시간이 오래 걸리니, DB에 연결된 커넥션을 미리 생성해서 보관하는 것을 의미한다.
애플리케이션은 DB작업이 필요할 때 풀에서 커넥션을 가져와 사용하고, 작업이 끝나면 다시 풀에 반환한다.
커넥션 풀을 사용하면 이미 연결된 커넥션을 재사용하기 때문에 응답 시간이 줄어드는 장점이 있다.
※ Springboot는 HikariCP, Go언어는 자체 DB 커넥션 풀을 지원
커넥션 풀의 설정값들 중 중요 3가지는 아래와 같다.
- 커넥션 풀 크기 (또는 최소 크기, 최대 크기)
- 커넥션 풀에 미리 생성해 둘 커넥션 개수를 지정하는 설정
- DB 상태를 고려하여 커넥션 풀의 최소/최대 크기를 조절한다. (ex. 은행은 낮 시간 / 게임은 저녁 시간에 트래픽이 높음을 참고)
- 풀에 커넥션이 없을 때 커넥션을 구할 때까지 대기할 시간
- 풀에 사용할 수 있는 커넥션이 없을 때 커넥션을 얻기 위해 기다릴 수 있는 최대 시간을 의미
(※ 지정된 대기 시간 안에 커넥션을 구하지 못하면 DB 연결 실패 에러가 발생) - 대기시간을 짧게 설정하여 에러를 빠르게 응답하는 것도 방법임.
→ 대기 시간을 짧게 설정하면 서버 부하를 일정 수준으로 유지할 수 있으며 서버를 안정적으로 운영하는 데 도움이 된다.
- 풀에 사용할 수 있는 커넥션이 없을 때 커넥션을 얻기 위해 기다릴 수 있는 최대 시간을 의미
- 커넥션의 유지 시간 (최대 유휴 시간, 최대 유지 시간)
- 커넥션이 사용되지 않는 시간이 길어지면 연결이 끊길 수 있다. 이러한 에러를 방지하기 위해 최대유휴시간, 최대유지시간을 제공한다.
- 최대 유휴 시간 : 사용되지 않는 커넥션을 풀에 유지할 수 있는 최대 시간을 의미
- 유효성 검사 : 커넥션이 정상적으로 사용할 수 있는 상태인지 여부를 확인
- 최대 유지 시간 : 설정한 시간 동안만 커넥션이 유지된다. 설정 시간이 지나면 커넥션이 유효하더라도 커넥션을 닫고 풀에서 제거된다.
3. 서버 캐시
DB 서버를 확장하지 않고도 응답 시간과 처리량을 개선하고 싶다면 캐시 사용을 고려할 수 있다.
캐시는 일종의 (key, value) 쌍을 저장하는 Map과 같은 형태의 데이터 저장소다.
캐시에 데이터를 저장해두면 동일한 데이터를 요청할 때 DB가 아닌 캐시에서 데이터를 읽어와 응답시간을 줄일 수 있다.
캐시에서 키에 해당하는 값 조회 → (값 존재) → 값 사용
↘︎ (값 없음) → DB에서 값 조회 → (key, value)를 캐시에 저장 ↗︎
- 캐시의 적중률
적중률(hit rate) = 캐시에 존재한 건수/캐시에서 조회를 시도한 건수
→ 적중률이 높을수록 DB와의 연동이 줄어들고 곧 응답 시간 감소, 처리량 증가, DB 부하 감소로 이어진다.
- 캐시가 가득 찼을 때 삭제할 대상을 선택하는 규칙
- LRU (Least Recently Used) : 가장 오래전에 사용된 데이터를 제거한다.
- LFU (Least Frequently Used) : 가장 적게 사용된 데이터를 제거한다.
- FIFO (First In First Out) : 먼저 추가된 데이터를 먼저 삭제한다.
4. 로컬 캐시와 리모트 캐시
- 로컬 캐시 (In-memory Cache)
- 서버프로세스와 동일한 메모리를 캐시 저장소로 사용.
- Caffeine(자바), go-cache(Go), node-cache(Node.js)
- 장점
- 속도가 빠르다
- 별도의 외부 연동이 필요하지 않아 구조를 단순하게 유지할 수 있다.
- 단점
- 캐시에 저장할 수 있는 데이터 크기에 제한이 있다.
- 서버 프로세스를 재시작하면 메모리에 존재하던 캐시 데이터가 모두 삭제된다.
- 캐시에 보관할 데이터 규모가 작고 변경 빈도가 매우 낮다면 로컬 캐시로 충분 (ex. 홈 화면에 표시할 최신 공지글 목록)
- 리모트 캐시
- 별도 프로세스를 캐시 저장소로 사용.
- Redis
- 장점
- 데이터 규모를 수평 확장할 수 있다.
- 서버 프로세스가 재시작되더라도 저장되어있던 캐시 데이터는 그대로 유지된다.
- 단점
- 속도가 느리다.
(∵ 서버 프로세스 ↔︎ 캐시 프로세스 네트워크 통신) - 시스템 구조가 복잡해진다.
(∵ 별도의 서버 장비와 프로세스가 필요하기 때문)
- 속도가 느리다.
- 데이터 규모가 크다면 리모트 캐시 사용 (ex. 트래픽이 많은 대형 쇼핑 사이트의 개별 제품 정보)
- 배포 빈도가 높은 서비스 (∵ 캐시를 유지할 수 있으니까)
- 변경에 민감한 데이터
- 캐시 사전 적재
- 트래픽이 순간적으로 급증하는 패턴을 보인다면 캐시에 데이터를 미리 저장하는 것을 고려
- ex. 요금 안내 푸시 알림을 배포하기 전, 미리 데이터를 캐시에 저장
- 캐시 무효화
- 캐시에 보관된 데이터의 원본이 바뀌면, 캐시에 보관된 데이터도 함께 변경하거나 삭제되어야한다.
5. 가비지 컬렉터와 메모리 사용
가비지 컬렉터를 사용하는 언어는 사용이 끝난 객체를 힙 메모리에서 바로 삭제하지 않고 정해진 규칙에 따라 사용하지 않는 메모리를 찾아서 반환한다.
- 장점
- 개발자가 메모리를 직접 관리해야 하는 부담을 줄여준다.
- 코드로 메모리를 관리할 수 없기 때문에 보안 이슈를 줄여준다.
- 단점
- 응답 시간에 영향을 줄 수 있다 (ex. Stop-The-World : GC가 실행되는 동안 실행이 멈추는 현상)
대량으로 객체가 생성되는 것도 메모리가 부족할 수 있기 때문에 조회 범위를 제한하거나 스트림을 활용하여 방지해야 한다.
아래처럼 스트림을 활용하면 파일 처리 과정에서 필요한 메모리 크기를 줄일 수 있다.
// Stream 사용 X
byte[] bytes = Files.readAllBytes(Path.of("path")); //파일을 한 번에 메모리에 로딩
out.write(bytes)
// Stream 사용 O
InputStream is = Files.newInputStream(Path.of("path"));
byte[] buffer = new byte[8192]; // 8KB 메모리
int read;
while ((read = is.read(buffer, 0, 8192)) >= 0 {
out.write(buffer, 0, read);
}'Programming > BackEnd' 카테고리의 다른 글
| [Back] 토큰 기반 인증 시스템 JWT의 특징 + SpringBoot 적용 (2) | 2022.06.15 |
|---|---|
| [Back] Lombok 롬복 (JAVA) (3) | 2022.04.14 |
| [Web] 세션과 쿠키 (Session & Cookie) (0) | 2022.04.08 |
- Total
- Today
- Yesterday
- BOJ
- subset
- IMAGE
- cicd
- Retrofit2
- 아이템59
- dfs
- Java
- bruteforce
- 순열
- docker-compose
- Container
- springboot
- dp
- 이펙티브자바
- BFS
- 조합
- docker
- 백준
- 운영체제
- EffectiveJava
- DevOps
- OS
- 그래프탐색
- 토큰기반인증
- 완탐
- 알고리즘
- 완전탐색
- 아이템60
- 아이템61
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |
