Debugging PHP Memory Leaks in Apache with Drupal: A Step-by-Step Investigation Guide


3 views

When dealing with memory leaks in Apache/PHP environments, the first step is validating the leak pattern. In our case with Drupal 6.x on RHEL 5.6, we observed:

  • Steady memory growth despite stable Apache process count
  • Memory release upon httpd reload
  • 512MB memory_limit per process being consumed over time
# Typical memory observation commands:
watch -n 5 'ps -ylC httpd --sort:rss | awk \'{sum+=$8; ++n} END {print "Tot="sum"("n") Avg="sum/n" Max="$8}\''
free -m

For PHP 5.3 environments, these tools prove most effective:

1. PHP Memory Tracking

<?php
// Add to settings.php
function track_memory() {
    static $max = 0;
    $current = memory_get_usage(true);
    if ($current > $max) {
        $max = $current;
        error_log(sprintf("Memory peak: %.2fMB", $max/1048576));
    }
}
register_shutdown_function('track_memory');
?>

2. Apache Process Inspection

# Monitor process memory changes:
sudo apt-get install smem
smem -P httpd -s rss -k -c "pid rss pss swap command"

From experience, these Drupal-specific patterns often cause leaks:

// Typical leak pattern in custom modules:
function hook_init() {
    // Static variables persist across requests
    static $heavy_data = [];
    if (empty($heavy_data)) {
        $heavy_data = some_expensive_operation();
    }
}

For PHP 5.3, the older XHProf works better than XHProfNG:

pecl install xhprof-beta
echo 'extension=xhprof.so' > /etc/php5/conf.d/xhprof.ini

Sample profiling code:

<?php
xhprof_enable(XHPROF_FLAGS_MEMORY);
register_shutdown_function(function() {
    $data = xhprof_disable();
    file_put_contents('/tmp/xhprof_'.uniqid().'.log', serialize($data));
});
?>

Critical php.ini settings for leak investigation:

; Recommended debug settings
memory_limit = 512M
max_execution_time = 120
display_errors = On
log_errors = On
error_log = /var/log/php_errors.log

Create a monitoring script like:

#!/bin/bash
while true; do
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    ps -eo pid,rss,comm | grep httpd > /var/log/httpd_mem_$TIMESTAMP.log
    lsof -p $(pgrep httpd) >> /var/log/httpd_files_$TIMESTAMP.log
    sleep 300
done

Implement these Drupal 6.x performance tweaks:

// In settings.php
$conf['cache'] = 1;
$conf['page_compression'] = 0;
$conf['preprocess_css'] = 1;
$conf['preprocess_js'] = 1;

When dealing with Apache/PHP memory leaks in Drupal environments, the first step is establishing clear monitoring. The memory growth pattern typically shows:

// Sample output showing memory creep
$ watch -n 60 'ps -ylC apache2 --sort:rss | awk \'{sum+=$8; ++n} END {print "Tot="sum"("n") Avg="sum/n/1024"MB"}\''
Tot=4235678(24) Avg=172.34MB
[2 hours later]
Tot=5872345(24) Avg=239.12MB

Before deep diving into PHP, verify your Apache MPM settings match your traffic pattern:

# Check current MPM configuration
$ apache2ctl -V | grep -i mpm
Server MPM:     prefork

# Recommended prefork settings for memory-heavy Drupal sites

    StartServers            5
    MinSpareServers         5
    MaxSpareServers        10
    MaxRequestWorkers      50
    MaxConnectionsPerChild 500

Implement real-time memory tracking in your Drupal environment:

// Add to settings.php for memory logging
function drupal_memory_log() {
    $mem = memory_get_usage(true);
    file_put_contents('/tmp/drupal_memory.log', 
        date('Y-m-d H:i:s') . " - " . 
        round($mem/1024/1024, 2) . "MB - " . 
        $_SERVER['REQUEST_URI'] . "\n", 
        FILE_APPEND);
}
register_shutdown_function('drupal_memory_log');

// Alternative: XHProf setup for detailed analysis
xhprof_enable(XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_CPU);
register_shutdown_function(function() {
    $data = xhprof_disable();
    file_put_contents('/tmp/xhprof.log', serialize($data));
});

Use PHP's built-in functions to track memory allocation:

// Debug function to identify memory hotspots
function debug_memory_usage($label = '') {
    static $last = 0;
    $current = memory_get_usage();
    $change = $current - $last;
    error_log(sprintf(
        "%s: %s (%+d)",
        $label,
        format_bytes($current),
        $change
    ));
    $last = $current;
}

// Sample Drupal hook implementation
function mymodule_init() {
    debug_memory_usage('After bootstrap');
    // Additional debug points
}

For deeper investigation, consider these tools:

  1. Valgrind Massif:
    $ valgrind --tool=massif --pages-as-heap=yes php -r 'apache_request_headers();'
    
  2. PHP Memory Profiler:
    $ pecl install memprof
    $ echo "extension=memprof.so" >> /etc/php.ini
    
  3. Drupal-specific:
    // In settings.php
    $conf['cache_backends'][] = 'sites/all/modules/memcache/memcache.inc';
    $conf['cache_default_class'] = 'MemCacheDrupal';
    

These Drupal-specific issues frequently cause memory leaks:

// 1. Static variable accumulation
function mymodule_get_data() {
    static $cache = array();
    // Without proper cache clearing, this grows indefinitely
}

// 2. Entity metadata wrappers
$node = node_load(123);
$wrapper = entity_metadata_wrapper('node', $node);
// Always unset wrappers after use

// 3. Views execution in loops
foreach ($items as $item) {
    $view = views_get_view('my_view');
    $view->execute();
    // Should use views_get_view_result() instead
}

Implement continuous monitoring with this shell script:

#!/bin/bash
while true; do
    TIMESTAMP=$(date +%Y%m%d-%H%M%S)
    # Track Apache processes
    ps -eo pid,rss,command | grep apache2 | grep -v grep > /var/log/apache_memory/$TIMESTAMP.log
    # Track PHP memory
    for pid in $(pgrep php); do 
        grep -i rss /proc/$pid/status | awk -v pid=$pid '{print pid,$2}' >> /var/log/php_memory.log
    done
    sleep 300
done

Critical PHP.ini adjustments for Drupal:

; Recommended settings for Drupal 6/7
realpath_cache_size = 256K
realpath_cache_ttl = 3600
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
mysql.connect_timeout = 3
default_socket_timeout = 60