• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 877
  • Last Modified:

Apache - PHP memory errors

I'm new to administering a LAMP server, and I'm having PHP memory issues.

This server is used to run Moodle 2. It's a memory hungry PHP application.

Here's the deal:  I had this setup working (albeit a little slow) on a shared hosting account without any visible memory issues. (I did have MySQL errors once in a while which is why we went to a VPS.) I can't imagine that they would have allocated more resources than what I have, so I believe this is a configuration error on my part.  How can I get this to run well? I have other detailed questions below, but that's the main one.

I have the following set up:

CentOS 5.5 64 bit VPS
1 Gb RAM
Apache 2.2.17
PHP 5.3.5
APC (Alternative PHP Cache) installed but I hope disabled

I believe that APC is disabled because I used to get different errors in the log.

Here's a link to my PHP info: PHP settings

I've embedded some of my httpd.conf below.
## Server-Pool Size Regulation (MPM specific)

# prefork MPM
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# ServerLimit: maximum value for MaxClients for the lifetime of the server
# MaxClients: maximum number of server processes allowed to start
# MaxRequestsPerChild: maximum number of requests a server process serves
<IfModule prefork.c>
StartServers       8
MinSpareServers    5
MaxSpareServers   20
ServerLimit      256
MaxClients        35
MaxRequestsPerChild  3000

# worker MPM
# StartServers: initial number of server processes to start
# MaxClients: maximum number of simultaneous client connections
# MinSpareThreads: minimum number of worker threads which are kept spare
# MaxSpareThreads: maximum number of worker threads which are kept spare
# ThreadsPerChild: constant number of worker threads in each server process
# MaxRequestsPerChild: maximum number of requests a server process serves
<IfModule worker.c>
StartServers         2
MaxClients          50
MinSpareThreads     25
MaxSpareThreads     75 
ThreadsPerChild     25
MaxRequestsPerChild  0

Open in new window

I believe that Apache is using the prefork MPM because 8 apache servers show up in top when I restart Apache . Should I be using the worker MPM? How do I do that?

How should I set  PHP's memory limit? Does the memory limit inflate each Apache server? (i.e. PHP memory = 128M,  apache server size becomes 128M + whatever apache needs to run)

Eventually I'd like to enable APC or another PHP accelerator.  How should I do that?

Thank you!
  • 4
  • 4
2 Solutions
Steve BinkCommented:
Is PHP running as SAPI, CGI, or FastCGI?  What is the allowed memory per thread?  If you're running Fast-/CGI, how many PHP threads are you allowing?

This also depends on how much memory MySQL is using, and how it is performing under those conditions.  What configuration settings do you have for it?

I would disagree with your assumption they did not allocate as much resources to you under shared.  Well, not necessarily, anyways.  Under your shared environment, your web service may have been constrained to a particular amount of memory, but that would have been dedicated to the PHP application itself, not the web service threads and MySQL.  In other words, your whole application would have had access to more resources than what was strictly defined in your plan.  In your VPS environment, your single server instance needs to pack all of that execution into a single gig of memory.  That is going to be a pretty tight fit under the best of circumstances.  You should look at increasing the amount of memory in the VPS.
Steve BinkCommented:
I just noticed your link to the PHP settings.  Your phpinfo() output shows 256M per thread, which means, with no consideration for the OS or other applications, you could have 4 threads running.  Any more than that and you start paging things off, taking a huge hit to performance.  Once the paging is full, or if it is not an option in your environment, you will see service denials, or existing services getting shut down due to lack of memory.

I maintain my previous recommendation - get more memory in your VPS.  In addition, start looking towards optimization of your PHP runtime environment.  Run some benchmarks to find out exactly what your applications require as they run their high-load pages, and set your limits accordingly.  Finally, be sure to take a look at your MySQL memory requirements.
eglescoutAuthor Commented:
Being new to this, I'm not sure how to tell which way PHP is running. How would I figure this out?

From top, here's what is allocated to mysql:
VIRT: 358m
RES:  41m

What would you suggest for benchmarking?

A proven path to a career in data science

At Springboard, we know how to get you a job in data science. With Springboard’s Data Science Career Track, you’ll master data science  with a curriculum built by industry experts. You’ll work on real projects, and get 1-on-1 mentorship from a data scientist.

Jan SpringerCommented:
I agree with routinet.  If you have enough physical memory in the box and can spare more toward php -- increase memory_limit in php.ini.
Steve BinkCommented:
>>> I'm not sure how to tell which way PHP is running.

By looking at your phpinfo() results.  You are currently running as an Apache SAPI handler.

The benchmarks you need come from several different areas.  For example, your top report for MySQL is fine, but it needs to be compared against actual MySQL performance, what kind of load the server is receiving (queries and connections), etc.  It may be that your database requirements are simple and do not need a lot of memory to provide the performance your application wants, which means you can limit the memory allocated to the service.  Or maybe you have a lot of data with inefficient indexing, which means you need to optimize your design and provide more memory/CPU power.

In PHP, you can monitor the amount of memory used, the peak memory required, and the execution time of each script.  Benchmark the overall requirements, as well as individual, high-execution portions of your code.  I doubt your threads need as much as 256MB to execute, so knowing what your highest load pages are will be valuable in determining your memory_limit value, which in turn determines how many concurrent requests can be made with the resources you have.

Optimization is not a simple task.  It is often a long, arduous road of trial and error, tweaking obscure configuration settings, and watching results over time.  There are literally hundreds, if not thousands, of data points that can affect how your application performs.
eglescoutAuthor Commented:
Thanks routinet for getting me started.

Here's my first round of optimization:
I'm going to bump up the memory to 2G this weekend.
I have someone who's going to help me get PHP running as a fast cgi process. That'll include some apache tuning in there too.  Any pointers for that?

In PHP, you can monitor the amount of memory used, the peak memory required, and the execution time of each script.

How do I do that?

I realize that optimization is a long term thing.  I appreciate you pointing out the tools to use.  Other than to use top and apache bench, I'll feel like I'm pretty much in the dark.
Steve BinkCommented:
My current projects do not have high loads or high traffic, so I'm a bit out of touch due to lack of necessity.  I'll let some other experts pick up the Apache tuning questions, and I'll be paying just as much attention as you are.  Also, I have only recently started using FastCGI, so do not have much practical experience with tuning it.  From my experience so far, it appears my normal CGI configurations work just as well with FastCGI.  

For PHP optimization, everything depends on just exactly what you are running.  For memory limitations, I use a general rule 125-150% of the highest load page, though certain applications might tend towards extravagant outlying requirements (e.g., a management app that pulls enormous datasets).  So if I'm running through my benchmarks and I see a page use 8mb of memory (peak), then I'll set the memory_limit to 12mb.  I also use an alerting system in my applications to log whenever a page's peak usage reaches 80% of memory_limit, and either investigate or adjust accordingly.  Aside from creating a ceiling for a thread's memory usage, this setting does not so much optimize performance of PHP as it does impact how much concurrent performance you can see from the server itself.  You should account for any base frameworks you are using (base memory usage/footprint), and have an idea what your application within that framework will require.  Here are some PHP functions you should become acquainted with:


Here's a nice little tutorial on how to use microtime to create benchmarks within your app:


The memory functions are very much the same concept, just with memory usage instead of execution time.

If you're using a framework, keep in mind that the framework itself not only requires a specific minimum footprint, but will typically increase the resource cost of every operation.  For example, setting a global variable in base PHP uses only the memory required to hold the data.  Setting it in a framework could require much more depending on data validation, escaping, and propagation through object hierarchies.
eglescoutAuthor Commented:
Thanks routinet.  I did find out that Moodle 2.0 with the Decaf theme will peak out around 152M for a couple of our page views.  I would not say this is extremely optimized code, but that's what it is at the moment. Considering I update this weekly via Moodle CVS, I'm hoping others are working to optimize the code.

Here's what happened this weekend:
Set up fastcgi - fcgid actually.  Pretty much followed the following 'recipe': http://wiki.mediatemple.net/w/%28dv%29:Run_PHP_as_FastCGI_and_resolve_Apache_permission_errors

added the following to fcgid.conf:
# These are the maximum processes for the whole web server
MaxProcessCount 60
# Default maximum processes per virtual host
DefaultMaxClassProcessCount 30

Open in new window

My vhost file now looks like this:
<VirtualHost *:80>
    ServerAdmin [email]
    DocumentRoot /var/www/vhosts/[your web root]
    ServerName yourdomain.com
    ServerAlias [your choice]
    ErrorLog /var/www/vhosts/[your vhost directory]/error_log
    CustomLog /var/www/vhosts/[your vhost directory]/logs/access_log combined

    UseCanonicalName Off

   <Directory /var/www/vhosts/[your web root]>
        AllowOverride none
        Options ExecCGI FollowSymLinks
        Order allow,deny
        Allow from all
        Deny from none

        AddHandler fcgid-script .php
        ## use the following if you have different virtual hosts running FastCGI
        #SuexecUserGroup domainuser psacln
        FCGIWrapper /usr/bin/php-cgi .php

      # Insert filter
          SetOutputFilter DEFLATE

          # Netscape 4.x has some problems.
          BrowserMatch ^Mozilla/4 gzip-only-text/html

          # Netscape 4.06-4.08 have some more problems
          BrowserMatch ^Mozilla/4\.0[678] no-gzip

          # NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48
          # the above regex won't work. You can use the following
          # workaround to get the desired effect:
          BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

          # Don't compress images, videos and audio
          SetEnvIfNoCase Request_URI \
          \.(?:gif|jpe?g|png|flv|mp4|m4v|mp3|zip|gz)$ no-gzip dont-vary

          # Make sure proxies don't deliver the wrong content
          Header append Vary User-Agent env=!dont-vary


#  Allows relative paths
   AcceptPathInfo on

   ExpiresActive On
   ExpiresByType text/html "access plus 2 seconds"
   ExpiresDefault "access plus 2 seconds"
   ExpiresByType image/gif "access plus 2 months"
   ExpiresByType image/jpeg "access plus 2 months"
   ExpiresByType image/png "access plus 2 months"
   ExpiresByType application/x-shockwave-flash "access plus 2 months"
   ExpiresByType text/css "access plus 4 weeks"
   ExpiresByType text/javascript "access plus 2 weeks"
   ExpiresByType application/x-javascript "access plus 2 weeks"
   ExpiresByType text/xml "access plus 2 seconds"

#   FileETag MTime Size
   FileETag None


Open in new window

Set my prefork processes to start @ 15 and expand to 30.
Did some tuning to MySQL using the following script:https://launchpad.net/mysql-tuning-primer

Set the MySQL connections 5 more than MaxProcessCount in fcgid.conf.

This seems to have worked pretty well.

I'll see how far I get into PHP based optimization.

eglescoutAuthor Commented:
This would have been better asked in multiple questions.
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

  • 4
  • 4
Tackle projects and never again get stuck behind a technical roadblock.
Join Now