일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- fastapi
- kubernetes
- 도커
- 쿠버네티스
- Docker
- 컨설팅
- 생성형
- POD
- 메세지큐
- vue.js
- 머신러닝
- Python
- OpenShift
- BFS
- SpringBoot
- 컨설턴트
- 오픈시프트
- 로깅
- vuejs
- LeetCode
- GPT
- k8s
- 솔루션조사
- fast api
- Machine Learning
- 리트코드
- Redis
- LLaMa
- 생성형 AI
- jpa
- Today
- Total
수 많은 우문은 현답을 만든다
웹서비스 성능 향상 방법 본문
오늘은 성능 향상에 대해서 이야기를 해보자.
서론
한달만에 Big-data를 다루는 API 중계 솔루션을 개발했다.
데이터를 수집하는 멀티쓰레딩 배치가 10개정도 동시에 돌아가고, 수집된 데이터를 API로 제공하는 서비스이다.
문제는 API 응답 데이터가 가장 큰 녀석은 한번에 250MB를 보내고 있어서 성능 테스트에 어려움을 겪었다.
처음부터 큰 데이터를 다루기 위한 설계를 잘 했어야 하는데, 분산시스템 개발 경험은 있으나 이런 큰 단건 데이터 처리는 경험이 없어서 고생을 했다.
솔루션 기술 스택 :
- Fast API, Redis, Mongo DB
성능 요구 사항 :
- 400 TPS, 동시접속자 150,000명, 속도 건당 1초 이내
본론
우선 단건 데이터가 적은 API들은 성능 요구 사항을 만족했다. 그러나 단건 데이터가 큰 API들을 기준으로 성능테스트를 하겠다는 고객의 의사를 듣고 다급해졌다. 내가 할 수 있는 조치로는 S/W 적인 조치와 H/W 적인 조치가 있었다.
1) H/W 조치
- 스펙 향상
성능 향상 중의 으뜸은 하드웨어 스펙을 올려버리는 것 이다. 동시접속이 많아질수록 CPU와 Memory가 100%에 가까워 졌기 때문에 1차적으로 하드웨어를 아래와 같이 증설했다. 다만 하드웨어 증설은 비용이 스펙올 올릴때마다 2배가 들기 때문에 단계적으로 접근을 해야한다.- API 서버 : 4 core, 8 GB -> 8 core, 16 GB
- DB 서버 : 2 core, 4 GB -> 4 core, 8 GB
- ulimit 설정
서버의 user limitation을 조회해보니 (ulimit -n) 1000개로 설정되어 있었다. 리눅스에서 사용자가 열 수 있는 파일 디스크립터(file descriptor)의 최대 수 설정을 65536개로 설정해서 해결했다. 동시테스트 1000건을 넘어가면 Too many open files라는 오류와 함께 500:Internal Server Error가 발생했다.
2) S/W 조치
하드웨어를 늘려서 유휴 자원은 확보했으나 속도 개선은 크게 되지 않았다. 그래서 아래 절차대로 S/W적으로 할 수 있는 조치들을 수행했다.
- MongoDB 인덱스 설정
실제 Query를 보낼때 쓰이는 Key들을 MongoDB의 인덱스로 설정했다.
응답시간 개선 : 8s -> 5s - Redis 설정
요청에 자주 쓰이는 파라미터(또는 컬럼)들을 Key로 만들고 그 결과들을 Redis에 Key:Value 형태로 저장하고, 같은 요청이 올때 MongoDB를 조회하지 않고 Redis에서 조회하도록 한다. 그러면 Disk I/O를 줄일 수 있으니 성능 개선이 될 것이다. 다만 데이터가 크기 때문에 Redis 메모리 한계를 3GB정도로 늘렸고, 계속해서 메모리 모니터링을 하며 성능 테스트를 했다.
응답시간 개선 : 5s -> 4s - 커넥션 풀 조정
혹시 커넥션 수가 부족해서 성능이 안나오는 것을 아닐까 생각이 들었다. 찾아보니 파이썬 데이터베이스 라이브러리인 pymongo, motor는 기본적으로 커넥션 풀을 100개 관리하고 있었다. CPU가 8코어인데 커넥션 풀 100개도 많다고 생각이 들었고 1000개, 2000개로 늘려서 테스트 해봐도 소용은 없었다. - MongoDB 커넥션 수
몽고 디비의 커넥션 수를 보니, 요청이 1000개가 넘어가도 커넥션 수가 15개밖에 되지 않았다. 여기서 분명히 속도 저하가 발생하고 있다고 생각했다.
$ client_db> db.serverStatus().connections
{
current: 15,
available: 999985,
totalCreated: 38,
rejected: 0,
active: 6,
threaded: 15,
exhaustIsMaster: 0,
exhaustHello: 5,
awaitingTopologyChanges: 5
} - 워커 수 확인
어플리케이션의 커넥션 풀을 늘려도 MongoDB의 커넥션 수는 증가하지 않았다. 이럴때는 어플리케이션에서 디비로 보내는 요청 자체를 늘리는 방법이 있다. Java를 사용했다면 LB를 두고 여러개 서비스를 띄웠겠지만 Fast API는 실행시 worker 수를 늘리면 알아서 여러개 프로세스가 뜨면서 요청이 분산된다고 한다.
$ uvicorn main:app --reload --log-level debug --workers 8
1개의 워커를 CPU 개수에 맞게 8개로 늘렸더니 connection 수가 100개까지 늘었다. - 워커 수 증가
워커 프로세스의 수는 몇개까지 늘릴 수 있을까? 이론적으로 실행을 시키는데는 문제가 없으나 그만큼 파이썬 프로세스가 뜨기 때문에 리소스 부족 문제가 발생할 수 있다. 아래는 6 core 랩탑으로 테스트 한 결과이다.
worker를 24개 까지 늘렸을때 최대 TPS를 얻을 수 있었다.
그러나 그 이상 워커를 늘리면 응답 속도도 10배로 증가하고 TPS도 절반으로 줄어든 것을 확인할 수 있다.
이처럼 점진적인 테스트를 하면서 부하를 측정하는 것이 좋다. - 코어 수 증가
구글링을 해보면 core x 2배 수 정도의 worker를 쓰는게 효율적이라고 한다. 위 6번 스텝에서 직접 검증을 해봤으니 이제 코어 수를 늘려보자. 인프라를 아래와 같이 증설했다.
- API 서버 : 8 core, 16 GB -> 32 core, 64 GB
- DB 서버 : 4 core, 8 GB -> 8 core, 16 GB
- 부하 테스트
위와같이 코어수를 증가시키고 아래와 같이 부하 테스트를 진행했다. 다만 실제 성능을 요구하는 큰 데이터로 측정을 했기 때문에 6번에서 측정한것과는 속도가 많이 떨어짐을 감안해서 보자.
- worker 1개 : 1 tps
- worker 32개 : 5 tps
- worker 64개 : 16 tps (메모리 10%, 커넥션 450개)
- worker 128개 : 34 tps (메모리 30%, 커넥션 900개)
- worker 256개 : 42tps (메모리 70%, 커넥션 1800개)
이번에는 core x 4 배에서 성능이 가장 잘 나왔다.
256개 부터는 tps가 증가하기는 하나 메모리 증가율이 너무 비효율적이라서 사용하지 않는것이 좋겠다.
결론
성능 향상을 위해 S/W, H/W 적으로 노력을 해보았으나 TPS 기준을 맞추지는 못했다. 근원적으로 데이터 양이 너무 많기 때문이다. 이럴때는 현명하게 데이터를 압축해서 전달하거나 파일 인터페이스 방식을 사용하자. 실제로 파일을 압축해서 보내 보니 300TPS 이상 나왔고, 파일 인터페이스로 전송하는 네트워크를 타고 간단히 파일만 보내기 때문에 훨씬 빨랐다.
감사합니다.
'토이 프로젝트' 카테고리의 다른 글
메세지 큐 - 1. MQ 비교 및 설치 (2) | 2024.11.06 |
---|---|
API 서비스 개발(3) - API (0) | 2024.08.03 |
API 서비스 개발(2) - DB (0) | 2024.08.01 |
API 서비스 개발(1) - JWT (0) | 2024.07.29 |