From: | PFC <lists(at)peufeu(dot)com> |
---|---|
To: | "Bryan Buecking" <buecking(at)gmail(dot)com>, pgsql-performance(at)postgresql(dot)org |
Subject: | Re: CPU bound at 99% |
Date: | 2008-04-22 16:45:24 |
Message-ID: | op.t90t5yumcigqcu@apollo13.peufeu.com |
Views: | Raw Message | Whole Thread | Download mbox | Resend email |
Thread: | |
Lists: | pgsql-performance |
> about 2300 connections in idle
> (ps auxwww | grep postgres | idle)
[...]
> The server that connects to the db is an apache server using persistent
> connections. MaxClients is 2048 thus the high number of connections
> needed. Application was written in PHP using the Pear DB class.
This is pretty classical.
When your number of threads gets out of control, everything gets slower,
so more requests pile up, spawning more threads, this is positive
feedback, and in seconds all hell breaks loose. That's why I call it
imploding, like if it collapses under its own weight. There is a threshold
effect and it gets from working good to a crawl rather quickly once you
pass the threshold, as you experienced.
Note that the same applies to Apache, PHP as well as Postgres : there is
a "sweet spot" in the number of threads, for optimum efficiency, depending
on how many cores you have. Too few threads, and it will be waiting for IO
or waiting for the database. Too many threads, and CPU cache utilization
becomes suboptimal and context switches eat your performance.
This sweet spot is certainly not at 500 connections per core, either for
Postgres or for PHP. It is much lower, about 5-20 depending on your load.
I will copypaste here an email I wrote to another person with the exact
same problem, and the exact same solution.
Please read this carefully :
*********************************************************************
Basically there are three classes of websites in my book.
1- Low traffic (ie a few hits/s on dynamic pages), when performance
doesn't matter
2- High traffic (ie 10-100 hits/s on dynamic pages), when you must read
the rest of this email
3- Monster traffic (lots more than that) when you need to give some of
your cash to Akamai, get some load balancers, replicate your databases,
use lots of caching, etc. This is yahoo, flickr, meetic, etc.
Usually people whose web sites are imploding under load think they are in
class 3 but really most of them are in class 2 but using inadequate
technical solutions like MySQL, etc. I had a website with 200K members
that ran on a Celeron 1200 with 512 MB RAM, perfectly fine, and lighttpd
wasn't even visible in the top.
Good news for you is that the solution to your problem is pretty easy. You
should be able to solve that in about 4 hours.
Suppose you have some web servers for static content ; obviously you are
using lighttpd on that since it can service an "unlimited" (up to the OS
limit, something like 64K sockets) number of concurrent connections. You
could also use nginx or Zeus. I think Akamai uses Zeus. But Lighttpd is
perfectly fine (and free). For your static content servers you will want
to use lots of RAM for caching, if you serve images, put the small files
like thumbnails, css, javascript, html pages on a separate server so that
they are all served from RAM, use a cheap CPU since a Pentium-M with
lighttpd will happily push 10K http hits/s if you don't wait for IO. Large
files should be on the second static server to avoid cache trashing on the
server which has all the frequently accessed small files.
Then you have some web servers for generating your dynamic content. Let's
suppose you have N CPU cores total.
With your N cores, the ideal number of threads would be N. However those
will also wait for IO and database operations, so you want to fill those
wait times with useful work, so maybe you will use something like 2...10
threads per core. This can only be determined by experimentation, it
depends on the type and length of your SQL queries so there is no "one
size fits all" answer.
Example. You have pages that take 20 ms to generate, and you have 100
requests for those coming up. Let's suppose you have one CPU core.
(Note : if your pages take longer than 10 ms, you have a problem. On the
previously mentioned website, now running on the cheapest Core 2 we could
find since the torrent tracker eats lots of CPU, pages take about 2-5 ms
to generate, even the forum pages with 30 posts on them. We use PHP with
compiled code caching and SQL is properly optimized). And, yes, it uses
MySQL. Once I wrote (as an experiment) an extremely simple forum which did
1400 pages/second (which is huge) with a desktop Core2 as the Postgres 8.2
server.
- You could use Apache in the old fasion way, have 100 threads, so all
your pages will take 20 ms x 100 = 2 seconds,
But the CPU cache utilisation will suck because of all those context
switches, you'll have 100 processes eating your RAM (count 8MB for a PHP
process), 100 database connections, 100 postgres processes, the locks will
stay on longer, transactions will last longer, you'll get more dead rows
to vacuum, etc.
And actually, since Apache will not buffer the output of your scripts, the
PHP or Perl interpreter will stay in memory (and hog a database
connection) until the client at the other end of the internets had loaded
all the data. If the guy has DSL, this can take 0.5 seconds, if he has
56K, much longer. So, you are likely to get much more than 100 processes
in your Apache, perhaps 150 or perhaps even 1000 if you are out of luck.
In this case the site usually implodes.
- You could have a lighttpd or squid proxy handling the client
connections, then funnelling that to a few threads generating the
webpages. Then, you don't care anymore about the slowness of the clients
because they are not hogging threads anymore. If you have 4 threads, your
requests will be processed in order, first come first served, 20 ms x 4 =
80 ms each average, the CPU cache will work better since you'll get much
less context switching, RAM will not be filled, postgres will be happy.
> So, the front-end proxy would have a number of max connections, say 200,
Number of connections to clients => don't set any values, sockets are free
in lighttpd.
Number of connections to PHP/fastcgi or apache/mod_perl backends => number
of cores x 2 to 5, adjust to taste
> and it would connect to another httpd/mod_perl server behind with a
> lower number of connections, say 20. If the backend httpd server was
> busy, the proxy connection to it would just wait in a queue until it was
> available.
Yes, it waits in a queue.
> Is that the kind of design you had in mind?
Yes.
The two key points are that :
* Perl/PHP processes and their heavy resources (database connections,
RAM) are used only when they have work to do and not waiting for the
client.
* The proxy must work this way :
1- get and buffer request data from client (slow, up to 500 ms, up
to 2000 ms if user has emule or other crap hogging his upload)
2- send request to backend (fast, on your LAN, < 1 ms)
3- backend generates HTML and sends it to proxy (fast, LAN), proxy
buffers data
4- backend is now free to process another request
5- proxy sends buffered data to client (slow, up to 100-3000 ms)
The slow parts (points 1 and 5) do not hog a perl/PHP backend.
Do not use a transparent proxy ! The proxy must buffer requests and
data for this to work. Backends must never wait for the client. Lighttpd
will buffer everything, I believe Apache can be configured to do so. But I
prefer to use lighttpd for proxying, it is faster and the queuing works
better.
Also, if you can use FastCGI, use it. I have never used mod_perl, but
with mod_php, you have a fixed startup cost every time a PHP interpreter
starts. With fastcgi, a number of PHP interpreter threads are spawned at
startup, so they are always ready, the startup cost is much smaller. You
can serve a small AJAX request with 1-2 database queries in less than 1 ms
if you are careful with your code (like, no heavyweight session
initialization on each page, using mmcache to avoid reparsing the PHP
everytime, etc).
If you have several backend servers generating webpages, use sticky
sessions and put the session storage on the backends themselves, if you
use files use ReiserFS not ext3 which sucks when you have a large number
of session files in the same directory. Or use memcached, whatever, but
don't put sessions in the database, this gives you a nice tight bottleneck
when adding servers. If each and every one of your pages has an UPDATE
query to the sessions table you have a problem.
As for why I like lighttpd, I am fond of the asynchronous select/poll
model for a webserver which needs to handle lots of concurrent
connections. When you have 50 open sockets threads are perfectly fine,
when you have 1000 a threaded server will implode. I wrote a bittorrent
tracker in Python using an asynchronous select/poll model ; it has been
handling about 150-400 HTTP hits per second for two years now, it has
about 100-200 concurrent opened sockets 24 hours a day, and the average
lifetime of a socket connection is 600 ms. There are 3 threads (webserver,
backend, deferred database operations) with some queues in between for the
plumbing. Serving an /announce HTTP request takes 350 microseconds of CPU
time. All using a purely interpreted language, lol. It uses half a core on
the Core 2 and about 40 MB of RAM.
When lighttpd is overloaded (well, it's impossible to kill it with static
files unless it waits for disk IO, but if you overload the fastcgi
processes), requests are kicked out of the queue, so for instance it will
only serve 50% of the requests. But an overloaded apache will serve 0%
since you'll get 1000 threads, it'll swap, and everything will timeout and
crash.
********************************************************
End of copypaste.
So :
- You need to get less Postgres connections to let Postgres breathe and
use your CPU power to perform queries and not context switches and cache
management.
- You need to get less PHP threads which will have the same effect on
your webserver.
The way to do this is is actually pretty simple.
- Frontend proxy (lighttpd), load balancer, whatever, sending static
requests to static servers, and dynamic requests to dynamic servers. If
the total size of your static files fits in the RAM of this server, make
the static server and the proxy the same lighttpd instance.
- Backends for PHP : a number of servers running PHP/fastcgi, no web
servers at all, the lighttpd frontend can hit several PHP/fastcgi backends.
- Use PHP persistent connections (which now appear to work in the latest
version, in fastcgi mode, I don't know about mod_php's persistent
connections though).
- Or use pgpool or pgbouncer or another connection pooler, but only if
PHP's persistent connections do not work for you.
> 1: Each apache / php process maintains its own connections, not
> sharing with others. So it's NOT connection pooling, but people tend
> to think it is.
True with mod_php (and sad). With fastcgi, you don't really care, since
the PHP processes are few and are active most of the time, no connection
hogging takes place unless you use many different users to connect to
postgres, in which case you should switch to pgpool.
> 2: Each unique connection creates another persistent connection for
> an apache/php child process. If you routinely connect to multiple
> servers / databases or as > 1 user, then each one of those
> combinations that is unique makes another persistent connection.
True also for fastcgi, but if you don't do that, no problem.
> 3: There's no facility in PHP to clean an old connection out and make
> sure it's in some kind of consistent state when you get it. It's in
> exactly the same state it was when the previous php script finished
> with it. Half completed transactions, partial sql statements,
> sequence functions like currval() may have values that don't apply to
> you.
Apparently now fixed.
> 4: pg_close can't close a persistent connection. Once it's open, it
> stays open until the child process is harvested.
Don't know about that.
> 5: Apache, by default, is configured for 150 child processes.
> Postgresql, and many other databases for that matter, are configured
> for 100 or less.
(and for good reason)
> Even if apache only opens one connection to one
> database with one user account, it will eventually try to open the
> 101st connection to postgresql and fail. So, the default
> configuration of apache / postgresql for number of connections is
> unsafe for pconnect.
fastcgi makes this problem disappear by separating the concept of "client
connection" from the concept of "web server thread". Not only will it make
Postgres happier, your PHP processing will be much faster too.
> 6: The reason for connection pooling is primarily to twofold. One is
> to allow very fast connections to your database when doing lots of
> small things where connection time will cost too much. The other is
> to prevent your database from having lots of stale / idle connections
> that cause it to waste memory and to be slower since each backend
> needs to communicate with every other backend some amount of data some
> times. pconnect takes care of the first problem, but exacerbates the
> second.
Moot point with fastcgi.
Unused PHP processes are removed in times of low traffic, along with
their connections.
> P.s. dont' think I'm dogging PHP, cause I'm not. I use it all the
> time, and it's really great for simple small scripts that need to be
> done NOW and need to be lightweight. I even use pconnect a bit. But
> my machine is set for 50 or fewer apache children and 150 postgresql
> connects, and I only use pconnect on small, lightweight things that
> need to zoom. Everything else gets regular old connect.
Very true for mod_php, wrong for fastcgi : you can get extreme
performance with pconnect and a PHP code cache like turck/mm or
eaccelerator, down to 1 ms per page.
Especially if you use PEAR which is very bloated, you nead a code cache
to avoid parsing it on every page.
On previously mentioned website it cut the page time from 50 ms to 2 ms
on some pages because there was a lot of includes.
From | Date | Subject | |
---|---|---|---|
Next Message | Rafael Barrera Oro | 2008-04-22 18:34:38 | Suspicious top output |
Previous Message | Craig Ringer | 2008-04-22 16:36:35 | Re: CPU bound at 99% |