업데이트 서버 고민 #2

햐얀하늘·2024년 8월 4일
0

파일 다운로드에 대한 고민

정답은 없다. 최선의 선택만 있을 뿐이다. 내가 선택한 것이 발생할 또 다른 문제도 꼬리물기식으로 생각하자

목표

최대 동시 20만명의 유저들에게 평균 100MB, 최대 500MB의 암호화된 데이터 파일을 동시에 전송해야한다.
총 8개 매번 주기적으로 다운로드가 필요한 암호화 데이터가 있다.

고려할 점

  • 20만명의 트래픽을 감당 할 수 있는가?
  • 속도가 빠른가?
  • 클라이언트의 사용성은?
  • 올바르게 다운로드가 되었는가?

해결방법

  • 네트워크 대역폭 조절

  • 로드밸런싱

  • 메시지큐(kafka, rabbitMQ)

  • 전송 데이터 타입 변경

  • 데이터 압축

  • 캐싱(Redis)

  • HTTP/2와 QUIC 프로토콜

1. 네트워크 대역폭 조절

많은 트래픽이 몰리면 각 사용자마다 가질 수 있는 네트워크 대역폭을 유동적으로 조절해 속도는 느리지만 안전하게 커버할 수 있는 사용자 폭을 넓히는 것

Lua가 지원되는 nginx를 이용해 현재 대역폭에서 들어오는 클라이언트에 맞춰서 대역폭을 유동적으로 할당 하는 방법이다.

네트워크 대역폭을 줄이면 속도가 떨어지게 되어 사용자의 불편함이 증가될 것이라고 생각한다.

그래서 이 방법은 크론탭을 사용해 클라이언트가 쉬는 새벽에 암호화 데이터를 받을 때 구현하면 좋을 것 같다.

그 외에 클라이언트가 사용하는 시간대에 해당 업데이트르 진행하게 되면 리소스를 많이 잡아먹을 것이고 클라이언트의 네트워크 속도가 저하되어 클라이언트가 다른 작업 시에도 속도 저하든 사용성이 저하 될 것

  • 구체적 방안 : Lua가 지원되는 Nginx 배포판을 사용해 대역폭을 조절할 수 있다. ex) 현재 대역폭 100 MB일때 기존에는 100MB가 제공되나 100명의 유저가 동시에 몰릴 때는 1인당 1MB로 할당하는 방법

2. 로드 벨런싱

이전에 썼던 1번과 비슷하다. docker로 동일한 인스턴스를 생성하고 해당 Nginx로 proxy_pass로 트래픽에 따라 nginx에서 바쁘지 않는 인스턴스로 해당 트래픽을 넘겨주는 것이다.

이때 로드 벨런싱을 구현하면 문제가 하나 발생한다. nestjs에서 scheduler를 이용해 주기적으로 돌아가게 만들었는데 로드밸런싱으로 동일한 이미지를 이용해 컨테이너 실행하면 4개의 인스턴스에서 동일한 api를 통해 반복작업을 해 불필요한 리소스 잡아먹게 될 것이다.

그래서 고려한 방법은 2가지이다.

  1. main, leader container를 설정하고 해당 leader container만 스케쥴러를 실행시킨다. -> redis를 이용해 리더 선출
  2. nestjs 내부가 아니라 kubernetics 에서 crontab 동작시켜서 외부에서 주기적으로 api를 실행하도록 한다.

3. 메시지큐(kafka, rabbitMQ)

1순위 로드벨런싱으로 해결이 되지 않으면 고려한 것이 바로 Kafka를 활용해 암호화파일을 다운로드 하는 것이다.

예를들어 업데이트 서버에서 새롭게 파일을 다운받으면 kafka의 메시지 큐에 암호화된 데이터 파일을 chunk로 분할해서 저장해 뒀을때 consumer(client) 측에서는 큐에 publisher가 보낸 데이터를 전송해서 클라이언트 측에서 해당 메시지를 조합해서 백그라운드로 다운로드 받는 방식이다.

consumer(client) 측에서는 미리 파일 다운로드 완료되었다는 문구를 전송해 보여주고 사용자는 파일 다운이 완료되었다고 느끼고 다시 그 사이에 백그라운드로 파일을 다운로드 완료하는 것이다.

이를 통해 nestjs 서버에서 모든 큰 데이터를 하나의 유저마다 전송할 필요없이 개인이 kafka 서버에서 비동기적으로 다운받는것이다.

4. 전송 데이터 타입 변경

원래는 파일을 암호화시켜서 전송시키는 방식인데 json 데이터로 전송하기전에 파일을 만들어서 파일을 암호화하는 방식으로 이루어져있다. 그래서 json데이터를 굳이 파일로 만들어 암호화해서 전달하는 것이 아니라 json데이터 그대로 암호화하고 직접 다운로드 하는 방식에서만 파일을 만들어 암호화해서 다운로드 받도록 설계했다.

5. 데이터 압축

단순히 데이터를 넘기는 것이 아니라 암호화된 버퍼 데이터를 압축해서 사용자에게 전달하는 방식이다. 파일의 크기가 줄어들어 빠른 전송이 가능하고 많은 트래픽이 몰려도 대규모 파일 전송보다 안정적이다.

다만 클라이언트 측애서 복호화하는 등 클라이언트의 기능이 더 많아 질 수 있다.

6. 캐싱(Redis)

모든 클라이언트가 주기적으로 요청하기 때문에 동일한 데이터를 여러번 호출할 것이다. 그렇기 떄문에 매번 새롭게 DB에서 가져와서 저장하는 것이 아니라 Redis에 저장해 동일 요청 시 캐싱해서 데이터를 가져와 속도를 높이는 것이다.

  • ps) Redis를 활용해 메시지 큐잉과 비슷한 기능을 할 수 있다. 기존 RabbitMQ로 구현하고자 했던 알림시스템을 pub/sub, sse를 이용해 메시징 시스템을 구현해 볼 수도 있다.

7. http2/QUIC 프로토콜

기존 http1 세대보다 http2를 이용해 더욱 빠르고 안전하게 데이터를 전송해 볼 수 도 있다.

profile
나는 커서 개발자가 될거야!

0개의 댓글