본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 대용량 데이터 처리 시 Generator와 Yield로 메모리를 90% 절감하는 방법과 3가지 핵심 차이

by Papa Martino V 2026. 4. 11.
728x90

리스트(List) vs 제너레이터(Generator)
리스트 (List) vs 제너레이터 (Generator)

 

현대 데이터 엔지니어링 환경에서 파이썬(Python)을 활용해 기가바이트(GB) 혹은 테라바이트(TB) 단위의 데이터를 다루는 것은 일상적인 업무가 되었습니다. 하지만 많은 개발자가 대용량 텍스트 파일이나 로그 데이터를 처리할 때 리스트(List) 형식을 고집하다가 메모리 부족(MemoryError) 현상에 직면합니다. 본 가이드에서는 파이썬의 마법과도 같은 기능인 Generator(제너레이터)Yield(이일드)가 어떻게 메모리 효율을 극대화하는지 그 내부 원리를 심층 분석하고, 실무에 즉시 적용 가능한 7가지 고성능 해결 전략을 소개합니다.


1. Generator와 Yield의 내부 작동 원리: 지연 평가(Lazy Evaluation)

일반적인 함수는 `return`을 만나면 결과값을 반환하고 함수의 실행 상태를 완전히 종료합니다. 반면, `yield`를 사용하는 제너레이터 함수는 값을 반환하되, 함수의 상태(로컬 변수, 명령어 포인터 등)를 정지(Suspend)시킨 상태로 메모리에 유지합니다. 다음 데이터가 필요할 때 비로소 멈췄던 지점부터 다시 실행됩니다. 이를 통해 모든 데이터를 메모리에 한꺼번에 올리지 않고 '한 번에 하나씩(One item at a time)' 처리하는 지연 평가(Lazy Evaluation) 시스템을 구축할 수 있습니다.


2. 리스트(List) vs 제너레이터(Generator) 핵심 차이 비교

데이터 처리 방식에 따른 리소스 점유와 성능 특성을 분석한 비교표입니다.

비교 항목 리스트 (List Comprehension) 제너레이터 (Generator Expression)
메모리 점유 모든 요소를 메모리에 할당 (데이터 비례) 현재 처리할 데이터만 할당 (상수 크기)
생성 속도 초기 생성 시 모든 연산 수행 (느림) 즉시 생성 (매우 빠름)
데이터 접근 인덱싱 및 반복 접근 가능 한 방향(Next)으로만 순회 가능 (일회성)
연산 방식 Eager Evaluation (즉시 평가) Lazy Evaluation (지연 평가)
추천 작업 소규모 데이터, 반복 재사용 필요 시 대용량 로그 처리, 실시간 스트리밍

3. 메모리 효율 극대화를 위한 실무 Python Example (7가지)

대용량 데이터 파이프라인 설계 시 성능 병목을 해결하기 위한 실전 코드입니다.

Example 1: 수 기가바이트 로그 파일 한 줄씩 읽기

파일 전체를 메모리에 올리지 않고 행 단위로 처리하여 메모리 폭발을 방지합니다.


def log_reader(file_path):
    with open(file_path, "r", encoding="utf-8") as file:
        for line in file:
            yield line.strip()

# 실무 적용
for log in log_reader("huge_access_log.txt"):
    if "ERROR" in log:
        print(f"위험 감지: {log}")
    

Example 2: 제너레이터 표현식으로 메모리 점유 비교

대량의 숫자 리스트와 제너레이터의 메모리 사용량 차이를 객관적으로 확인합니다.


import sys

# 리스트: 모든 숫자를 메모리에 즉시 할당
list_data = [i for i in range(1000000)]
# 제너레이터: 규칙만 저장하고 필요할 때 생성
gen_data = (i for i in range(1000000))

print(f"리스트 메모리: {sys.getsizeof(list_data)} bytes")
print(f"제너레이터 메모리: {sys.getsizeof(gen_data)} bytes")
    

Example 3: 대규모 DB 쿼리 결과 청크(Chunk) 처리

수백만 건의 DB 레코드를 처리할 때 메모리 부하를 해결하는 패턴입니다.


def fetch_large_data(cursor, chunk_size=1000):
    while True:
        rows = cursor.fetchmany(chunk_size)
        if not rows:
            break
        for row in rows:
            yield row

# 사용 예시
# for row in fetch_large_data(my_cursor):
#     process(row)
    

Example 4: 무한 수열 생성기 (Infinite Sequence)

메모리 한계 없이 끝없이 이어지는 데이터를 다룰 수 있는 유일한 방법입니다.


def infinite_counter():
    n = 1
    while True:
        yield n
        n += 1

counter = infinite_counter()
print(next(counter)) # 1
print(next(counter)) # 2
    

Example 5: 데이터 파이프라인 연결 (Generator Chaining)

여러 단계의 전처리를 체인처럼 연결하여 중간 결과가 메모리를 점유하지 않도록 합니다.


def get_lines(filename):
    for line in open(filename):
        yield line

def clean_lines(lines):
    for line in lines:
        yield line.strip()

def filter_errors(lines):
    for line in lines:
        if "CRITICAL" in line:
            yield line

# 파이프라인 실행 (최종 결과 도출 시까지 메모리 최소 사용)
raw = get_lines("system.log")
clean = clean_lines(raw)
errors = filter_errors(clean)

for e in errors:
    save_to_db(e)
    

Example 6: yield from을 이용한 중첩 제너레이터 최적화

복잡한 트리 구조나 중첩된 리스트를 메모리 효율적으로 평탄화(Flattening)합니다.


def sub_generator(data):
    yield from data

def main_generator():
    yield from sub_generator([1, 2, 3])
    yield from sub_generator([4, 5, 6])

for value in main_generator():
    print(value)
    

Example 7: send() 메서드를 활용한 양방향 제너레이터

단순 데이터 전달을 넘어 외부에서 제너레이터 내부 상태를 제어하는 해결 방법입니다.


def smart_filter():
    threshold = 10
    while True:
        val = yield
        if val > threshold:
            print(f"기준치 {threshold} 초과: {val}")
        # 외부에서 send()를 통해 threshold 변경 가능 (실제 구현 시 로직 확장)

f = smart_filter()
next(f) # 제너레이터 준비
f.send(15)
    

4. 결론: 왜 지금 제너레이터를 배워야 하는가?

데이터가 자산인 시대에 메모리 관리 능력은 시니어 개발자를 구분하는 척도입니다. 제너레이터는 단순히 '멋진 코드'를 작성하는 도구가 아니라, 서버 비용을 절감하고 서비스 안정성을 담보하는 핵심 기술입니다. I/O Bound 작업과 결합된 비동기 제너레이터(Async Generator)까지 확장한다면, 당신의 파이썬 애플리케이션은 비약적인 성능 향상을 이룰 것입니다.


내용 출처 및 기술 참조

  • Python PEP 255: Simple Generators
  • Python PEP 380: Syntax for Delegating to a Subgenerator
  • Real Python: "How to Use Generators and yield in Python"
  • Effective Python (2nd Edition) - Item 30: Consider Generators Instead of Returning Lists
728x90