리서치

[tus protocol] 재개 가능한 파일 업로드를 위한 오픈 프로토콜

꾸준함. 2022. 10. 6. 23:40

개요

개발 진행 중인 프로젝트에서 대용량 파일 업로드 기능을 구현해야 하는데 요구사항은 아래와 같았습니다.

  • 대용량 파일을 브라우저에서 서버로 한 번에 업로드할 경우 OOM(Out of Memory Exception)이 발생할 수 있으므로 파일을 청크 단위로 끊어서 업로드할 수 있어야 함
  • 업로드하는 도중 네트워크 오류 혹은 휴먼 에러로 인해 업로드 요청이 끊기더라도 재요청 시 이어서 업로드할 수 있어야 함 (Resumable)

 

찾아본 결과 위에서 언급한 두 가지 요구사항을 충족하는 라이브러리가 있었고(tus-js-client, tus-java-client) 이 둘은 모두 tus protocol을 기반으로 구현이 되어 있었습니다.

따라서, 저는 이번 프로젝트에서 해당 라이브러리들을 적용하기로 했고 이번 게시글에서는 tus protocol 설명, tus protocol을 적용 유무에 따른 성능 비교, 그리고 샘플 코드를 공유하겠습니다. (자료들이 대부분 영어이므로 오역이 있을 수 있지만 너그럽게 봐주시길 바랍니다.)

 

1. Tus Protocol

tus는 재개 가능한 업로드를 위해 개발된 HTTP 기반 오픈 프로토콜이며 다양한 언어 및 플랫폼을 지원하는 것이 장점입니다.

제가 사용한 tus-js-client와 tus-java-client 이외에도 tusd와 같이 Go 언어를 위한 라이브러리,  tus-py-client와 같이 파이썬을 지원하는 라이브러리 등이 있습니다.

라이브러리 목록들은 아래 깃 레포지토리에서 확인 가능합니다.

https://github.com/orgs/tus/repositories

 

tus - Resumable File Uploads

tus - Resumable File Uploads has 14 repositories available. Follow their code on GitHub.

github.com

 

Tus Protocol의 특징으로는 아래와 같이 크게 6가지가 있습니다.

  • HTTP 기반 프로토콜
    • HTTP 기반 프로토콜이기 때문에 기존에 사용하던 라이브러리, 프록시 및 방화벽을 이용하여 애플리케이션에 쉽게 통합할 수 있으며 모든 웹 사이트에 직접 적용 가능

 

  • 검증된 프로토콜
    • Vimeo, Google과 같이 유명 기업에서 재직 중인 개발자들로부터 피드백을 받으며 수많은 개선 과정을 거쳐왔기 때문에 상용 환경에서 사용해도 무방

 

  • 오픈 소스 기반 프로토콜
    • Tus는 Transloadit에서 개발했지만 모든 소스코드가 깃헙에 공개되어있고 MIT License이기 때문에 모두가 사용 가능

 

  • 미니멀리즘 지향
    • 개발자 편의를 위해 클라이언트와 서버 내 직접 구현해야 하는 코드를 최소화
    • dependency만 추가하면 누구나 쉽게 사용 가능

 

  • 확장성
    • 병렬 업로드, md5 checksum 비교재개 업로드 만료 시간 적용과 같은 추가 기능 제공

 

  • 지속적인 피드백
    • 오픈소스 기반 프로토콜이기 때문에 issue를 통해 꾸준히 피드백을 받고 있으며 https://tus.io/ 웹사이트를 운영하며 개발 현황 꾸준히 공유
 

tus - resumable file uploads

 

tus.io

 

2. Tus Protocol 작동 방식

Tus Protocol은 HTTP POST, PATCH, 그리고 HEAD 메서드를 사용합니다.

따라서, 작동 방식을 이해하기 전에 해당 메서드들에 대한 사전 지식을 갖추고 있어야 합니다.

간단하게 요약하자면 POST 메서드는 데이터를 새로 생성하고 싶을 때, PATCH 메서드는 데이터를 부분 수정하고 싶을 때, 그리고 HEAD 메서드는 GET 요청의 헤더 내용만 가져오고 싶을 때 사용됩니다.

자세한 내용은 아래 링크를 참고해주세요.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods

 

HTTP request methods - HTTP | MDN

HTTP defines a set of request methods to indicate the desired action to be performed for a given resource. Although they can also be nouns, these request methods are sometimes referred to as HTTP verbs. Each of them implements a different semantic, but som

developer.mozilla.org

 

다시 본론으로 돌아와서 예시를 통해 Tus Protocol 작동 방식을 설명하도록 하겠습니다.

 

2.1 HTTP HEAD 메서드를 통해 파일 생성 현황 파악

대용량 파일 업로드할 때 시간이 상대적으로 오래 걸리기 때문에 중간에 네트워크 오류가 발생하거나 브라우저를 이탈했을 경우 정상적으로 업로드되지 않을 수 있습니다.

따라서, tus protocol은 우선 HEAD 요청을 보내 파일의 현재 업로드 오프셋을 가져와 이미 업로드된 청크와 업로드할 청크를 확인합니다.

신규 파일 생성 요청이거나 파일 생성 요청을 한지 오래되었다면(expiration 설정 값을 넘어선 시간) HEAD 요청을 보냈을 때 404 에러가 발생하고 클라이언트에서는 2.2에서 설명할 HTTP POST 메서드를 통해 파일 신규 생성 요청을 진행합니다.

이미 업로드된 청크가 존재할 경우 HTTP POST 메서드를 건너뛰고 2.3에서 설명할 HTTP PATCH 메서드를 통해 이어서 업로드할 청크들을 업로드합니다.

신규 요청이기 때문에 404
기존 요청이 존재하기 때문에 200

 

2.2 HTTP POST 메서드를 통해 파일 생성 요청

두 번째 단계로 클라이언트에서 파일의 업로드 사이즈가 지정된 POST 요청을 서버로 전송합니다.

서버가 새 파일을 만들고 파일의 저장 장소를 응답 값으로 내려줍니다.

HEAD 요청이 404여야 POST 요청

 

위 사진을 보시면 Response Headers 내 Location 필드가 파일의 저장 장소를 의미하고 Upload-Expires 필드가 재개 업로드 만료 시간을 의미합니다.

그리고 Request Headers 내 Upload-Length 필드를 통해 업로드한 파일 사이즈를 확인할 수 있습니다.

 

2.3 HTTP PATCH 메서드를 통해 파일 업데이트

HTTP PATCH 메서드는 청크 단위로 분할된 파일을 업로드할 때 쓰입니다.

각 패치 요청에는 업로드 중인 파일 데이터의 현재 오프셋을 나타내는 Upload-Offset 필드가 포함되어 있습니다.

Response Headers 내 Upload-Offset 사이즈와 파일 사이즈를 비교해서 동일할 경우 파일 업로드 완료로 판단합니다.

 

5GB 파일을 업로드했을 때 마지막 PATCH 메서드

 

2.4 실제 시연 영상

아래는 제가 실제로 대용량 파일을 서버로 업로드했을 때 화면입니다.

 

tus protocol을 통해 resumable 업로드

 

도중에 새로고침을 한 뒤 재요청을 했을 때 처음부터 업로드하는 것이 아니라 이어서 업로드하는 것을 확인할 수 있습니다.

 

3. tus protocol 적용 유무에 따른 성능 비교

현재 프로젝트에 tus protocol을 적용해야하는 당위성을 파악하기 위해 저는 prometheus와 grafana를 통해 tus protocol 적용 유무에 따른 성능 비교를 진행했습니다.

prometheus와 grafana 관련 게시글은 아래 링크를 확인해주세요.

https://jaimemin.tistory.com/2188

 

[SpringBoot] Prometheus, Grafana 연동하는 방법

개요 기존에 엑셀 다운로드 기능을 Apache POI 라이브러리를 통해 구현했는데 메모리도 많이 먹는 것 같고 non streaming 방식이라 streaming 방식인 fastexcel 라이브러리로 변경하려고 합니다. 기존 코드

jaimemin.tistory.com

 

tus protocol은 앞서 언급한대로 tus-js-client와 tus-java-client 라이브러리를 사용했고 해당 프로토콜이 적용되지 않은 버전은 일반적인 방법인 form을 통해 업로드했습니다.

5GB 파일을 업로드했을 때, tus protocol이 적용되지 않은 버전에서 OOM이 발생하여 1GB 파일로 성능 비교를 진행했고 이에 따른 결과는 아래와 같습니다.

 

tus protocol을 통해 대용량 파일을 업로드했을 때 파일을 청크 단위로 분할하여 여러 번 업로드하기 때문에 HTTP 요청은 tus protocol을 적용한 버전이 훨씬 많이 요청된 것을 확인할 수 있었습니다.

또한, 청크 사이즈를 10MB로 적용했기 때문에 JVM Heap Memory는 전혀 증가하지 않은 것을 확인할 수 있었습니다.

 

반면, tus protocol을 적용하지 않은 버전은 단 한 번의 HTTP 요청에 의해 업로드되기 때문에 HTTP Rate은 변화가 없는 것을 확인할 수 있었지만 JVM Heap Memory가 확 증가한 것을 확인할 수 있었습니다. (여기서 포인트는 tus protocol이 적용된 버전은 파일 사이즈가 5GB인데도 불구하고 memory 증가가 없었던 것에 반해 적용되지 않은 버전은 파일 사이즈가 1GB인데도 불구하고 memory를 많이 잡아먹는 것이었습니다.)

CPU 사용량은 두 버전 모두 비슷한 추이를 나타낸 것을 확인할 수 있었습니다.

 

* tus protocol이 적용되지 않은 버전에서 5GB 파일을 올렸을 때는 아래와 같이 OOM 에러가 발생했습니다.

 

OOM

 

4. Tus Protocol 적용 샘플 코드

tus.io 홈페이지에 잘 작성이 되어있지만 자주 사용될 것 같은 기능 위주로 작성한 샘플 코드를 간단하게 작성해봤습니다.

전체 소스코드는 아래 깃헙 레파지토리에 올라와 있기 때문에 핵심적인 부분만 발췌해서 포스팅했습니다.

https://github.com/jaimemin/spring-tus-io-example-project

 

Config

 

 

 

Controller

 

 

 

Service

 

 

 

Client

 

 

 

Config 파일 내 tusStoragePath 변수는 업로드된 파일 오프셋 정보를 보관하는 장소이며 tusExpirationPeriod는 해당 정보를 몇 ms 동안 보관할지를 지정하는 변수입니다.

 

2022.10.18 업데이트

만료된 파일 데이터는 TusFileUploadService의 cleanup() 메서드를 통해 삭제가 가능합니다.

따라서 아래와 같이 scheduler를 expirationperiod에 맞게 설정하면 됩니다!


 

실제 tusStoragePath 경로로 가면 아래와 같이 메타데이터를 보관하고 있는 것을 확인할 수 있습니다. (여기서 uploads 디렉토리는 tus 라이브러리에서 지정한 이름이므로 서비스를 운영할 때 해당 디렉토리명과 동일한 디렉토리가 있을 경우 해당 디렉토리명을 변경하는 것을 추천드립니다.)

 

* 위 소스 코드는 아래 gif에서 확인할 수 있듯이 병렬 업로드도 지원합니다.

 

1GB 파일과 5GB 파일 동시 업로드

 

5.  정리

tus protocol을 적용했을 때 장점과 단점은 아래와 같이 명확합니다.

 

장점

  • 파일을 chunk 단위로 분할하여 HTTP PATCH 메서드를 통해 업로드하기 때문에 OOM 에러를 방지할 수 있음
  • 에러로 인해 업로드가 끊기더라도 설정한 만료 시간 내 재 업로드할 경우 이어서 업로드가 가능
  • HTTP 기반 프로토콜이기 때문에 다양한 언어 및 플랫폼 지원
  • MIT License이기 때문에 상용 서비스에서도 사용 가능하고 여러 개발자들로부터 꾸준한 피드백을 받고 있음

 

단점

  • 파일을 분할해서 업로드하기 때문에 상대적으로 시간이 더 오래 걸림
    • 위 문제는 메모리 이슈가 발생하지 않는 선에서 청크 단위를 보다 크게 잡는 방식으로 보완이 가능
  • 클라이언트와 서버 모두 tus protocol이 적용된 라이브러리가 적용되어 있어야 하는 dependency 이슈가 존재
  • 상용 환경에서는 보통 서버가 이중화되어있고 앞에 L4가 존재하여 Load Balancing을 수행하는데 별도 스토리지가 없을 경우 이슈가 발생할 수 있음
    • 샘플 코드에서 언급했던 것처럼 tusStoragePath를 설정해서 해당 경로에서 메타데이터를 불러와 파일 업로드 현황을 파악할 수 있는데 요청이 엇갈릴 경우 offset의 일부는 A 서버, offset의 다른 일부는 B 서버에 저장되어 있는 문제가 발생할 수 있음
    • 따라서, 별도 스토리지가 없을 경우 L4 Load Balancing을 Round Robin 방식이 아닌 IP Hashing 방식을 적용하여 client에서의 요청이 하나의 서버로 갈 수 있도록 설정해야 하는 제약 사항이 존재
    • 별도 스토리지가 존재할 경우 A 서버, B 서버 모두 해당 스토리지를 마운트 하거나 바라보므로 파일 저장 위치를 해당 스토리지 내 설정할 경우 위와 같은 문제 발생하지 않음
  • 보안 정책에 의해 HTTP GET/POST 메서드만 사용 가능한 회사들이 있는데 해당 프로토콜은 HEAD, PATCH 메서드도 사용하기 때문에 보안 정책팀에 사용 가능 여부 확인 필요

 

개인적으로 결론을 내리자면 tus protocol을 적용할 경우 일부 제약 사항이 발생하지만 메모리 측면에서 워낙 뛰어난 성능을 보이고 Resumable 업로드를 지원해 사용성이 높아 프로젝트에 tus protocol을 도입하는 것을 추천드립니다!

 

6. 참고  

https://tus.io/

 

tus - resumable file uploads

 

tus.io

https://golangbot.com/understanding-tus/

 

Understanding tus protocol - Resumable file uploader

This tutorial explains how tus protocol works and how it can be used to create a resumable file uploader in Go.

golangbot.com

https://golb.hplar.ch/2019/06/upload-with-tus.html

 

Reliable file uploads over HTTP with tus.io

In an older blog post, I wrote about the unreliability of file uploads over HTTP. It's by default an all or nothing operation where you have to send the file in one piece to the server and hope that during the transfer, the connection does not break. If th

golb.hplar.ch

https://github.com/tus/tus-js-client

 

GitHub - tus/tus-js-client: A pure JavaScript client for the tus resumable upload protocol

A pure JavaScript client for the tus resumable upload protocol - GitHub - tus/tus-js-client: A pure JavaScript client for the tus resumable upload protocol

github.com

https://github.com/tus/tus-java-client

 

GitHub - tus/tus-java-client: The tus client for Java.

The tus client for Java. Contribute to tus/tus-java-client development by creating an account on GitHub.

github.com

 

반응형