Chiming in on everything thats been said:

You can also use lighttpd for proxying. I do and find it works well

Cluster DB
Postgresql has slony for replication

Session / Scaling / Caching
I've found this works well:
code from the start with a min of 3 db handles - read , write ,
session. should your app be small, let them all have the same
connect args, but make sure your internal logic is addressing them by
db. you may want more handles to dole permissions on the read/write
- fine. but don't ever start an app with less than 3 handles -
you'll want to kill yourself when you try to scale.
put sessions in a separate db. if possible, put them on a separate
machine. i only use postgres now for application data, but I still
use mysql for sessions on a sep. machine -- its super fast as isam or
innodb for stuff like that.
i use memcached extensively. like others here said, you can't
realistically cache page content -- but you can cache the data used
to assemble it. in one app I have a lot of super complex joins -
12-20 tables on average. i optimized my application overall by
pulling in more data off the join than I need so its a bit more
generalized, then storing the resultset as a perl hash in memcached
with a 10min ttl. if anything that would affect the join is
inserted, i just send a clear to memcached for that key. pages are
built against the hash if it exists, or a live db call + cache set if
it doesn't. it puts more work on perl - but a completely negligible
amount -- maybe 3% more execution time and memory on the page
generation. But I'm saving a ton of db resources as I don't directly
query as much - so my db time is cut sharply. i'd say this results
in about a overall 50% speedup on page generation because of that.

| - - - - - - - - - - - - - - - - - - - -
| /
| Collaborative Online Management And Syndication Tools
| - - - - - - - - - - - - - - - - - - - -