Node.jsで大容量ファイルを扱う:AIモデルのような大きなデータ保存はストリーム処理使いましょう

Node.jsで大容量ファイルを扱う:AIモデルのような大きなデータ保存はストリーム処理使いましょう

こんにちは!今日はAIシステムのフロントサーバーとしてもよく使用するNode.jsについてのお話です。

AIモデルの普及に伴い、大容量のデータファイルを扱う機会が急増しています。LLMなどのモデルファイルやトレーニングデータセットは数GB、場合によっては数十、数百GBにも達することがあります。

一方、Node.jsはWebアプリケーションのフロントサーバーとして広く採用されており、データマネジメントやPythonで書かれたAIバックエンドとの橋渡し役としてもかなりお役立ちな存在です。

本記事では、Node.js v20LTSで5GB程度のファイルを処理しようとして遭遇した問題と、その解決方法について解説します。

Node.jsのバッファサイズ制限の変遷

Node.jsのバッファサイズ制限は、バージョンによって大きく変化してきました

Node.jsバージョン サポート終了日 バッファサイズ上限 備考
Node.js 0.12.x 2016年12月31日 ~1GB 初期のバッファサイズ制限(smalloc.kMaxLength使用)
Node.js 4.x (Argon) 2018年4月30日 ~2GB V8 4.4での書き換えにより制限が拡大
Node.js 6.x (Boron) 2019年4月30日 32ビット符号付き整数の最大値
Node.js 8.x (Carbon) 2019年12月31日 OpenSSL 1.0.2のEOLに合わせて早期終了
Node.js 10.x (Dubnium) 2021年4月30日 32ビット符号付き整数の最大値
Node.js 12.x (Erbium) 2022年4月30日 32ビット符号付き整数の最大値
Node.js 14.x (Fermium) 2023年4月30日 途中から4GBに拡大
Node.js 16.x 2023年9月11日 ~4GB OpenSSL 1.1.1のサポート終了に合わせてEOLが早まった
Node.js 17.x 2022年6月1日 奇数バージョンは短期サポート
Node.js 18.x 2025年4月30日 現在メンテナンスLTSフェーズ
Node.js 19.x 2023年6月1日 奇数バージョンは短期サポート
Node.js 20.x 2026年4月30日 現在アクティブLTSフェーズ
Node.js 21.7.2 2024年6月1日
Node.js 21.7.3 2024年6月1日 ~8TB v21.7.3でバッファサイズ上限が大幅拡大
Node.js 22.x (Jod) 2027年4月30日 2024年10月29日にLTSに移行
Node.js 23.x 2025年6月1日 奇数バージョンは短期サポート

Node.js v20LTSでは理論上は4GBまでのバッファを扱えるようになっていますが、I/O操作(ファイルの読み書き)における制限が依然として存在します。これはNode.js自体ではなく、その下層で動作するlibuv(非同期I/Oライブラリ)の制限によるものです。

実際に遭遇した問題:5GBのAIモデルファイル

あるプロジェクトで、5GBのAIモデルファイルをモデル管理サーバーとして使っているNode.js v20 LTSを経由して保存しようとした際、以下のコードを使用しました:

save_file(target_dir, file_name, file_buffer) {
  try {
    // 保存先ディレクトリが存在しない場合は作成
    if (!fs.existsSync(target_dir)) {
      fs.mkdirSync(target_dir, { recursive: true });
    }

    const file_path = path.join(target_dir, file_name);
    fs.writeFileSync(file_path, file_buffer);
    return true;
  } catch (error) {
    console.error(`ファイル保存エラー: ${error.message}\n${error.stack}`);
    return false;
  }
}

すると、以下のようなエラーが発生しました

ファイル保存エラー: The value of "length" is out of range. It must be >= 0 && <= 4294967295. Received 5368709120

このエラーは、Node.js v20LTSのバッファ制限が4GBであるのに対し、我々が扱おうとしていたファイルは5GB(5,368,709,120バイト)だったことを示しています。
こうやって無邪気なコードをかきましたが、巨大ファイルをこのような方法で保存するのはいただけないです。

エラーのとおり、5GBのファイルを一度に処理することはできないことが分かります。

(5GBならかわいいもんですが、素人が数百GBクラスのモデルデータをあつかうと、通常のコードは何でもなかったコードが一斉に不具合に見舞われたりします。)

解決策:ストリーム処理と非同期I/O

さて、この問題を解決するために、ストリーム処理と非同期I/Oを採用したアプローチに切り替えました

async save_file(target_dir, file_name, input_data) {
  try {
    // 保存先ディレクトリが存在しない場合は作成(非同期版)
    await fs.promises.mkdir(target_dir, { recursive: true });

    const file_path = path.join(target_dir, file_name);
    
    // ストリームを使用してファイルを書き込む
    const writeStream = fs.createWriteStream(file_path);
    
    // Bufferの場合
    if (Buffer.isBuffer(input_data)) {
      // チャンクに分割して書き込む
      const chunkSize = 1024 * 1024; // 1MBずつ
      for (let i = 0; i < input_data.length; i += chunkSize) {
        const chunk = input_data.slice(i, Math.min(i + chunkSize, input_data.length));
        writeStream.write(chunk);
      }
      writeStream.end();
    } 
    // ストリームの場合
    else if (typeof input_data.pipe === 'function') {
      input_data.pipe(writeStream);
    }
    // その他の場合(文字列など)
    else {
      writeStream.write(input_data);
      writeStream.end();
    }

    // 完了または失敗を待機する
    await new Promise((resolve, reject) => {
      writeStream.on('finish', resolve);
      writeStream.on('error', reject);
    });
    
    return true;
  } catch (error) {
    console.error(`ファイル保存エラー: ${error.message}\n${error.stack}`);
    throw error; // asyncメソッドなのでthrowを使う
  }
}

この改善版コードを使って5GBのモデルファイルを問題なく保存できるようになりました。

主な改善点は以下の通りです

  1. ストリーム処理
    データを小さなチャンク(1MB)に分割して処理することで、バッファサイズの制限を回避しました。
  2. 非同期処理
    async/awaitを使用することで、ファイル処理中もサーバーが他のリクエストに応答できるようになりました。
  3. プログレス表示の実装
    大きなファイルの転送過程を監視するために、チャンク単位のプログレス表示も組み込みました(コード例では省略)。

ということで、巨大ファイルを扱い、安定性を向上するためには、キャッシュ・ストリーミング・非同期での処理が非常に重要となります。

最新のNode.js(2025年4月時点でv.23)でも注意が必要

さて、Node.js v22以降では理論上8TBまでのバッファを扱えるようになりますが、実際のI/O操作ではまだ制限があるため、大きなファイルを扱う際にはどのバージョンでもストリーム処理を採用することがおすすめです。

(おまけ)さらに、マルチコアを活かすことで、パフォーマンス向上・最適化

Node.jsは単一スレッドで動作するため、CPUバウンドな処理を行う場合、マルチコアのパフォーマンスを活かしきれません。これを解決するのがclusterモジュールです。

今回のように単純なファイル保存の場合、基本的に単一ファイルへの書き込みはI/Oバウンドな処理で、OSのファイルシステムによって直列化されますので、複数のプロセスからの保存には実はそんなに意味がありません。まして、同じファイルに同時に書き込むと、ファイルシステムのロックやシークポインタの競合が発生し、むしろパフォーマンスが低下する可能性すらあります。

ただ、ファイルに対して一定の処理を行ったりする場合には、マルチコアにすることで、パフォーマンスを向上できる可能性もありますので、ご紹介します。

cluster モジュールの基本的な使い方

import cluster from 'node:cluster';
import http from 'node:http';
import { cpus } from 'node:os';
import process from 'node:process';

const numCPUs = cpus().length;

if (cluster.isPrimary) {
  console.log(`メインプロセス ${process.pid} 実行中`);
  
  // CPUコア数分のワーカーを起動
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  cluster.on('exit', (worker, code, signal) => {
    console.log(`ワーカー ${worker.process.pid} が終了しました`);
    // 必要に応じてワーカーを再起動
    cluster.fork();
  });
} else {
  // ワーカーは同じポートでHTTPサーバーを起動
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('Hello World\n');
  }).listen(8000);
  
  console.log(`ワーカー ${process.pid} 起動完了`);
}

大容量ファイル処理での最適化の組み合わせ

大容量ファイル+何等かな処理(CPUバウンドな)を扱う場合は、ストリーム処理とclusterモジュールを組み合わせることで、さらに効率的な処理が可能になります

  1. CPUコア数の最適利用
    clusterモジュールでCPUコア数分のプロセスを起動
  2. ストリーム処理
    各ワーカープロセス内でチャンク単位のストリーム処理を実装
  3. 負荷分散
    大きなファイルをワーカー間で分割処理(例: 範囲ごとに担当を分ける)

まとめ

AIモデルのような大容量ファイルを扱うNode.jsアプリケーションのストリーム処理についてご紹介しました。巨大ファイルはストリーム処理と非同期I/O操作を組み合わせることで効率的に扱うことができます

それではまた次回おあいしましょう!

Read more

モデルを「壊さずに」ドメインを広げる ― XLM-RoBERTa 継続学習の設計ノート

モデルを「壊さずに」ドメインを広げる ― XLM-RoBERTa 継続学習の設計ノート

こんにちは、Qualiteg研究部です。 今日は「すでに完成している強いモデルを、壊さずに広げる」という、地味だけど実務でとても大事なテーマを取り上げたいと思います。 機械学習に取り組んでいると、 「一度しっかり仕上げたモデルを、新しい用途やデータに合わせてもう少し広げたい」 そんな場面はよく出てきます。 今回ご紹介するNER(固有表現抽出)のシーンに限らず、いろいろなタスクで共通する悩みではないでしょうか。 ところが、ここで素朴に追加学習をかけると、せっかくの強みがあっさり崩れてしまう。 私たちは、PII(個人特定情報や要配慮情報)を検出・マスキングするエンジン(PII-FI)を構築する際、実際にそれを経験しました。 Precision(適合率)が 0.83 から 0.17 まで転げ落ちる、なんてことも本当に起きるんです。 PII検出では、ドメイン(分野)ごとに検出したいPII型の種類や求められる精度が異なる場合があります。そこで1つのエンジンといっても、対応ドメインを広げていくたびに(そのドメインに適応させるための)追加学習が求められることがあります。 本稿は、そう

By Qualiteg 研究部
Claude Codeで出てくる「court」って何? “XML露出” 現象とツール呼び出し未実行事故の対策

Claude Codeで出てくる「court」って何? “XML露出” 現象とツール呼び出し未実行事故の対策

こんにちは! Qualitegプロダクト開発部です。 Claude Code を使っていると、ツール呼び出しの XML(<invoke> や <parameter>)が画面にそのまま表示されたり、実際にはコマンドや PR 作成が実行されていないのに「完了しました」と報告されたりして、動作がおかしくなることがあります。 そして、その呼び水となる文字列 court や course や count が出現します 本稿では、 この現象(本稿では「XML露出」と呼びます)を実ログから解説し、検知と対策をまとめました。 ● ● ●  claude-code — bash➜ ~/qualiteg-project claude> プロジェクト配下のストレージ使用量を調査します。court<invoke name="Bash">

By Qualiteg プロダクト開発部
AIが攻撃と防御の両方を変える――セキュリティ市場2026と次の10年

AIが攻撃と防御の両方を変える――セキュリティ市場2026と次の10年

ここ数年で、サイバーセキュリティをめぐる議論の前提は大きく変わりました。かつての中心は「いかに侵入を防ぐか」でしたが、いまは攻撃側も防御側も、ともにAIを使い始めています。攻撃が機械の速度で自動化・大規模化する一方、防御も人手だけでは追いつかない領域に入りつつあります。本記事では、公開されている市場データをもとに、AI時代のセキュリティ市場を「どこが伸び、どこが重なり、どこに注意すべきか」という観点から整理します。 「AIとセキュリティ」には三つの市場がある 最初に、用語を整理しておきます。「AIセキュリティ」とひとくくりにすると分かりにくいのですが、実際には少なくとも三つの異なるテーマが同時に進んでいます。 この三つの違いは、「誰がAIを使うのか」と「何を守るのか」で考えると分かりやすくなります。 第一は、防御側がAIを使う「AIで守る」領域です。 攻撃者がAIを使っているかどうかにかかわらず、企業やセキュリティ事業者がAIを利用して、サイバー攻撃やインシデントを検知・分析・阻止します。大量のログやアラートの分析、脅威の優先順位付け、異常の検知、初動対応の支援などは、すでに

By Qualiteg コンサルティング, Qualiteg AIセキュリティチーム
Claude Opus 4.8 完全ガイド — 公式ドキュメントから読み解くモデル仕様とClaude Code運用ポイント

Claude Opus 4.8 完全ガイド — 公式ドキュメントから読み解くモデル仕様とClaude Code運用ポイント

こんにちは! 2026年5月に、AnthropicからClaude Opus 4.8がリリースされました。 そして、2026年6月には Fable5 /Mythos5がリリースされました。 しかし都合により現在(2026/6/18)は利用できないため、実質 Claude Opus 4.8 が一般人がつかえるClaudeシリーズの最上位モデルということになります。 そこで、今回は長く付き合うことになるかもしれない Opus 4.8 について徹底解説したいとおもいます。 Opus4.8は従来の4.7の延長線上にあるアップデートですが、「ベンチマークが少し上がった」では片付けられない変化を含んでいます。 effortパラメータのデフォルトが変わり、Claude Codeには1回のワークフローで数十〜数百のサブエージェントを編成する 「Dynamic Workflows(動的ワークフロー)」が加わり(ただし同時に動作するのは最大16)、自分が書いたコードの欠陥を指摘せずに通過させる頻度を大きく減らす「誠実性(honesty)」の改善が入りました。 つまり、4.7時代に組んだ運用や

By Qualiteg プロダクト開発部