PHP 웹사이트를 운영하다 보면 갑자기 서버에서 "PHP Fatal error: Allowed memory size of X bytes exhausted" 라는 에러가 발생하는 경험을 하게 된다. 특히 대용량 파일을 처리하거나 많은 데이터를 조회할 때 더 자주 발생한다. 이 에러가 발생하면 해당 페이지는 완전히 작동 불능 상태가 되기 때문에 사용자 경험도 떨어지고, 빠르게 원인을 파악해서 해결해야 한다. 

 

다만 PHP의 메모리 제한은 보안과 성능의 균형을 맞추기 위한 것이기도 하고, 단순히 메모리 제한을 늘리는 것만으로는 근본적인 해결책이 아닐 수 있다. 이번에는 메모리 부족 에러의 원인을 파악하는 방법과 상황별 해결책, 그리고 메모리를 효율적으로 사용하는 방법을 소개하겠다. 

 

PHP 메모리 제한 확인하기

먼저 현재 PHP의 메모리 제한이 어디에 설정되어 있는지 확인해야 한다.

<?php
// 현재 메모리 제한 확인
echo 'Memory Limit: ' . ini_get('memory_limit') . PHP_EOL;
echo 'Current Memory Usage: ' . number_format(memory_get_usage()) . ' bytes' . PHP_EOL;
echo 'Peak Memory Usage: ' . number_format(memory_get_peak_usage()) . ' bytes' . PHP_EOL;

// php.ini에서 설정된 값 확인
echo 'PHP Config File: ' . php_ini_loaded_file() . PHP_EOL;
?>

기본 메모리 제한은 php.ini에 설정되어 있다. 일반적으로 128MB, 256MB 정도로 설정되어 있다.

# php.ini 파일 확인 (보통 /etc/php.ini 또는 /etc/php/7.x/apache2/php.ini)
grep "^memory_limit" /etc/php.ini

 

메모리 부족 에러의 원인

메모리 에러는 다양한 상황에서 발생한다. 원인을 파악하기 위해 먼저 다음을 확인해보자:

  • 무한 루프나 재귀 함수 : 종료 조건이 없거나 잘못되어 메모리가 계속 할당됨
  • 대용량 배열 생성 : 전체 결과를 배열에 넣고 처리하는 경우
  • 전체 파일 읽기 : 대용량 파일을 한 번에 메모리로 읽음
  • 이미지 처리 : GD 라이브러리로 고해상도 이미지 처리
  • 메모리 누수 : 사용 완료된 객체나 리소스가 제대로 해제되지 않음

이 중에서 가장 흔한 원인은 "전체 데이터를 한 번에 메모리에 로드하는" 것이다. 

 

상황별 해결 방법

1. 대용량 파일 읽기

✗ 잘못된 방법 (메모리 낭비):

<?php
// 전체 파일을 한 번에 읽음 - 100MB 파일이면 메모리를 100MB+ 사용
$content = file_get_contents('large_file.csv');
$lines = explode("n", $content);
foreach($lines as $line) {
   // 처리
}
?>

✓ 올바른 방법 (스트리밍):

<?php
// 파일을 한 줄씩 읽음 - 메모리 사용량 최소
$handle = fopen('large_file.csv', 'r');
if($handle) {
   while(($line = fgets($handle)) !== false) {
      // 각 줄을 처리
      $data = str_getcsv($line);
      // 처리 후 변수 해제
      unset($data);
   }
   fclose($handle);
}
?>

2. 대용량 데이터베이스 조회

✗ 잘못된 방법 (모든 행을 메모리에 로드):

<?php
// 100만 건을 모두 배열에 저장 - 메모리 초과 가능성 높음
$mysqli = new mysqli('localhost', 'user', 'password', 'database');
$result = $mysqli->query('SELECT * FROM large_table');
$rows = $result->fetch_all(MYSQLI_ASSOC);

foreach($rows as $row) {
   // 처리
}
?>

✓ 올바른 방법 (페이징 처리):

<?php
$mysqli = new mysqli('localhost', 'user', 'password', 'database');
$limit = 1000;
$offset = 0;

while(true) {
   $result = $mysqli->query("SELECT * FROM large_table LIMIT {$offset}, {$limit}");
   
   if($result->num_rows == 0) break;
   
   while($row = $result->fetch_assoc()) {
      // 각 행 처리
      // 처리 후 $row 변수는 다음 행으로 덮어씌워짐
   }
   
   $offset += $limit;
   
   // 메모리 정리
   $result->free();
}
?>

3. 이미지 처리

✗ 잘못된 방법 (원본 크기로 처리):

<?php
$image = imagecreatefromjpeg('large_image.jpg'); // 20MB 이미지 메모리 로드
$resized = imagescale($image, 200, 200);
imagejpeg($resized, 'thumb.jpg');
imagedestroy($image);
imagedestroy($resized);
?>

✓ 올바른 방법 (사전 컴프레스):

<?php
// 업로드 시 사전에 리사이징
$source_image = imagecreatefromjpeg('large_image.jpg');
$width = imagesx($source_image);
$height = imagesy($source_image);

// 최대 크기 설정
$max_width = 2000;
$max_height = 2000;

if($width > $max_width || $height > $max_height) {
   $ratio = min($max_width / $width, $max_height / $height);
   $new_width = (int)($width * $ratio);
   $new_height = (int)($height * $ratio);
   
   $resized = imagescale($source_image, $new_width, $new_height);
   imagejpeg($resized, 'processed.jpg', 85);
   imagedestroy($resized);
}
imagedestroy($source_image);
?>

 

메모리 제한 조정

위의 해결책들을 적용했는데도 메모리 부족이 발생한다면, 상황에 따라 제한을 늘려도 된다. 다만 무분별한 상향은 서버 전체에 영향을 미치니 신중해야 한다. 

방법 1: php.ini에서 수정 (전역 설정)

; /etc/php.ini
memory_limit = 512M  # 기본값 128M에서 512M으로 상향

방법 2: .htaccess에서 수정 (Apache 서버)

<code">php_value memory_limit 512M

방법 3: 스크립트에서만 수정 (특정 PHP 파일)

<?php
ini_set('memory_limit', '512M');
// 이 스크립트만 메모리 제한이 512M으로 설정됨
?>

 

메모리 사용량 모니터링

정기적으로 메모리 사용량을 모니터링하면 문제를 조기에 발견할 수 있다.

<?php
// 실행 시작 시점의 메모리
$start_memory = memory_get_usage();

// 작업 수행
for($i = 0; $i < 100000; $i++) {
   $data[] = array('id' => $i, 'name' => 'user_'.$i);
}

// 실행 후 메모리
$end_memory = memory_get_usage();
$used_memory = $end_memory - $start_memory;

echo 'Memory Used: ' . number_format($used_memory / 1024 / 1024, 2) . ' MB' . PHP_EOL;
echo 'Peak Memory: ' . number_format(memory_get_peak_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;

// 더 이상 필요없는 변수 해제
unset($data);
echo 'After unset: ' . number_format(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
?>

 

PHP 메모리 부족 에러는 대부분 "한 번에 너무 많은 데이터를 메모리에 로드"하는 것이 원인이다. 따라서 처음부터 대용량 데이터는 스트리밍이나 페이징으로 처리하는 습관을 들이는 것이 가장 좋은 예방책이다. 메모리 제한을 늘리는 것은 임시방편일 수 있으니, 코드 레벨에서 먼저 최적화하는 것을 권장한다.