moeffju.net

Managing multiple mod_passenger standalone configurations

If you’re deploying multiple Ruby/Rails apps with passenger, you might end up with different ruby version requirements. Or maybe you want to keep gemsets separate. For whatever reason, you end up running one or more of your apps in a standalone passenger server and proxying them through Apache or nginx.

Or maybe you’re doing mass webhosting with only passenger standalone instances. Whatever floats your boat.

This is the script I hacked together for Toptranslation. Instructions below.

#!/bin/bash

### BEGIN INIT INFO
# Provides:          passenger-standalone
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Easily start or stop standalone passenger instances
### END INIT INFO

# default config
USER=www-data
APP_DIR=
RVM_RUBY=1.9.2
ADDR=127.0.0.1
PORT=3000
ENVIRONMENT=production

if [ -n "$2" -a -r "/etc/passenger-standalone.d/$2" ]; then
  CONFIG="$2"
elif [ -r "/etc/passenger-standalone.d/$(basename $0)" ]; then
  CONFIG="$(basename $0)"
fi

if [ -n "$CONFIG" ]; then
  if [ ! -r "/etc/passenger-standalone.d/$CONFIG" ]; then
    echo "Error: Configuration '${CONFIG}' not found in /etc/passenger-standalone.d/" >&2
    exit 4
  fi
  source "/etc/passenger-standalone.d/$CONFIG"
  if [ -z "$APP_DIR" ]; then
    echo "Error: APP_DIR is not defined."
    exit 5
  fi
  CMD_START="cd $APP_DIR; rvm use $RVM_RUBY; passenger start -a $ADDR -p $PORT -e $ENVIRONMENT -d"
  CMD_STOP="cd $APP_DIR; rvm use $RVM_RUBY; passenger stop -p $PORT"
fi

. /lib/lsb/init-functions
case "$1" in
  start)
    echo "Starting passenger for ${CONFIG}"
    echo "[$USER] $CMD_START"
    su - $USER -c "$CMD_START"
    ;;
  stop)
    echo "Stopping passenger for ${CONFIG}"
    echo "[$USER] $CMD_STOP"
    su - $USER -c "$CMD_STOP"
    ;;
  *)
    echo "Usage: $0 start|stop [config]" >&2
    exit 3
    ;;
esac

To use, first put it into /etc/init.d/passenger-standalone. Then, for each standalone instance you want to start, create a configuration file in /etc/passenger-standalone.d/ with a sensible name. For example, here’s a redmine configuration:

# /etc/passenger-standalone.d/redmine 
USER=www-data
APP_DIR=/opt/redmine
RVM_RUBY=1.8.7-p330
ADDR=127.0.0.1
PORT=3081
ENVIRONMENT=production

And here is the corresponding VirtualHost for Apache:

<VirtualHost *:80>
  ServerName redmine.example.com
  DocumentRoot /opt/redmine/public

  PassengerEnabled Off
  ProxyRequests Off
  <Proxy *>
    Order deny,allow
    Allow from all,
  </Proxy>
  ProxyPass / http://127.0.0.1:3081/
  ProxyPassReverse / http://127.0.0.1:3081/
</VirtualHost>

Now, when you reload apache with that config and try to access redmine.example.com, you will get an Error 500 because the passenger instance is not running yet. To start it, simply run /etc/init.d/passenger-standalone start redmine, OR symlink /etc/init.d/passenger-standalone to /etc/init.d/redmine and run /etc/init.d/redmine start.

To automatically run the script in the appropriate runlevels, symlink it as described, then run update-rc.d redmine defaults. This will set up the appropriate symlinks.

Update: Fixed a backtick that wasn’t supposed to be there.

Fighting frameworks

Rant time!

So, back when I was still doing PHP, the story went like this: “We have this kinda okay framework which is documented okay and there are almost no plugins or contributions, so you’re on your own, and if things don’t work, well, you’re screwed, enjoy digging in the depths of the ‘magic’ code.” As soon as you try to do something the framework creators haven’t thought of, it’s like clubbing wild bears to death with only your bare hands and small sticks, in the mud, while torrential rain is falling on you and the air reeks of sulphur. Boy, am I glad I’m done with PHP.

With Rails, it’s different: “We have this super awesome framework which is pretty well documented [ed: in blog posts], oh, except, the new version [ed: rails 3] isn’t, and we have a gazillion of plugins and stuff, but they’re mostly not documented at all, so you’re best off reading the source, and the plugins might or might not work with your version of rails, or they might eat your soul.” You still end up fighting the framework and the plugins, but at least you’re doing it with rapiers. It’s a very civilized form of combat.

(A vaguely related side note, it’s a damn shame how much Yahoo! sucks. They had this great search engine, owned the market, and then they got the idea that they were a ‘media company’ with ‘producers’ managing ‘properties’ and they turned the suck up to eleven. Nowadays, they have a great bunch of very bright people, yet still manage to turn out mostly products that suck. It is quite astonishing. Also, what is it with them launching products and then just discontinuing them a few months later? Are they trying to be like Google?)

Rewriting Favcharts - Part 1

Um in Form zu bleiben, widme ich möglichst jeden Tag 30-90 Minuten Nebenprojekten.

Diese Woche baue ich die favcharts neu. Die momentante Implementation der favcharts ist ein Haufen von “Hacks upon hacks”, weil die Charts aus einem Satz hastig programmierter Scripte und viel Zeitdruck entstanden sind. Es funktioniert irgendwie, aber so richtig super ist es nicht. Ein Rewrite ist schon seit einiger Zeit überfällig, jetzt ist es soweit.

Kurz zu den aktuellen favcharts: Der Code ist in PHP geschrieben, die Daten liegen in MySQL, klassischer LAMP-Stack also. Ein Crawler durchforstet regelmäßig die Favorites aller bekannten Nutzer und packt diese Daten in die Datenbank. Das Webfrontend liest aus der DB, macht Auswertungen, und speichert die Resultate dann zwischen, je nach Ansicht (täglich, monatlich, User-spezifisch, etc.) für ein paar Stunden bis mehrere Monate. Beim nächsten Zugriff wird dann nur der Cache gelesen, die Datenbank muss also nicht nochmal ran.

Soweit die Theorie. In der Praxis treten jedoch gehäuft Probleme auf: Viele Requests treffen den Cache gar nicht, weil z.B. jeder seine eigenen Charts abruft, und der User Cache dann irgendwann voll läuft und verdrängt wird. Oder es kommen Suchmaschinencrawler und fragen uralte Charts ab. Außerdem bieten die favcharts relativ viele Ansichten (verschiedene Zeiträume, verschiedene Gewichtungen, etc.), so dass die Anzahl der Cache-Einträge schnell hoch wird. Die Requests, die nicht vom Cache abgehandelt werden können, tun dann richtig weh: Je nach Scoring-Methode muss über riesige Datenmengen berechnet werden. Das kostet Zeit und I/O. Für die gewichtete Bewertung muss sogar das ganze Resultset zweimal durchlaufen werden. Dank mod_php wird dann für geraume Zeit ein Apache-Prozess blockiert und schluckt mehr und mehr Speicher und CPU, und der Rest der Apaches wird dadurch auch nicht schneller. In Stoßzeiten brauchen Requests so manchmal über zehn Sekunden. Der Crawlingprozess funktioniert dafür hervorragend - neue Favs sind oft schon nach wenigen Minuten erfasst. Das Datenbankzugriffsmuster ist allerdings eher ungesund: Gleichzeitig finden viele Writes (von den Crawlern) und viele lange Reads (vom Webfrontend) statt. Das führt zu Congestion, schlechten Indexen, und generell sehr unpraktischen Zugriffsmustern.

All das soll mit dem Rewrite besser werden. In den folgenden Posts beschreibe ich den Prozess des Rewrites, und mit welchen Ansätzen diese Probleme gelöst und in Zukunft verhindert werden sollen.

Und weil ich schon lange nichts mehr mit Ruby gemacht habe, und Rails nach wie vor “in” ist, wird die neue Version auf Ruby on Rails gebaut. Yee-haw.