Kategorien
Rails Server: Performance Optimierung
Performance Optimierung am Rails Server durch Passenger Konfigurationen
Im letzten Artikel habe ich gezeigt wie man eine Applikation auf dem Rails Server installiert. Heute werde ich darauf eingehen wie man die Performance mit dem Rails Server noch weiter steigern kann.
Als Beispiel nehme ich wieder die Applikation vereine.ch.
Stand der Applikation
Es wurden Query-Optimierungen mit DB-Indexes vorgenommen. Zudem profitieren wir vom Rails Caching im production Environment.
Da vereine.ch zuerst auf normalem Internet Standard Hosting betrieben wurde konnten keine Optimierungen an MySQL und Apache/Passenger Konfiguration gemacht werden. Auch auf Result Caching mit memcached mussten wir verzichten.
Messungen mit altem Hosting
Wir haben die meistbesuchten Pages von vereine.ch einem Stresstest unterzogen. Dazu wird jede Page parallel von mehreren Threads abgerufen. Als Resultate erhalten wir die Response Time der Applikation in MS. Wir verwenden hier den 90% Wert um noise auszufiltern.
Diese Resultate erhalte ich mit 5 Threads die je 10 Requests absetzen:

Hier ist eine kurze Erklärung der Abfragen angebracht.
- Live Search ist eine Abfrage auf Vereine mit Einschränkung auf Kategorie und Umkreis (geo-Daten). Es simuliert einen AJAX Request und lädt somit keine Layoutdaten. Ist auf 15 Resultate beschränkt.
- Kategorien zeigt mir die 15 Hauptkategorien und die Anzahl dazu eingetragener Vereine und Dienstleistungen an.
- Index ist ähnlich wie Live Search nur diesmal mit Layout.
- Kategorien Vereineinfo ist wieder ein AJAX Request der von der grössten Kategorie (mit ca 600 Vereinen) alle Vereine mit Link lädt. In den Resulateten zeigt sich hier eindeutiges Verbesserungspotenzial.
- More Results ist nochmal ein Live Search Request diesmal einfach mit offset.
- String Search macht auch eine Abfrage auf 15 Vereine, diesmal wird aber Fulltext Search über ein Textfeld verwendet.
Den Werten ist zu entnehmen, dass Kategorien Vereineinfo viel zu langsam lädt und auch Index mit über einer Sekunde relativ viel Zeit beansprucht. Die Anderen Pages sind mit einer Response Time von 300 bis 700 ms zwar annehmbar aber bestimmt nicht optimal.
Im zweiten Durchlauf erhöhen wir die Anzahl Threads auf 10 und belassen die Anzahl Requests pro Thread auf 10.

Die Resulate sind katastrophal. Die beste Response Time liegt bei 2.7 Sekunden und ist damit weit über dem Grenzwert für ein angenehmes Benutzererlebnis.
Wie lässt sich diese drastische Verschlechterung erklären?
Unser Internet Standard Hosting erlaubt es uns leider nur 2 Passenger Instanzen gleichzeitig laufen zu lassen. Da jede Instanz nur einen Request verarbeiten kann steigt somit die Response Time linear zur Anzahl Benutzer und erreicht somit sehr schnell eine kritische Länge.
Optimierung am Rails Server
Apache und Passenger
Wir wissen jetzt, dass die Anzahl Passenger Instanzen einen starken Einfluss auf die Performance einer Rails Applikation hat. Aber wie verhält sich das genau?
Eine Passenger Instanz ist ein Prozess der eine Kopie meiner Rails Applikation im RAM hält um möglichst schnell einen Request beantworten zu können. Das heisst wie viele Passenger Instanz ich starten kann hängt davon ab wie viel unbenutztes RAM mir auf meinem Server zur Verfügung steht. Wenn ich aber zu viele Instanzen starte fängt der Server an zu swappen was für die Performance noch schädlicher ist als zu wenig Passenger Prozesse zu haben.
Die perfekte Zahl von Instanzen hängt dabei natürlich immer von der Applikation ab. Um diese zu bestimmen sind die Befehel passenger-memory-stats und free -m sehr hilfreich. Wobei der Erste mir sagt wie viel RAM meine Applikation braucht und der Zweite mir sagt wie viel auf dem Server noch ungenutzt ist. Als Faustregel hat es sich bewährt 15 Instanzen pro GB Memory zu konfigurieren.
Um das so für vereine.ch zu Konfigurieren öffne ich das entsprechende Apache Configfile und füge folgendes zur VirtualHost Direktive hinzu:
PassengerMaxPoolSize 15
Hier sind noch ein paar weitere Passenger Optionen die sich für uns bewährt haben.
# Speeds up spawn time by a lot but some apps might be incompatible PassengerSpawnMethod smart
# ApplicationSpawner is kept alive. # This speeds up spawning of new Application Instances. RailsAppSpawnerIdleTime 0
# Application Instances are kept alive longer PassengerPoolIdleTime 1000
# Perform filesystem checks (i.e. check for restart.txt) only once every 5 seconds. # Default is on every request PassengerStatThrottleRate 5
# Explicitly specify that the host is a Ruby on Rails application. RailsAutoDetect off RailsBaseURI /
So, wie verhält sich unser Test mit 5 Benutzern à 10 Requests nun mit der neuen Konfiguration?

Man sieht jetzt schon eine klare Verbesserung gegenüber dem Standard Hosting. Die Response Time fast aller Requests ist stark gesunken und zwischen 100 – 400ms schon viel näher am Optimum.
Den massivsten Unterschied sieht man aber wenn man den Test mit 10 Benutzern à 10 Requests durchführt.

Die meisten Requests können hier eine Responsetime von unter einer Sekunde halten. Das ist schon eine massive Verbesserung wenn man bedenkt wie wenig Aufwand dazu nötig war.
Durch diese Resultate wird auch klar, dass der Engpass von Kategorien Vereineinfo in der Datenbank zu suchen sein wird.
Im nächsten Artikel werde ich Rails Server Optimierungen im Bereich Caching behandeln.
Rails Server: SSL für vereine.ch
In Rails Server: vereine.ch schnell und einfach deployed habe ich über das Deployment der vereine.ch Applikation gesprochen.
Da wir mit dieser auch https Logins erlauben wollen, muss ich den Webserver noch entsprechend Konfigurieren.
Dazu lege ich einen neuen VirtualHost auf Basis der normalen vereine.ch Konfiguration an.
cp /etc/apache2/sites-enabled/prod.vereine.conf /etc/apache2/sites-enabled/prod.vereine.conf_443
Dieser muss noch für SSL Zugriffe angepasst werden.
NameVirtualHost 193.247.72.65:443
<VirtualHost 193.247.72.65:443>
PassengerAppRoot /var/www/rails_apps/prod/vereine/
SSLEngine on
SSLCertificateFile /etc/ssl/certs/vereine.ch.crt
SSLCertificateChainFile /etc/ssl/certs/vereine.ch.ca
SSLCertificateKeyFile /etc/ssl/private/vereine.ch.key
...
</VirtualHost>
und damit ist vereine.ch auf schon unter https://vereine.ch erreichbar.
Rails Server: vereine.ch schnell und einfach deployed
Die vereine.ch Applikation in 10 Minuten auf den Citrin Rails Server migrieren.
Folgende Themen werde ich hier behandeln
- Installation einer bestehenden Ruby on Rails Applikation
- Übernahme der Datenbank
- Apache Konfiguration
Der Rails Server
Für den Betrieb von vereine.ch werden wir neu den Rails Server Standard verwenden. Dieser ist für 25 Fr./Monat zu haben und bietet root-Zugriff auf ein Rails-optimiertes Ubuntu mit 1 GB Memory und 15 GB Festplattenspeicher.
Wir versprechen uns von der Migration einen Gewinn in Performance und Wartbarkeit von vereine.ch. Zudem wollen wir dadurch in Zukunft Features ermöglichen, die mit herkömmlichem Hosting nicht umsetzbar wären.
Installation der Applikation
Da es ein bestehendes Projekt ist, haben wir bereits ein Subversion Repository und ich will nun die Applikation von da installieren. Dazu logge ich mich zuerst per SSH auf dem Rails Server ein.
ssh root@rh05.citrin.ch
Nun verwende ich den citrin Gem um mir einen neuen Apache VirtualHost anzulegen.
citrin create_webserver vereine
Das erstellt mir ein Apache Configfile und gibt mir an wohin ich meine Applikation installieren muss
Apache VirtualHost Konfiguration unter: /etc/apache2/sites-enabled/prod.vereine.conf App Root unter: /var/www/rails_apps/prod/vereine URL: http://vereine.rh05.citrin.ch
Ich wechsle also in das angegebene Verzeichnis und mache dort einen Checkout meines Subversion Projekts.
cd /var/www/rails_apps/prod/vereine svn co svn+ssh://svn.citrin.ch/svn/vereine/trunk vereine
Danach lasse ich bundler die für diese Applikation benötigten Gems nachinstallieren
cd vereine bundle install
Übernahme der Datenbank
Mit dem citrin Gem lege ich noch eine MySQL Datenbank für meine Applikation an.
citrin create_database vereine
Der Gem gibt mir auch gleich die Konfigurationsdaten so an, dass ich sie nur noch in mein config/database.yml übernehmen muss.
production: adapter: mysql2 encoding: utf8 database: vereine_prod username: vereine_prod password: ********
Nachdem ich das eingefügt habe importiere ich noch die Datenbank:
mysql -u vereine_prod -p vereine_prod < /var/tmp/vereine.sql
Die Applikation ist somit lauffähig und ich kann die automatisierten Tests laufen lassen.
rake test
Die Tests laufen Fehlerfrei durch. Damit ist vereine.ch fertig installiert.
Die Applikation kann auch schon über eine Subdomain des Servers erreicht werden: http://vereine.rh05.citrin.ch
Apache Konfiguration
Um die Applikation auch unter vereine.ch erreichbar zu machen braucht es noch eine kleine Anpassung am Virtualhost in /etc/apache2/sites-enabled/prod.vereine.conf.
Aus
<VirtualHost *:80> ServerName vereine.rh05.citrin.ch
wird
<VirtualHost *:80> ServerName vereine.ch ServerAlias www.vereine.ch *.vereine.ch vereine.rh05.citrin.ch
Mit einem /etc/hosts Eintrag kann ich die Konfiguration noch testen bevor ich die DNS Server umstelle.
193.247.72.65 vereine.ch
Auch hier läuft alles wie erwartet also lasse ich die DNS-Server von Citrin anpassen.
Das wars auch schon zum Thema Applikationsmigration. In einem Nachfolgeartikel werde ich auf die verschiedenen Möglichkeiten für Performance Optimierung mit dem Rails Server eingehen.
Wer allgemeinere Anleitungen zum Rails Server sucht findet diese hier.
Programme im Hintergrund laufen lassen mittels screen
screen ist ein nettes Tool, um mit mehreren Personen gemeinsam auf einem Bildschirm (engl. “screen”) zu arbeiten, genauer gesagt in einem Terminal (xterm oder Konsole spielt keine Rolle). Man kann so z.B. jemand beim Konfigurieren zuschauen (oder jemand zuschauen lassen, wie man etwas konfiguriert).
Auch sehr praktisch ist screen für unzuverlässige Verbindungen, z.B. per Interneteinwahl oder Wireless-LAN: bricht die Verbindung zum fern gewarteten Server ab, dann wählt man sich einfach wieder neu ein und schaltet sich erneut auf screen auf – die Bildschirminhalte sind dann genauso wie vor dem Abbruch!
screen ist ein sehr vielfälltiges Konsolen-Tool, welches sich für verschiedenste Aufgaben nutzen lässt.
Vorgehensweise:
- Verbindung zum Zielrechner aufbauen, z.B. mit OpenSSH.
- Starten tut man es einfach mit “screen”, ab dann arbeitet man auf einem von “screen” verwalteten screen
- Eine Liste von laufenden screens erzeugt screen -ls
- Multi-User-Mode: Wenn schon ein screen läuft, kann man sich einfach mit screen -r
draufschalten. - Wenn man sich nicht sicher ist, ob schon ein anderer screen läuft: screen -R erstellt bei Bedarf einen neuen.
- Wenn man fertig ist, einfach Verbindung abbrechen (ggf. vorher Strg-a d = detach).
Die wichtigsten Screen-Kommandos:
Kombination Kommando Beschreibung ctrl-a ? help Zeigt den eingebauten Hilfebildschirm ctrl-a 0 select Schaltet zum Bildschirm 0 ... select ... ctrl-a 9 select Schaltet um auf Bildschirm 9 ctrl-a A title Erlaubt dem User, dem aktuellen Bildschirm einen Namen zu geben. ctrl-a c create erzeugt einen neuen von Screen verwalteten Bildschirm ctrl-a d detach Beendet den screen prozess, der die Session und das Terminal verbindet ctrl-a n next Schaltet zum Bildschirm mit der nächst höheren Nummer ctrl-a p previous Schaltet zum Bildschirm mit der nächst niedrigeren Nummer ctrl-a x lockscreen Sperrt die Konsole, bis das Passwort des Benutzers eingegeben wurde ctrl-a <Leertaste> next Alternative zu n; u.U. handlicher zu bedienen ctrl-a " windowlist -b Listet alle Fenster der aktuellen Session ctrl-d exit Beendet die Session in Screen
Multi-Provider Authentisierung in Ruby on Rails
Moderne Applikationen erlauben es ihren Benutzern über Single sign-on Dienste zu authentisieren.
In diesem Guide erkläre ich wie man mit Ruby on Rails eine Applikation erstellt die Multi-Provider Authentisierung über OAuth und OpenID Dienste wie die von Google, Facebook oder Twitter erlaubt.
Ich verwende dafür die Plugins Devise und OmniAuth.
Devise
Devise ist ein Authentisierungs-Plugin für Rails. In diesem Guide verwende ich es für
- Benutzerkonten Verwaltung
- Session Verwaltung
- Als Schnittstelle zu OmniAuth
Ich definiere den devise gem in meinem Gemfile.
gem 'devise'
und installiere ihn mit bundler
bundle install
Danach führe ich den Generator aus um den Initializer zu erstellen.
rails generate devise:install
Den Initializer kann ich nun anschauen und bei Bedarf editieren:
vi config/initializers/devise.rb
Als nächstes füge ich devise dem User Model an (falls das Model noch nicht existiert wird es angelegt)
rails generate devise User
Damit wird auch ein migration File erstellt in welchem Helper Methoden für erweiterte Funktion von devise auskommentiert werden können.
create_table(:users) do |t|
t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
# t.encryptable
# t.confirmable
# t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
# t.token_authenticatable
t.timestamps
end
Für eine ausführliche Beschreibung der Optionen siehe: https://github.com/plataformatec/devise
Ich belasse die migration hier so wie sie generiert wurde und wende sie auf die Datenbank an.
rake db:migrate
OmniAuth
OmniAuth ist ein universell einsetzbarer Rubygem mit dem sich Multiprovider-Authentisierung realisieren lässt.
Erneut erweitere ich mein Gemfile. Diesmal um folgende Zeilen.
gem 'omniauth', :require => "omniauth/oauth" gem 'oa-openid', :require => 'omniauth/openid'
Und führe erneut bundler aus.
bundle install
Facebook (OAuth)
Ich will meinen Benutzern die Möglichkeit geben sich mit ihrem Facebook Account an meiner Applikation zu authentisieren. Dazu muss ich zuerst meine Applikation bei Facebook registrieren um eine App ID zu erhalten.
Danach konfiguriere ich diese in config/initializers/devise.rb
config.omniauth :facebook, "APP_ID", "APP_SECRET"
Danach muss ich dem Model (in unserem Fall User) noch sagen, dass es über omniauth authentisierbar ist.
devise :omniauthable
Zudem braucht mein Model noch eine Funktion die definiert was beim Facebook login geschehen soll.
In diesem Beispiel wird anhand der von Facebook erhaltenen E-Mail Adresse ein bestehender Benutzer gesucht. Falls keiner existiert wird ein neuer Angelegt.
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token['extra']['user_hash']
if user = User.find_by_email(data["email"])
user
else
User.create!(:email => data["email"], :password => Devise.friendly_token[0,20])
end
end
Jetzt muss meine Applikation nur noch die Callbacks verarbeiten können. Dazu ändere ich die devise_for Konfiguration in config/routes.rb zu:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
und erstelle den Callback Controller
# app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
@user = User.find_for_facebook_oauth(env["omniauth.auth"], current_user)
if @user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect @user, :event => :authentication
else
session["devise.facebook_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
vereine.ch
Ein Beispiel für die produktive Anwendung ist die von mir geschriebene Vereine Plattform vereine.ch
Diese erlaubt Login über konventionellen E-Mail Sign-Up, den Facebook OAuth Dienst und Google OpenID.
Update von CYAML auf YAML 3.3.1
Wir haben unser Projekt CYAML auf die neuste YAML Version 3.3.1 aktualisiert.
Zu diesem Anlass wollen wir auch einen kurzen Überblick über die Funktionen und den Einsatz von CYAML geben.
Was ist CYAML?
CYAML ist ein compass Plugin und bildet das YAML Framework in SASS/SCSS ab. compass ist ein Ruby gem der sowohl für Stand-Alone Layouts wie auch die Integration in Ruby on Rails verwendet werden kann.
Welche Vorteile bietet mir CYAML?
Mit CYAML ist es möglich sehr einfach einzelne Teile von YAML für die Gestaltung eines XHTML/CSS Layouts zu verwenden. Ziel ist es nur noch eine einzige css Datei zu haben, welche alle nötigen CSS Rules (ob Framework oder Projekteigene) beinhaltet und bei der Entwicklung trotzdem einzelne Stylesheet Files zu haben um Übersicht und Wiederverwendbarkeit zu gewährleisten. In der neusten CYAML Version werden sogar die Bilder des YAML Frameworks als inline-images im CSS integriert wodurch die Anzahl HTTP-Requests reduziert werden kann.
Wie verwende ich CYAML?
sudo gem install cyaml
Danach kann man ein neues Projekt erstellen:
compass create projectname -r cyaml --using
Jetzt noch die Standardkonfiguration an den richtigen Ort verschieben:
cd projectname mv .config.scss src/config.scss mv .basemod.scss src/basemod.scss
Um den Start zu erleichtern stellen wir ein vorgefertigtes 3-Spalten HTML Template bereit. Dieses kann kopiert werden:
cp citrin.html index.html
In src/basemod.scss können nun alle projektspezifischen CSS Regeln eingefügt werden.
Um mehr Teile des YAML Frameworks zu verwenden kann man einfach bei den entsprechenden Zeilen in src/default_includes.scss die Kommentar-Zeichen entfernen.
Werte wie Header und Content-Höhe oder die Breite der Content-Spalten können als Variabeln in src/config.scss gesetzt werden.
Nachdem man die SCSS-Files nach seinen Wünschen angepasst hat kompiliert man noch alles in ein einzelnes CSS File (in diesem Fall stylesheets/basemod.css)
compass compile
Das Repository und die Dokumentation findet man auf github: https://github.com/servasat/cyaml
SMS via aspsms versenden mit PHP via SOAP
aspsms bietet die Möglichkeit SMS über verschiedene Schnittstellen zu versenden.
Eine Möglichkeit ist die Verwendung der SOAP-Web-Schnittstelle. Damit wird der Kern einer PHP-Applikation sehr trivial:
$sms_sender = new SoapClient(
'http://webservice.aspsms.com/aspsmsx.asmx?WSDL'
);
$params = array(
'UserKey' => 'XXXXXXXXXXX',
'Password' => 'xxxxxxxxxxxx',
'Recipient' => $empfaenger,
'Originator' => $sender,
'MessageText' => $nachricht
);
$result = $sms_sender->SimpleTextSMS($params);
Einen benutzbaren PHP-Client inkl. Shell-Script für den Aufruf kann frei und auf eigenes Risiko verwendet werden. Einzig ein Userkey und Passwort des zu benutzenden aspsms-Accounts ist im Code noch einzutragen.
Voraussetzung: In PHP muss die SOAP-Extension geladen sein.
Unsere Kunden benötigen übrigens keinen eigenen aspsms-Account, sondern können über unseren Webservice direkt mit dem E-Mail Account SMS versenden.