I've written several web applications in Common Lisp, one of which is currently deployed and making money (a charter airline reservation system that surprisingly has nothing to do with ITA), and others which are either still under development (a financial modeling system for large oilfield projects - I left but they signed up with a prominent VC firm a couple of months ago, next-gen trading/TA platform for futures and commodities - my current project) or were mothballed but are good business ideas (a web "reverse proxy" for advertisers and bolt-on solutions to fixing web apps that you can't fix any other way, I'm still interested in developing this).
As a result I have some experience in deploying Lisp web apps, however as you can see it is heavily biased towards high-performance Intranet apps with not that many users, and particularly the need to make the web browser experience feel as responsive as a desktop app. If my current project goes according to plan I should have the opportunity to test my approach on a high-traffic public website.
This is how I set up Lisp web applications:
Everything (including static content like images) is served straight from Hunchentoot running on SBCL on Linux. Hunchentoot uses a thread-per-request model, which is exactly what my web apps need. SBCL on Linux uses NPTL, which according to some benchmarks makes thread-per-request faster than futzing with NIO. I haven't done my own benchmarks, but I'm not worried about Hunchentoot keeping up as a web server.
I start SBCL inside GNU screen using setuidgid to have it run under a locked-down, non-privileged account and still have access to the REPL for upgrading the system without service interruptions.
All the HTML generation is done by CL-WHO. I have my own Lisp-to-JavaScript compiler called Parenscript. On the oilfield modeling project we evaluated pretty much everything out there for writing web apps and settled on doing our own by adopting Parenscript from its previous maintainers. Writing web apps that perform like desktop apps is difficult - the difference between something that feels "native" and something that is unusable junk is usually a couple of lines of code. Finding the right couple of lines without being able to generate and instrument JS code in an intelligent way is a huge task.
For storage I use the CLSQL library. It has a really goofy syntax for writing first-class SQL queries (ie - real objects and not just strings) in Lisp, but after using it I think it's more trouble than it's worth. For the reservation project I wrote some macros that took a business object description and generated SQL schemas (really straightforward ones using SQL types without any kind of Lisp encoding or metadata), functions to produce HTML forms for inputting and editing those objects, and POST handlers for handling and verifying those form submissions while dealing with multiple-submit and CSRF issues. That turned out really RESTy.
Whoever claims this setup is unreliable has some severe problems with their servers. The reservation system I designed has been running for over a year with the only downtime being due to hardware failure.
Neat things that I've easily done with this approach:
- Comet (just ask Hunchentoot for the stream and keep writing to it)
- Compile-time intra-application link checking (see my blog entry)
- Portable profiling for all of your JS code
- Generating versioned, browser-specific JS and content resources that are perfectly cacheable
I dislike frameworks, preferring the Unix ideal of putting together orthogonal tools, so I can't really recommend either UCW or Webactions.
I use CL-WHO because most of the HTML in those applications is generated. If you have a document-oriented web app and someone dedicated to doing HTML and CSS you'll probably want some kind of template system.
One thing you need to watch out for when deploying is the issue that Brian Carper mentioned with VPSs. The main culprit seems to be Virtuozzo - their virtual memory system can't handle overcommitment on any but the host instances. SBCL does this, but other implementations with different memory management strategies won't have this problem. FYI the reservation system is actually deployed on host VZ instances.