Seiichi Yonezawa

大容量のファイルをアップロードする

· nzwsch

File Browserの紹介と背景

File BrowserというGoで書かれたウェブサーバーがある。 しばらくはMinIOを使ってアップロードすることもあったが、MinIO上にアップロードしたファイルを直接別のアプリケーションから利用するのはひと手間かかる。

File Browserはもともとその名の通りファイルの一覧を表示することもできるが、簡易なアップローダーとしても使えるようだ。GitHub上で26K以上ものスターがついているほどポピュラーなプロジェクトなのだが、APIのドキュメントは現在も用意されていない

Node.jsによるアップロード方法とRubyでの実装

ブラウザのネットワークタブなどを使ってある程度は推測できるが、幸いNode.JSでの方法がissueに残っていた

このコードを参考にHTTPartyを使って単純なファイルをアップローダーを記述した:

class Filebrowser
  include HTTParty

  def self.auth(username, password)
    headers = { "Content-Type" => "application/json" }
    body = { username:, password: }.to_json

    post("/api/login", headers:, body:).then do |res|
      new(res.body) if res.success?
    end
  end

  # ログインしたトークンを格納する
  def initialize(token)
    @token = token
  end

  # `/Media`に保存する場合は`/api/resources/Media`を指定
  def upload(file_path, mime_type = "text/plain")
    url = "/api/resources/Media/#{File.basename(file_path)}?override=true"
    headers = { "Content-Type" => mime_type, "X-Auth" => @token }

    self.class.post(url, headers:, body: File.read(file_path))
  end
end

大容量ファイルの問題

このアップローダーは比較的小さめのファイルであれば問題なく動作する。 もしアップロードしたいファイルが1GBを超えていた場合はどうだろうか。

2.8 GBのビデオファイル

まずは2.8 GBvideo.mp4というファイルを用意して、このファイルを実際にアップロードしてみた。

リソースモニター

当然ではあるが、このプログラムはメモリを2.8 GB使用している。

デスクトップ環境のようにメモリに余裕があればこのプログラムを実行してもさほど問題はないが、このプログラムをサーバーなどの限られたリソースで実行する場合はアップロードするファイルの容量に左右されてしまう。

Rubyでのストリーム処理による解決策

Node.JSにはfs.createReadStream()があるのだが、Rubyの場合はどうすればよいだろうか。

class Filebrowser
  def upload(file_path)
    url = "/api/resources/Media/#{File.basename(file_path)}?override=true"
    headers = { "Content-Type" => "application/octet-stream",
                "Transfer-Encoding" => "chunked",
                "X-Auth" => @token }

    self.class.post(url, headers:, body_stream: File.open(file_path, "rb"))
  end
end

ヘッダにContent-Type: application-octet-streamTransfer-Encoding: chunkedを指定し、bodyの代わりにbody_streamを指定するようにした。

改良されたメモリの使用量

全く同じファイルをアップロードしているが、全体のメモリの使用量は23.3 MBまで抑えることに成功した。

このスクリプトであればファイルのサイズに関係なく、問題なく実行できると思う。