[storage-7] 서버는 안 터지는데, 업로드가 느려졌다

standardChan·2026년 3월 20일
post-thumbnail

쓰기 제어의 필요성

업로드 요청의 쓰기 속도 보장

이전 서버 최적화 작업으로 굉장히 많은 수의 업로드 요청을 한번에 처리할 수 있게 만들었습니다. 이제 5MB 파일의 경우 100명을, 100MB파일의 경우에는 60명도 서버 종료 없이, 안정적으로 처리할 수 있습니다.

(이전 기록 https://velog.io/@standard-chan/storage-5-부하-테스트-중-서버가-터졌다-원인-분석과-해결-및-삽질-기록)

하지만, 많은 요청을 한번에 받으면, 업로드 속도에서 문제가 발생하게 됩니다. 실제로 5MB파일을 100명이 올리게 될 경우, 60초가 넘어가는 경우가 많아서 TIMEOUT이 많이 발생하게 됩니다.

아래는 과도하게 동시 업로드 시를 진행하였을때, DISK의 I/O 그래프입니다.

DISK IO

DISK I/O 속도의 최대값이 약 140MB/s 넘지 못하는 것을 확인할 수 있습니다 (실제 Azure의 DISK 제한은 100MB/s인데, 40정도 더 나온 것 같습니다). 이로 인해서 DISK 대기큐도 많이 증가하게 됩니다.

DISK QUEUE

따라서 이러한 속도 저하 문제를 방지하기 위해서, 쓰기를 제한하는 정책이 필요하다고 생각하였습니다.

쓰기를 제어하여 업로드 속도를 보장하는 방법을 찾아보고, 이를 적용하는 것을 목표로 하였습니다.


2. 어떻게 쓰기를 제어할까?

가장 무난한 방법으로는 업로드 요청의 개수를 기준으로 잘라내는 방법이 있습니다.
이보다 더 정확하게 제어하려면, 업로드 데이터 흐름의 속도를 기준으로 판단하는 방법도 있을 수 있습니다.

2.1 업로드 요청 수로 제한하기

이 방법은 한계 점이 분명합니다.

효율적이지 못하다

1MB 업로드 10개 요청과, 100MB 업로드 10개 요청은 다릅니다. 1MB의 경우 100개를 받아도 빠르게 처리할 수 있는데, 10개로 제한해버리면 90개의 요청을 처리하지 못한다는 문제가 있습니다.

이 방식으로 구현한다면 정말 간단하겠지만, 서버의 자원을 효율적으로 사용할 수는 없을 것 같습니다. 그래서 다른 방법을 고민해보았습니다.

2.2 데이터 흐름의 속도로 제한하기

데이터가 DISK까지 흘러가는 속도를 기준으로 제한한다면, 1MB같은 작은 요청은 더 많이 수용할 수 있을 것이고, 큰 요청은 적게 수용할 수 있을 것입니다.

예를 들어 각 요청당 5MB/s의 하한을 두고, 모든 요청이 해당 속도 이하가 되어버리면, 더 이상 요청을 받지 않는 방법이 있을 것 같습니다.

이렇게 되면 자원을 효율적으로 사용할 수 있습니다.

하지만 한가지 어려움이 있었습니다.

어떤 수치를 기준으로 흐름을 판단해야할지 설정하기가 어렵다.

데이터들의 흐름 속도가 DISK 병목으로 느려진 것인지, 네트워크가 느려서 느려진 건지 판단하기가 어렵습니다.

더구나 이렇게 측정한 수치 측정 시간의 기준을 1s 로 할건지, 2s로 할건지의 시간문제부터, 그 값의 평균으로 할지 등의 계산 방식의 문제도 고려가 필요할 것 같습니다. 이러한 값을 추측하기 어려울 뿐더라 구현 외적으로 복잡도가 많이 크다고 생각하여 다른 방법을 생각해보았습니다.

2.3 업로드 요청 수 + 요청 파일 크기로 제한하기

모든 요청이 동일하지 않다면, 요청 별로 제한을 다르게 하자!

업로드 요청에 들어가는 파일의 크기를 바탕으로 가중치를 부여하고, 이를 바탕으로 요청 수를 제한하는 방법입니다.

예를 들어서, 다음 처럼 각 파일에 가중치를 둬서, 100을 넘어가면 더이상 요청을 받지 않는 방법입니다.

  • 5MB 미만인 파일 : 1 가중치
  • 5MB 이상인 파일 : 5 가중치

만약 3MB 파일 20개, 5MB 파일 5개라고 한다면, 총 45가 되는 것이고, 5MB파일 20개를 처리중이라고 하면 100으로 더 이상 요청을 받지 않는 것입니다.

이렇게 한다면 업로드 시, 서버 자원도 효율적으로 사용하고, 쓰기 속도도 보장할 수 있겠다고 판단하였습니다.


3. 가중치 및 속도 설계하기

위 5MB의 가중치 기준은 예시일 뿐이고, 어떻게 가중치를 둬서, 어떻게 속도를 보장할 지를 고민해보았습니다.

3.1 속도 측정하기

현재 Azure의 DISK I/O 속도가 100MB/s 로 되어있습니다. 실제로 이 수치가 나오는지 확인을 해야겠다고 생각을 하였습니다. 위 수치는 이론상 수치일 뿐이고 실제로는 모르기 때문입니다.

이 속도를 측정한 이후로, 몇 개의 요청을 받을지를 추측하면 될 것이라고 생각했습니다.

3.1.1 세션 업로드 속도 측정하기

세션 1개의 업로드 속도가 어느정도 나오는지를 측정했습니다. 이론상 DISK I/O가 100MB/s이니, 그와 비슷하게 나올 것 같습니다.

dirty page 메모리 때문에, DISK I/O 속도 100MB/s 보다 빠른 수치가 나올 수 있습니다. 따라서 3m + dirty 메모리 128MB로 설정하여 진행하였습니다.

# dirty page 메모리 설정하기
sudo sysctl -w vm.dirty_bytes=134217728

dirty page 메모리

DISK 쓰기가 100MB/s이므로 dirty 제한을 128MB로 설정하였습니다.

파일 크기동시 업로드 수요청 당 업로드 속도총 업로드 속도
10MB10개11MB/s110MB/s
100MB1개71MB/s71MB/s
100MB10개8.1MB/s81MB/s
500MB1개75MB/s75MB/s
500MB2개48MB/s96MB/s
1GB1개78MB/s78MB/s

3.1.3 결과 분석하기

10MB의 업로드 속도가 유독 빠른 이유는 page cache 크기가 파일 크기보다 크기 때문에 그런 것 같습니다.

해당 수치를 제외하고 보면, 업로드 속도가 100MB/s에 수렴하는 것 같습니다. 이를 통해 대충 90MB/s에 근접한 속도를 갖는다는 것을 확인할 수 있었습니다.

(여담이지만, 동시 업로드 수가 많아질수록 총 업로드 속도가 높아지는 것을 확인할 수 있는데, buffer에 데이터를 미리 받아오기 때문이 아닐까… 라고 생각해봅니다.)

3.2 가중치 설정하기

업로드 속도의 하한선은 5MB/s로 설정한다고 하겠습니다.

90MB/s를 이상적으로 나눈다고 하면, 18개의 요청으로 제한해야합니다. 다만, 모든 요청이 고르게 분배되지 않을 수도 있습니다. 예를 들어, 1MB 파일의 경우에는 5MB/s를 모두 사용하지 않기 때문에, 1MB파일 17개와 100MB 파일 1개의 요청이 들어온다면 고르게 분배되지 않을 것입니다.

따라서 5MB를 기준으로 5MB 이하의 파일은 파일 크기의 정수값으로 설정하고, 5MB 이상의 파일들은 가중치 6로 설정하는게 좋겠다고 판단하였습니다.

또한 100MB/s 를 파일 업로드 작업 뿐만 아니라, DB 쓰기, 파일 읽기 등에도 사용되어야하기 때문에 여유롭게, 제한을 70으로 설정하는 것이 좋다고 생각하였습니다. 그래야 다른 작업들이 들어와도 5MB/s를 보장할 수 있다고 생각했기 때문입니다.

3.3 테스트를 통해, 업로드 속도 직접 확인하기

일반적인 상황을 가정하기 위해서, 1MB ~ 100MB 까지의 파일을 바탕으로 테스트를 진행하였습니다. 동시 업로드의 수(VUs)는 20과 30으로 진행하였습니다.

VUs 20명 기준: 업로드 속도 비교

파일 크기개선 전 속도개선 후 속도향상률
1 MB0.55 MB/s0.50 MB/s(소폭 감소)
10 MB2.75 MB/s3.05 MB/s약 11% ↑
50 MB3.41 MB/s4.59 MB/s약 35% ↑
100 MB3.66 MB/s5.04 MB/s약 38% ↑
성공률100%46%약 46%

VUs 30명 기준 : 업로드 속도 비교

파일 크기개선 전 속도개선 후 속도향상률
1 MB0.41 MB/s0.58 MB/s약 41% ↑
10 MB1.68 MB/s3.01 MB/s약 79% ↑
50 MB1.89 MB/s4.69 MB/s약 148% ↑
100 MB2.11 MB/s5.19 MB/s약 146% ↑
성공률99%39%약 61%

4. 결과 분석

파일 크기VUs = 20VUs = 30속도 차이
1 MB0.50 MB/s0.58 MB/s약 16% ↑
10 MB3.05 MB/s3.01 MB/s약 1% ↓ (동등 수준)
50 MB4.59 MB/s4.69 MB/s약 2.2% ↑
100 MB5.04 MB/s5.19 MB/s약 3% ↑

VUs가 20명일 때와 30명일 때, 큰 차이가 없는 것을 확인할 수 있었습니다.

이는 랜덤 파일용량 업로드라는 점을 고려한다면, 큰 차이가 없다고 생각하였습니다. 따라서 동시 업로드 수가 아무리 많아도 위 속도를 유지할 수 있다고 생각하였습니다.

혹시나 해서, VUs = 50 의 경우에도 테스트를 해보았습니다.

파일 크기VUs=50 속도 (MB/s)
1 MB약 0.59 MB/s
10 MB약 3.39 MB/s
50 MB약 4.38 MB/s
100 MB약 5.07 MB/s

이 역시 VUs 20, 30과 큰 차이가 없음을 확인할 수 있었습니다.

결과적으로는 요청이 아무리 많아도, 항상 유사한 속도를 보장할 수 있다는 걸 확인할 수 있었습니다.이로써 초기 목표였던, 과도한 동시 요청 시, 속도가 저하되는 문제를 막을 수 있게 되었습니다. 속도를 항상 5MB/s와 유사하게 보장할 수 있도록 구조를 만들었습니다.

다만 개수제한으로 거절을 하는 방식이다보니, 성공률 자체는 낮다는 아쉬움이 있습니다.


5. 아쉬운 점

아직 성공률에 있어서 한계가 많습니다. 즉, 업로드를 보냈는데 바로 거절 당해버리는 경우가 많습니다.

물론 업로드 요청이 연결만 되면, 빠르게 처리할 수 있지만, 업로드 요청이 많은 상황이라면 업로드 연결을 하는 것 자체가 어렵다는 문제가 있습니다.
뿐만 아니라, 만약 가중치가 높은 업로드 요청들이 네트워크 이슈로 세션을 계속 차지하고 있다면, 다른 업로드 요청들이 진입을 못하는 문제도 상상할 수 있겠습니다.

따라서 이 부분에 있어서 개선이 필요할 것 같습니다. 지금 당장 떠오르는 해결방안으로는

과하게 오랜 시간 동안 업로드를 하는 경우에는 동적으로 가중치 조절하거나, 연결 끊는 방법을 생각해볼 수 있습니다.


6. 롤백..? - 추가 (26.3.24)

실제로 사용을 해보면서, 문득 이런 의문이 생겼습니다.

내가 사용자라면 아예 거절당하는 것보다, 속도가 느리더라도 업로드 요청이 전달되기를 바랄 것 같은데…

지금 방식이, 먼저 업로드한 사용자의 속도를 유지하고, DISK 부하를 줄이겠다는 취지에서는 좋은데, 이게 과연 사용자 전체의 관점에서 봤을 땐 좋은 선택이었을까 하는 의문입니다.

반대로 먼저 업로드한 사용자의 관점에서는 어떨까 생각해봤습니다.

‘내가 먼저 업로드 했으니! 5MB/s 업로드 속도가 나오는게 맞아!’ 라고 생각할까?

물론 너무 길게 소요된다면 불편함이 있을 것 같지만, 5MB/s든 3MB/s든 크게 신경 안쓸 것 같다고 생각합니다. 그저 업로드만 완료하면 되니까. (물론 당연히 빨리되면 좋다)

즉, 사용자의 관점에서는 ‘빠른 업로드의 속도가 보장되면 좋지만, 업로드가 되고 있느냐 아니냐’가 중요할 것 같다고 생각한다.

그럼 최선은 어떻게…?

롤백…? 보다는 한계를 느슨하게

아무리 업로드가 되고 있는게 중요하다고 한들, 업로드 속도가 1~2MB/s 미만이 나오게 된다면, 업로드 경험이 썩 좋지는 못할 것이다. (1GB 파일을 올리는데만 15분 이상이 걸리면, 사용을 하더라도 불쾌할 것 같다.) 즉, 제한을 아예 없애는 것은 좋은 방법이 아니라고 생각한다.

그래서 한계를 느슨하게 잡으려고 한다. 가능한 최대로 수용하되, 과하면 차단하려고 한다

가중치 수정하기

대충 90MB/s IO 속도를 갖으므로, 업로드 외의 IO작업을 생각하면, 60-70개의 요청 개수로 한계를 설정하면 1~2MB/s로 여유롭게 처리할 수 있을 것 같다.

그러면 2MB 이상인 파일에 대해서 가중치를 2로 설정하고, 그 외의 파일은 1로 설정하기로 하였다. 그리고 상한선은 70으로 하자.

이론적으로는 최저 35개 ~ 최대 70개의 동시 요청까지 수용할 수 있다.
VUs=35로 테스트를 진행하였고, 총 301개의 요청을 100%로 처리할 수 있었다. 속도도 예상치와 유사하게 나왔다.

파일 크기평균 소요 시간 (avg)초당 전송 속도 (Mbps)초당 전송 속도 (MB/s)
1MB1,835.89ms4.36 Mbps0.54 MB/s
10MB5,853.85ms13.67 Mbps1.71 MB/s
50MB27,915.33ms14.33 Mbps1.79 MB/s
100MB51,282.62ms15.60 Mbps1.95 MB/s

아쉬운 점

처리 요청 수를 늘리면, 결국 속도가 느려진다. 즉, 두 요소 모두 확보하기가 불가능에 가깝다.

결국 이 방식의 근본적인 한계는 모든 요청이 동일한 대역폭을 나눠 갖는다는 점이다. 10MB짜리 파일은 사실 금방 끝날 수 있는데, 거의 5초가량 대기해야한다.
1MB ~ 10MB 파일(카메라 이미지, pdf파일 등)이 보통 가장 많이 업로드되기 때문에, 이러한 파일에 우선권을 주는게 좋다고 생각한다.

그렇다면 처리 요청 수를 최대한 많이 유지하면서도, 작은 파일에게 더 많은 대역폭을 우선적으로 주는 방법은 없을까?

이 부분에 대해서 고민을 해봐야겠다.

profile
호기심 많은 개발자

0개의 댓글