大量データ処理とは何か
大量データ処理は「配列やオブジェクトが数万〜数百万件規模になっても、時間とメモリを破綻させずに目的を果たす」ための考え方です。ここが重要です:やみくもに map や filter を重ねると“走査回数の増加”と“中間配列の割り当て”で急速に遅くなります。計算量(何回なめるか)とメモリ(何個の配列を作るか)を常に意識し、「最小限の走査」「最小限の割り当て」に設計します。
処理の順序設計(単一パスと早期終了)
フィルタ→変換→集計→出力の順に“1回で”やり切る
大量データでは「フィルタと変換を1パスで融合し、必要なら集計を同時に行う」ことが効きます。中間配列を作らず、最後の結果のみ作るのが理想です。
function filterMapReduce(items, pred, mapFn) {
let sum = 0;
const out = [];
for (const x of items) {
if (!pred(x)) continue; // フィルタ
const y = mapFn(x); // 変換
sum += y.price ?? 0; // 集計(例)
out.push(y); // 最終出力だけ作る
}
return { rows: out, sum };
}
JavaScriptここが重要です:filter→map→reduce の三段チェーンを1回の for…of にまとめると、走査は1回・割り当ても1回になり、GCプレッシャーが大幅に下がります。
早期終了を徹底する
必要なものが見つかったら終わる設計にします。find、some、every は“途中で止まれる”ため、全件走査の無駄を省けます。
const hit = items.find(x => x.id === targetId); // 見つかれば即終了
const hasExpensive = items.some(x => x.price > 100000);
JavaScript計算量を落とす定番手法(辞書化・前計算・ソート回避)
重複検索は辞書化に切り替える
同じキー(ID)で何度も探すなら、配列から辞書(オブジェクト/Map)へ変換し、検索を O(1) にします。
const dict = Object.fromEntries(items.map(x => [String(x.id), x]));
const pick = ids => ids.map(id => dict[String(id)]).filter(Boolean);
JavaScriptここが重要です:二重ループや毎回 find で O(n*k) になりがちな処理を、辞書で O(n+k) に変えます。
比較用のキーを前計算する(Schwartzian transform)
重い正規化や日付変換は“比較・判定の前”に1回だけ行い、以後は前計算キーで高速に処理します。
const prepared = items.map(x => ({
x,
keyName: x.name.trim().toLowerCase(),
keyDate: new Date(x.createdAt).getTime()
}));
const recent = [];
for (const p of prepared) {
if (p.keyName.includes("alice") && p.keyDate > cutoffMs) recent.push(p.x);
}
JavaScriptここが重要です:new Date や正規化を何十万回も繰り返すと定数倍で激遅になります。前計算で“比較を軽くする”のが王道です。
ソートは本当に必要かを見直す
sort は O(n log n) で高コストです。トップNが欲しいだけなら“部分選抜”でソートを回避できます。
function topN(items, n) {
const heap = []; // 簡易:最小値を先頭に保持
for (const x of items) {
heap.push(x);
heap.sort((a, b) => a.score - b.score); // 小さい順
if (heap.length > n) heap.shift(); // 最小を捨てる
}
return heap.toSorted((a, b) => b.score - a.score); // 見やすく整列
}
JavaScriptここが重要です:全体ソートを避け、必要部分だけ維持する発想にすると、n が小さいほど劇的に速くなります。
メモリ戦略(チャンク処理・ストリーミング・中間削減)
チャンク処理でピークメモリを抑える
巨大配列は一定サイズで分割して順次処理します。部分結果をその都度出力・集約すれば、同時に抱えるメモリ量を減らせます。
function processInChunks(arr, size = 10000, handle) {
for (let i = 0; i < arr.length; i += size) {
const chunk = arr.slice(i, i + size);
handle(chunk); // ここで集計や書き出し
}
}
JavaScriptここが重要です:一度に全てをメモリに載せず、段階的に処理していく。スループットと安定性が向上します。
中間配列を作らない(融合する)
filter と map をチェーンすると“中間配列”が増えます。1パスで結果だけ作る関数を用意して、割り当て数を減らします。
function filterMap(arr, pred, mapFn) {
const out = [];
for (const x of arr) if (pred(x)) out.push(mapFn(x));
return out;
}
JavaScriptここが重要です:大量データでは“新しい配列を作る回数”がボトルネックになります。最後の出力だけ作ると GC が安定します。
破壊と非破壊の使い分け
外部に影響しないローカル一時データは“破壊的操作”で割り当てを減らし、共有状態やUIは“非破壊”で安全性を保つ方針が現実的です。
const tmp = getChunk(); // 新規生成なら in-place が安全
tmp.sort((a,b)=>a-b); // 破壊的でも外へ漏れない前提
consume(tmp);
JavaScriptI/O と並列の工夫(API・ファイル・UI)
ページング・カーソルで“必要な分だけ”取得する
外部データは“全部ください”を避け、ページングまたはカーソルで必要な範囲だけ取る。取得前にサーバー側でフィルタ・ソートを済ませてもらうと最も効率的です。
function cursorPage(list, cursorId = null, limit = 2000) {
const i = cursorId == null ? -1 : list.findIndex(x => x.id === cursorId);
const start = Math.max(0, i + 1);
const rows = list.slice(start, start + limit);
return { rows, nextCursor: rows.at(-1)?.id ?? null };
}
JavaScriptここが重要です:データは“持ちすぎない”。I/Oの段階で絞ることが、メモリと時間の最大の節約になります。
UI は“仮想化”でレンダリングを絞る
画面表示は全件を描画せず、見えている範囲だけ表示(Virtualization)。配列処理自体より、レンダリングコストがボトルネックになることが多いです。配列はページング・検索結果だけ渡す設計にします。
失敗しないためのガードと計測
まず計測する(想像で最適化しない)
実データ規模で console.time や軽いベンチを取り、どこが遅いかを把握してから手を入れます。
console.time("pipeline");
const out = filterMapReduce(items, pred, mapFn);
console.timeEnd("pipeline");
JavaScriptここが重要です:小さな配列では差が見えません。“大きいほど効く最適化”に注力するため、計測をルーチン化します。
ガードで安全化(欠損・型揺れを潰す)
大量データは欠損が混ざりがちです。null/undefined を先に除外・既定値化して、コールバックの分岐を軽くします。
const safeItems = (items ?? []).filter(Boolean);
JavaScriptすぐ使えるレシピ(大量データでも耐える定番)
一括フィルタ・変換・集計(単一パス)
function pipeline(items, pred, mapFn) {
const out = [];
let count = 0, sum = 0;
for (const x of items) {
if (!pred(x)) continue;
const y = mapFn(x);
out.push(y);
count++;
sum += y.price ?? 0;
}
return { rows: out, meta: { count, sum } };
}
JavaScriptチャンク+逐次出力
function processLarge(items, handle, size = 10000) {
for (let i = 0; i < items.length; i += size) {
const chunk = items.slice(i, i + size);
handle(chunk); // 書き出し・集約・送信など
}
}
JavaScript辞書化+部分復元
const toDict = (list, key = "id") =>
Object.fromEntries(list.map(x => [String(x[key]), x]));
function updateDict(dict, id, patch) {
return { ...dict, [id]: { ...dict[id], ...patch } };
}
function toList(dict) { return Object.values(dict); }
JavaScriptまとめ
大量データ処理の核心は「単一パス化で走査回数と中間配列を減らす」「辞書化・前計算で計算量を落とす」「チャンク処理・ページングで“必要な分だけ持つ”」ことです。ソートは本当に必要なときだけ、トップNは部分選抜で代替。ローカルは破壊的で割り当てを減らし、共有は非破壊で安全に。計測を習慣化し、欠損ガードでコールバックを軽く保つ。この指針に従えば、初心者でもスケールしても壊れない、速くて安定した大量データ処理を設計できます。
