alltools.one
JSON
2025-06-02
8 min
alltools.one Team
JSONStreamingPerformanceLarge FilesMemory

대용량 JSON 스트리밍: 전체를 로드하지 않고 처리하기

표준 JSON 파싱은 전체 문서를 메모리에 로드하고 완전한 데이터 구조를 구축한 뒤 접근을 제공합니다. 10MB 파일이라면 문제없습니다. 하지만 10GB 파일이라면 프로세스가 메모리 부족으로 충돌합니다. 스트리밍 파서는 전체 문서를 메모리에 보관하지 않고 데이터가 도착하는 대로 증분 처리하여 이 문제를 해결합니다.

표준 파싱의 문제점

import json

# 이렇게 하면 전체 파일이 메모리에 로드됩니다
with open('huge.json') as f:
    data = json.load(f)  # 10 GB 파일 = 10+ GB RAM

# 전체 로드 후에야 처리가 시작됩니다
for item in data['records']:
    process(item)

레코드 100만 개, 각 10KB인 파일의 경우:

  • 파일 크기: ~10 GB
  • 파싱 메모리: ~10 GB (원시 문자열)
  • 데이터 구조 메모리: ~15-20 GB (Python 객체는 원시 JSON보다 큼)
  • 합계: ~25-30 GB RAM

스트리밍은 이를 메가바이트 단위로 줄여줍니다.

스트리밍 접근 방식

SAX 스타일 (이벤트 기반)

파서가 JSON 토큰을 만날 때마다 이벤트를 발생시킵니다:

import ijson

# 한 번에 하나씩 처리 - 일정한 메모리 사용량
with open('huge.json', 'rb') as f:
    for record in ijson.items(f, 'records.item'):
        process(record)  # 각 레코드가 개별적으로 파싱됨
        # 이전 레코드는 가비지 컬렉션됨

이벤트 종류: start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.

JSON Lines (JSONL / NDJSON)

더 간단한 접근: 한 줄에 하나의 JSON 객체. 각 줄이 완전하고 유효한 JSON 문서입니다:

{"id": 1, "name": "Alice", "email": "alice@example.com"}
{"id": 2, "name": "Bob", "email": "bob@example.com"}
{"id": 3, "name": "Charlie", "email": "charlie@example.com"}

처리가 간단합니다 — 줄 단위로 읽기만 하면 됩니다:

with open('data.jsonl') as f:
    for line in f:
        record = json.loads(line)
        process(record)

JSON Lines의 장점:

  • 각 줄이 독립적으로 파싱 가능 (병렬 처리)
  • 추가가 용이 (새 줄만 추가)
  • 표준 Unix 도구와 호환 (grep, wc, head, tail)
  • 로그 파일과 스트리밍 데이터에 자연스러움

청크 처리

표준 JSON 배열의 경우, 처리를 청크로 분할:

import ijson

def process_in_chunks(filename, chunk_size=1000):
    chunk = []
    with open(filename, 'rb') as f:
        for record in ijson.items(f, 'item'):
            chunk.append(record)
            if len(chunk) >= chunk_size:
                process_batch(chunk)
                chunk = []
    if chunk:
        process_batch(chunk)

언어별 구현

Python (ijson)

import ijson

# 파일에서 스트리밍
with open('large.json', 'rb') as f:
    parser = ijson.parse(f)
    for prefix, event, value in parser:
        if prefix == 'records.item.name':
            print(value)

# HTTP 응답에서 스트리밍
import urllib.request
response = urllib.request.urlopen('https://api.example.com/data')
for record in ijson.items(response, 'records.item'):
    process(record)

JavaScript (Node.js)

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

const pipeline = createReadStream('large.json')
  .pipe(parser())
  .pipe(streamArray());

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

pipeline.on('end', () => {
  console.log('Done processing');
});

커맨드 라인 (jq)

# 스트림 모드 - 객체를 개별 처리
jq --stream 'select(length == 2) | .[1]' large.json

# JSON Lines 처리
cat data.jsonl | jq -c 'select(.age > 30)'

# 배열을 JSON Lines로 변환
jq -c '.[]' large_array.json > data.jsonl

언제 스트리밍을 사용해야 할까

시나리오표준 파싱스트리밍
100 MB 미만 파일권장과도함
100 MB ~ 1 GB 파일RAM에 따라 다름권장
1 GB 초과 파일불가능필수
대용량 HTTP 응답타임아웃 위험수신하면서 스트리밍
실시간 데이터 피드해당 없음필수
단순 일회성 읽기권장불필요

성능 비교

100만 레코드 처리 (1 GB 파일):

접근 방식메모리 사용량처리 시간복잡도
json.load()3-5 GB15초단순
ijson 스트리밍50 MB45초보통
JSON Lines10 MB12초단순
청크 (1000)100 MB20초보통

스트리밍은 메모리를 훨씬 적게 사용하지만 이벤트 기반 오버헤드로 인해 SAX 스타일 파싱은 더 느립니다. JSON Lines는 각 줄이 독립적인 파싱 작업이므로 빠르면서도 메모리 효율적입니다.

모범 사례

  1. 가능하면 JSON Lines 사용: 가장 간단한 스트리밍 포맷이며 표준 도구와 호환됩니다.
  2. 버퍼 크기 설정: 최적 처리량을 위해 읽기 버퍼를 구성하세요 (보통 64 KB ~ 1 MB).
  3. 배치 처리: 레코드를 하나씩 처리하는 대신 데이터베이스 삽입과 API 호출을 배치로 수행하세요.
  4. 오류를 우아하게 처리: 스트리밍에서는 하나의 잘못된 레코드가 전체 파이프라인을 중단시키면 안 됩니다.
  5. 메모리 모니터링: 프로파일링을 사용하여 스트리밍이 실제로 메모리를 제한하고 있는지 확인하세요.

더 작은 JSON 파일의 포맷팅과 검증에는 JSON 포매터가 최대 100 MB까지 실시간 포맷팅을 지원합니다.

FAQ

스트리밍 파서에서 JSONPath를 사용할 수 있나요?

일부 스트리밍 라이브러리는 경로 기반 필터링을 지원합니다. Python ijson.items는 스트리밍 중 경로 기반 필터링을 지원합니다. 그러나 복잡한 JSONPath 쿼리(필터, 레벨 간 와일드카드)는 일반적으로 전체 문서가 메모리에 있어야 합니다. 경로 기반 쿼리에 대해서는 JSONPath 가이드를 참조하세요.

대용량 JSON 배열을 JSON Lines로 어떻게 변환하나요?

메모리에 들어가는 파일은 jq -c '.[]' input.json > output.jsonl을 사용하세요. 정말 큰 파일은 스트리밍 변환기를 사용하세요: 스트리밍 파서로 배열을 읽고 각 요소를 한 줄로 작성합니다.

관련 리소스

Published on 2025-06-02
JSON Streaming for Large Files: Process Without Loading All | alltools.one