◆ 他言語より遅いと聞いたので試してみた
◆ ハードウェア的な制限の 100MB/s でアップロードできてた
◆ かなり高速なネットワーク環境のところでもないと気にする必要なさそう

Node.js で多くのファイルをアップロードすると遅かったので別の言語にしたみたいな話を耳にしました
CPU を使った計算なら JIT コンパイルや最適化があってもネイティブ寄りの言語でチューニングしたものよりは劣ると思います
ですがアップロードってほぼネットワーク速度で Node.js だから遅いというものではない気がします

一応 Node.js でイベントループに処理戻さず 同期的なループ処理で何万や何十万の非同期処理をキューに追加するようなことをすると異常に遅くなることはありました
それは使い方の問題ですし 別の問題です

とりあえずどんなものなのか試してみました

送信用のファイルは 1MB * 1000 にしました
送信方法は単純に HTTP での POST リクエストです

Node.js の処理では

  • ファイルの読み取り方法 (readFile or stream)
  • 送信数 (all or 10)

の 2 * 2 の組み合わせの 4 通り用意しました

ファイルの読み取りは readFile で全部を読み取ってから body に Buffer を設定するものと ReadStream としてファイルを開いて body に stream を設定するものです
送信数はひとつひとつ await で待つのはムダなので並列にしますが そのときの同時実行数です
特に制限せず全件の処理を実行させて Promise.all で待つか 最大 10 並列に制限して最初に 10 個分処理を開始して 1 つ終われば 次の 1 つを開始させるかです

結果はこんな感じでした

方法経過時間
readFile + all15s
stream + all10s
readFile + 109s
stream + 109s

環境は WSL の Ubuntu で Node.js 15.2 です
送信先サーバは同じネットワーク内の別の PC です
JavaScript の処理ではなくネットワーク待ちが多いなら libuv のスレッド数を増やすと速くなったりするかなと思って 20 にしてみましたが どの方法も特に変化はなかったです

速度的には 1GB が 10 秒程度なので 100MB/s です
Node.js 関係なくファイル転送してるときもだいたい 100MB/s 程度しかでていないのでネットワーク側の限界のようです
原因がルータなのか LAN ケーブルなのかは把握してませんが ハードウェア的な制限な以上 Node.js でもその他言語でも変わらなそうです

同じネットワーク内なので 100MB/s も出ていましたが 外部との通信で 100MB/s も出ることって基本ないと思います
NETFLIX の通信速度テストだと 500Mbps 弱でしたが bit なので byte に直すと 62.5MB/s ですし
現実的には Node.js で困ることはなさそうです

ソースコード

4 パターンのアップロード処理のコードです

readFile + all

[fa.js]
const fs = require("fs")
const fetch = require("node-fetch")

const d = Date.now()
const files = [...Array(1000).keys()]

Promise.all(files.map(async i => {
const file = String(i).padStart(3, "0")
const buf = await fs.promises.readFile(`./${file}`)

const res = await fetch("http://192.168.1.5:8000", {
method: "POST",
body: buf
})
const result = await res.text()
if (result !== "OK") throw new Error("not ok")
})).then(x => {
console.log("done", (Date.now() - d) / 1000 + "sec")
})

Stream + all

[sa.js]
const fs = require("fs")
const fetch = require("node-fetch")

const d = Date.now()
const files = [...Array(1000).keys()]

Promise.all(files.map(async i => {
const file = String(i).padStart(3, "0")
const stream = fs.createReadStream(`./${file}`)

const res = await fetch("http://192.168.1.5:8000", {
method: "POST",
body: stream,
})
const result = await res.text()
if (result !== "OK") throw new Error("not ok")
})).then(x => {
console.log("done", (Date.now() - d) / 1000 + "sec")
})

readFile + 10

[fl.js]
const fs = require("fs")
const fetch = require("node-fetch")

const d = Date.now()
const files = [...Array(1000).keys()]

const co = (max) => (arr, afn) => {
const items = arr.slice()
const run = async () => {
if (!items.length) return
await afn(items.shift())
await run()
}
const promises = []
for(let i=0;i<max;i++) promises.push(run())
return Promise.all(promises)
}

co(10)(files, async i => {
const file = String(i).padStart(3, "0")
const buf = await fs.promises.readFile(`./${file}`)

const res = await fetch("http://192.168.1.5:8000", {
method: "POST",
body: buf
})
const result = await res.text()
if (result !== "OK") throw new Error("not ok")
}).then(x => {
console.log("done", (Date.now() - d) / 1000 + "sec")
})

Stream + 10

[sl.js]
const fs = require("fs")
const fetch = require("node-fetch")

const d = Date.now()
const files = [...Array(1000).keys()]

const co = (max) => (arr, afn) => {
const items = arr.slice()
const run = async () => {
if (!items.length) return
await afn(items.shift())
await run()
}
const promises = []
for(let i=0;i<max;i++) promises.push(run())
return Promise.all(promises)
}

co(10)(files, async i => {
const file = String(i).padStart(3, "0")
const stream = fs.createReadStream(`./${file}`)

const res = await fetch("http://192.168.1.5:8000", {
method: "POST",
body: stream,
})
const result = await res.text()
if (result !== "OK") throw new Error("not ok")
}).then(x => {
console.log("done", (Date.now() - d) / 1000 + "sec")
})