피드로 돌아가기
You Wanted a Number, but Loaded 500,000 Rows Into Memory
Dev.toDev.to
Database

메모리 내 50만 건 로드 방지로 OOM 장애 해결 및 DB 연산 최적화

You Wanted a Number, but Loaded 500,000 Rows Into Memory

Dane Wu2026년 6월 26일8intermediate

Context

Ruby on Rails 애플리케이션에서 ActiveRecord의 Lazy Loading 특성을 오해하여 대량의 데이터를 Application Memory로 로드한 설계 결함 발견. 특히 Ruby Array 메서드와 ActiveRecord 메서드의 유사한 명칭으로 인해 DB 수준의 필터링이 아닌 메모리 내 전수 조사가 수행되어 서버 OOM(Out Of Memory) 장애 유발.

Technical Solution

  • Ruby Array의 .select 대신 ActiveRecord의 .where를 사용하여 SQL 수준에서 필터링을 수행하는 구조로 변경
  • 데이터 존재 여부 확인 시 전체 로우를 가져오는 .present? 대신 LIMIT 1 쿼리를 생성하는 .exists?를 채택하여 네트워크 I/O 및 메모리 점유 최소화
  • 단순 수치 계산 시 .length를 배제하고 상황에 따라 .count(항상 DB 쿼리) 또는 .size(로딩 상태에 따른 최적 경로 선택)를 적용하는 전략 수립
  • 대량 데이터 처리 시 .each를 통한 일괄 로드 방식에서 .find_each를 이용한 Batch Processing(기본 1,000건 단위)으로 전환하여 메모리 사용량 상한선 설정
  • 집계 연산을 Application Layer에서 루프로 처리하던 로직을 DB 엔진의 SUM, AVG, GROUP BY 함수로 위임하여 데이터 전송량 최적화

- 단일 수치/불리언 값만 필요한 경우 `.count`, `.exists?`, `.sum(:column)` 등 DB 전용 메서드 사용 여부 확인 - 컬렉션의 크기를 구할 때 이미 로드된 데이터인지 확인 후 `.size`를 우선 고려 - 1만 건 이상의 레코드를 처리하는 루프 설계 시 `.each` 대신 `.find_each` 또는 `.in_batches` 적용 검토 - Ruby 블록을 사용하는 `.select { ... }`나 `.sum { ... }`이 SQL이 아닌 메모리 내 연산임을 인지하고 사용

원문 읽기