← 블로그로 돌아가기

2026년에 크래시 없이 대용량 JSON 파일 파싱하는 방법

JavaScript와 Python에서 대용량 JSON 파일(100MB 이상)을 처리하는 완전한 가이드. 메모리 오류를 피하기 위한 스트리밍, 점진적 파싱, 청킹 기술을 배워보세요.

Big JSON Team15분 소요programming
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

15 분 읽기

# 2026년에 크래시 없이 큰 JSON 파일 파싱하는 방법

소개

큰 JSON 파일을 파싱하려고 할 때 "FATAL ERROR: JavaScript heap out of memory" 오류를 만난 적이 있나요? 당신만 그런 것이 아닙니다. 대용량 JSON 파일(100MB+) 처리는 현대 웹 개발에서 가장 일반적인 과제 중 하나입니다.

이 종합 가이드에서는 메모리를 소진하거나 애플리케이션을 크래시시키지 않고 대용량 JSON 파일을 효율적으로 파싱하는 검증된 기술을 배웁니다.

JSON.parse()가 큰 파일에서 실패하는 이유

표준 JSON.parse() 메서드는 전체 파일을 한 번에 메모리에 로드합니다:

// ⚠️ 큰 파일(>100MB)에는 나쁨

const fs = require('fs');

const data = fs.readFileSync('huge-file.json', 'utf8');

const json = JSON.parse(data); // 엄청난 메모리 소비!

실패하는 이유:
  • 전체 문자열을 메모리에 저장 (파일 크기의 2-3배)
  • 완전한 JavaScript 객체 생성 (추가로 파일 크기의 2-5배)
  • 완료될 때까지 메모리 해제 없음
  • 총계: 500MB 파일이 2GB 이상의 메모리를 사용할 수 있습니다!

해결책 1: JSONStream을 사용한 스트리밍 (Node.js)

대용량 JSON 파일에 대한 최상의 접근 방식은 스트리밍 - 데이터를 조각조각 처리하는 것입니다.

실용적인 예제

const fs = require('fs');

const JSONStream = require('JSONStream');

// 객체 배열 파싱

const stream = fs.createReadStream('large-dataset.json', { encoding: 'utf8' });

const parser = JSONStream.parse(''); // 배열 요소에는 ''

let count = 0;

parser.on('data', (item) => {

// 한 번에 하나의 항목 처리

console.log(레코드 ${++count} 처리 중:, item.id);

// 여기에 로직 - DB에 삽입, 변환 등

if (item.price > 1000) {

saveToDatabase(item);

}

});

parser.on('end', () => {

console.log(${count}개 레코드 성공적으로 처리);

});

parser.on('error', (err) => {

console.error('파싱 오류:', err);

});

stream.pipe(parser);

중첩된 JSON 경로 파싱

// 용도: { "users": [ {...}, {...} ] }

const parser = JSONStream.parse('users.');

// 용도: { "data": { "items": [...] } }

const parser = JSONStream.parse('data.items.');

// 용도: 깊은 구조 또는 복잡한 조건

const parser = JSONStream.parse(['data', 'nested', { emitKey: true }]);

성능 비교

| 메서드 | 파일 크기 | 메모리 사용 | 시간 |

|-------|---------|----------|-----|

| JSON.parse() | 500MB | ~2.1GB 💥 | 8초 |

| JSONStream | 500MB | ~45MB ✅ | 12초 |

| stream-json | 500MB | ~38MB ✅ | 10초 |

벤치마크: Node.js 20, Mac M1, 16GB RAM

해결책 2: stream-json (더 빠름)

stream-json은 JSONStream보다 빠르고 더 많은 제어를 제공합니다:
const { chain } = require('stream-chain');

const { parser } = require('stream-json');

const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = chain([

fs.createReadStream('huge-array.json'),

parser(),

streamArray()

]);

pipeline.on('data', ({ key, value }) => {

console.log(인덱스 ${key}:, value.name);

// 여기서 처리

processRecord(value);

});

pipeline.on('end', () => console.log('완료!'));

JSON 객체용 (배열 아님)

const { streamObject } = require('stream-json/streamers/StreamObject');

const pipeline = chain([

fs.createReadStream('huge-object.json'),

parser(),

streamObject()

]);

pipeline.on('data', ({ key, value }) => {

console.log(키: ${key}, value);

});

해결책 3: 청크 읽기 (브라우저)

브라우저에서는 FileReader를 사용하여 파일을 점진적으로 처리합니다:

async function parseHugeJSON(file) {

const chunkSize = 1024 1024; // 1MB

let offset = 0;

let buffer = '';

let results = [];

while (offset < file.size) {

const chunk = file.slice(offset, offset + chunkSize);

const text = await chunk.text();

buffer += text;

// 완전한 객체 파싱 시도

const lines = buffer.split('\n');

buffer = lines.pop(); // 불완전한 줄 유지

for (const line of lines) {

if (line.trim()) {

try {

const obj = JSON.parse(line);

results.push(obj);

// 메모리 누적 방지를 위한 배치 처리

if (results.length >= 1000) {

await processBatch(results);

results = [];

}

} catch (e) {

console.warn('유효하지 않은 줄 건너뜀:', line.substring(0, 50));

}

}

}

offset += chunkSize;

// 진행 상황 보고

const progress = (offset / file.size 100).toFixed(1);

console.log(진행률: ${progress}%);

}

// 마지막 배치 처리

if (results.length > 0) {

await processBatch(results);

}

}

// 사용법

document.getElementById('fileInput').addEventListener('change', async (e) => {

const file = e.target.files[0];

await parseHugeJSON(file);

});

해결책 4: Web Workers (논블로킹)

UI 동결을 방지하기 위해 파싱을 Web Worker로 이동:

// worker.js

self.onmessage = async function(e) {

const { file } = e.data;

const chunkSize = 1024 1024;

let processed = 0;

for (let offset = 0; offset < file.size; offset += chunkSize) {

const chunk = file.slice(offset, offset + chunkSize);

const text = await chunk.text();

// 청크 처리

const lines = text.split('\n').filter(l => l.trim());

const objects = lines.map(line => {

try {

return JSON.parse(line);

} catch {

return null;

}

}).filter(Boolean);

// 결과 반환

self.postMessage({

type: 'progress',

data: objects,

percent: (offset / file.size 100)

});

processed += objects.length;

}

self.postMessage({ type: 'complete', total: processed });

};

// main.js

const worker = new Worker('worker.js');

worker.onmessage = function(e) {

if (e.data.type === 'progress') {

console.log(처리됨: ${e.data.percent.toFixed(1)}%);

updateUI(e.data.data);

} else if (e.data.type === 'complete') {

console.log(완료! 총: ${e.data.total});

}

};

// 처리 시작

const fileInput = document.getElementById('upload');

fileInput.addEventListener('change', (e) => {

worker.postMessage({ file: e.target.files[0] });

});

해결책 5: API 페이지네이션

API에서 JSON을 가져오는 경우 페이지네이션을 사용:

async function fetchAllData(apiUrl) {

let allData = [];

let page = 1;

let hasMore = true;

while (hasMore) {

const response = await fetch(${apiUrl}?page=${page}&limit=100);

const data = await response.json();

// 페이지 즉시 처리

await processPage(data.items);

allData.push(...data.items);

hasMore = data.hasNextPage;

page++;

// 속도 제한 방지를 위한 지연 추가

await new Promise(resolve => setTimeout(resolve, 100));

}

return allData;

}

// 최적화 버전: 모든 것을 저장하지 않음

async function processAllData(apiUrl) {

let page = 1;

let hasMore = true;

let totalProcessed = 0;

while (hasMore) {

const response = await fetch(${apiUrl}?page=${page}&limit=100);

const data = await response.json();

// 저장하지 않고 처리

for (const item of data.items) {

await processItem(item); // DB에 저장, 변환 등

totalProcessed++;

}

hasMore = data.hasNextPage;

page++;

console.log(현재까지 ${totalProcessed}개 항목 처리...);

}

return totalProcessed;

}

해결책 6: ijson을 사용한 Python

서버 측 처리의 경우 Python의 ijson이 훌륭합니다:

import ijson

def process_large_json(filename):

with open(filename, 'rb') as file:

# 객체 배열 파싱

objects = ijson.items(file, 'item')

count = 0

for obj in objects:

# 각 요소 처리

if obj.get('price', 0) > 1000:

save_to_database(obj)

count += 1

if count % 1000 == 0:

print(f"{count}개 레코드 처리...")

print(f"총: {count}개 레코드")

# 중첩 구조용

def process_nested(filename):

with open(filename, 'rb') as file:

# 특정 키만 추출

for user_id in ijson.items(file, 'users.item.id'):

print(f"사용자 ID: {user_id}")

최적화 전략

1. 개별 대신 배치 처리

let batch = [];

const BATCH_SIZE = 1000;

parser.on('data', async (item) => {

batch.push(item);

if (batch.length >= BATCH_SIZE) {

await insertBatch(batch); // 하나의 배치 삽입

batch = [];

}

});

parser.on('end', async () => {

if (batch.length > 0) {

await insertBatch(batch); // 마지막 배치 처리

}

});

2. 조기 데이터 필터링

const { pick } = require('stream-json/filters/Pick');

const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = chain([

fs.createReadStream('data.json'),

parser(),

pick({ filter: 'users' }), // "users" 속성만

streamArray()

]);

3. 파일 압축

const zlib = require('zlib');

const pipeline = chain([

fs.createReadStream('data.json.gz'),

zlib.createGunzip(), // 즉석에서 압축 해제

parser(),

streamArray()

]);

일반적인 시나리오 및 솔루션

시나리오 1: JSON에서 데이터베이스 가져오기

const { chain } = require('stream-chain');

const { parser } = require('stream-json');

const { streamArray } = require('stream-json/streamers/StreamArray');

const { MongoClient } = require('mongodb');

async function importToMongo(filename, collectionName) {

const client = await MongoClient.connect('mongodb://localhost:27017');

const collection = client.db('mydb').collection(collectionName);

let batch = [];

const BATCH_SIZE = 1000;

const pipeline = chain([

fs.createReadStream(filename),

parser(),

streamArray()

]);

for await (const { value } of pipeline) {

batch.push(value);

if (batch.length >= BATCH_SIZE) {

await collection.insertMany(batch);

console.log(${batch.length}개 문서 삽입);

batch = [];

}

}

if (batch.length > 0) {

await collection.insertMany(batch);

}

await client.close();

}

시나리오 2: JSON을 CSV로 변환

const { Transform } = require('stream');

const csvWriter = require('csv-write-stream');

const jsonToCSV = new Transform({

objectMode: true,

transform(chunk, encoding, callback) {

// JSON 객체를 CSV 행으로 변환

callback(null, {

id: chunk.id,

name: chunk.name,

email: chunk.email

});

}

});

const writer = csvWriter();

writer.pipe(fs.createWriteStream('output.csv'));

pipeline

.pipe(jsonToCSV)

.pipe(writer);

시나리오 3: 줄바꿈 구분 JSON (NDJSON) 처리

const readline = require('readline');

async function processNDJSON(filename) {

const fileStream = fs.createReadStream(filename);

const rl = readline.createInterface({

input: fileStream,

crlfDelay: Infinity

});

let count = 0;

for await (const line of rl) {

if (line.trim()) {

const obj = JSON.parse(line);

await processObject(obj);

count++;

}

}

console.log(${count}개 줄 처리);

}

문제 해결

오류: JavaScript heap out of memory

해결책:
# Node.js에서 메모리 제한 증가

node --max-old-space-size=4096 script.js

하지만 더 나은 방법: 대신 스트리밍을 사용하세요!

오류: 청크 읽기 시 유효하지 않은 JSON

문제: 청크가 JSON 객체를 분할할 수 있습니다. 해결책: 줄 구분 JSON (NDJSON) 사용:
// 먼저 JSON을 NDJSON으로 변환

const items = require('./data.json'); // 한 번만 작게 로드

const stream = fs.createWriteStream('data.ndjson');

items.forEach(item => {

stream.write(JSON.stringify(item) + '\n');

});

stream.end();

오류: 프로세스가 너무 느림

최적화:
  • JSONStream 대신 stream-json 사용 (2배 빠름)
  • 배치 크기 증가 (1000 → 5000)
  • DB 삽입 병렬화
  • DB 연결 풀 사용
  • 대량 삽입 전 인덱스 추가
  • 권장 도구

    JSON 검증용

    BigJSON.online을 사용하여 대용량 JSON 파일을 완전히 로드하지 않고 빠르게 검증하고 검사합니다. 스트리밍 및 실시간 검증을 지원합니다.

    라이브러리

    • Node.js: stream-json (가장 빠름), JSONStream (가장 간단)
    • Python: ijson (최고)
    • 브라우저: FileReader API + Web Workers

    결론

    빠른 권장 사항:

    | 사용 사례 | 최고의 솔루션 | 메모리 사용 |

    |---------|-----------|----------|

    | Node.js (큰 배열) | stream-json | 매우 낮음 |

    | Node.js (단순 구조) | JSONStream | 낮음 |

    | 브라우저 (< 50MB) | 청크 읽기 | 보통 |

    | 브라우저 (> 50MB) | Web Workers | 낮음 |

    | API 다운로드 | 페이지네이션 + 스트림 처리 | 매우 낮음 |

    | Python | ijson | 매우 낮음 |

    핵심 사항:
    • 50MB 이상 파일에는 항상 스트리밍 사용
    • DB 삽입에는 배치 처리
    • 메모리 사용 줄이기 위해 조기 필터링
    • 큰 파일에 JSON.parse() 사용하지 말 것
    • 모든 것을 한 번에 메모리에 로드하지 말 것

    최신 스트리밍 패키지를 사용하면 메모리를 소진하거나 크래시하지 않고 GB 크기의 JSON 파일을 쉽게 처리할 수 있습니다.

    추가 리소스

    이 가이드가 유용했나요? 큰 JSON 파일로 고생하는 개발자 동료들과 공유하세요!

    Share:

    관련 글

    Read in English