vsftp unter Opensuse 12.2 und 12.3

Ich bin gerade in der Situation, für Entwicklungszwecke parallel zwei Web-Testserver aufsetzen zu müssen – einen unter Opensuse 12.2 und einen unter Opensuse 12.3. Entwickler sollen per FTP Files in Verzeichnisse unterhalb von

/srv/www/htdocs/webs

auf die Apache Testservern hochladen. Die dortigen Verzeichnisse sind “named virtual domains” des Webservers zugeordnet. Neu entwickelte PHP-Programme sollen unter diesen Domainen für unsere Kunden getestet werden.

Der jeweils dedizierte und einzige FTP-User auf diesen Systemen hatte natürlicherweise ein zunächst leeres Home-Verzeichnis

“/srv/www/htdocs/webs”

erhalten. Bzgl. der ihm zugeordneten Shell habe ich “/bin/false” definiert. Die Entwickler sollen sich über den FTP-Account auf dem Server-System nicht in eine Shell einloggen können.

Als FTP-Serverdienst wollte ich zur Abwechslung mal “vsftp” mit SSL/TLS [ftps] statt “sftp” aus der openssh-Suite aufsetzen. Dabei erlebte ich so einige Überraschungen, die vielleicht auch für andere interessant sind, die sich an vsftp heranwagen oder von früheren Opensuse-Versionen auf OS 12.3 upgraden wollen.

Sicherheit spielt in meinem Fall für die Testserver, die nur aus einem lokalen Netz zugänglich sind, zwar nicht eine essentielle Rolle, aber wenn man einen solchen Service aufsetzt, dann natürlich zur Übung gleich auf TLS-Basis.

Die wichtigsten Statements der “/etc/vsftp.conf” sehen daher wie folgt aus:

write_enable=YES
dirmessage_enable=YES
nopriv_user=ftpsecure
local_enable=YES
anonymous_enable=NO
anon_world_readable_only=YES
syslog_enable=NO
connect_from_port_20=NO
ascii_upload_enable=YES
pam_service_name=vsftpd
listen=YES
pasv_min_port=30000
pasv_max_port=30100
anon_mkdir_write_enable=NO
anon_upload_enable=NO
ssl_enable=YES
rsa_cert_file=/etc/ssl/servercerts/servercert.pem
rsa_private_key_file=/etc/ssl/servercerts/serverkey.pem
ssl_sslv2=NO
ssl_sslv3=NO
ssl_tlsv1=YES
 
#require_ssl_reuse=NO
 
chroot_local_user=YES
local_root=/srv/www/htdocs/webs
hide_ids=YES
ftpd_banner= mysysb vsFTP Server
idle_session_timeout=900
hide_ids=YES
log_ftp_protocol=NO
max_clients=20
max_per_ip=5
pasv_enable=YES
anon_root=/srv/ftp
anon_umask=022
local_umask=006

und abhängig von der Opensuse-Version:

seccomp_sandbox=NO [für Opensuse 12.3]

Wichtiger Hinweis:
Nach meiner Erfahrung ist es leider so, dass der letzte Parameter bei Opensuse 12.3 auf NO gesetzt werden muss. Das gilt nicht für Opensuse 12.2 und Opensuse 13.1. Dort sollte man den Standard nehmen bzw. das Statement ganz weglassen. Die aktuellen man-pages führen diesen Parameter gar nicht mehr auf !

Natürlich kann man sich über das “chroot_local_user=YES” und die Tatsache, dass nicht mehrere virtuelle User angelegt wurden, vielleicht streiten. Aber wir wollen ja keine userspezifischen Daten-Container aufbauen, sondern gemeinsam genutzte Web-Test-Domainen versorgen.

Nicht ganz unwichtig ist hier das Statement

local_root=/srv/www/htdocs/webs

das auch künftige andere User auf ein ganz bestimmtes Verzeichnis – hier ist das identisch mit dem Home-Verzeichnis des FTP-Users – einschränken würde.

Übrigens: Da der FTP-User sich ja sowieso nicht einloggen darf, kann
man den Owner und die Gruppe des Verzeichnisses in unserem Fall auch auf “root:root”, z.B. mit Mode 751, ändern.

# chown root.root /srv/www/htdocs/webs
# chmod 751 /srv/www/htdocs/webs

Man muss das aber nicht. Wichtig ist hingegen der Entzug der Schreibrechte für unseren FTP-User – auch wenn das zunächst paradox wirken mag. Letzteres ist aus Sicherheitsgründen wichtig. Sie die Anmerkungen weiter unten.

Ein weiterer Hinweis:
Konfiguriert man vsftp mittels YaST2, so wird für TLS unbedingt ein File zu einem DSA-Key gefordert. Die YaST-CA-Verwaltung legt aber Serverzertifikate RSA-basiert an. Ich helfe mir über diese Klippe dadurch weg, dass ich das pem-File des RSA-Keys angebe, danach dann die Datei “vsftp.conf” editiere und die Statements zum Zertifikats- und Key-File wie oben gezeigt auf “RSA” abändere:

rsa_cert_file=/etc/ssl/servercerts/servercert.pem
rsa_private_key_file=/etc/ssl/servercerts/serverkey.pem

Die Kombination “local_enable=YES” und “ssl_enable=YES” lässt keine unverschlüsselten Sitzungen mehr zu

Hat man SSL aktiviert, werden unverschlüsselte Sitzungen nicht mehr zugelassen, wenn man sich über einen lokalen, nicht-anonymen User authentifizieren will. Das erscheint völlig logisch. Und so wunderte ich mich nicht, dass beim Versuch einer unverschlüsselten Verbindung folgende Meldung in FTP-Clients, wie Filezilla, hochkommt:

Response: 530 Non-anonymous sessions must use encryption.
Error: Could not connect to server

Eine klare und verständliche Meldung. Also “Explizites TLS” für die gewünschte FTP-Verbindung anfordern ! In Filezilla kann man das sehr einfach bei der Definition seiner FTP-Server hinterlegen.

Der FTP-User darf keine Schreibrechte auf sein chroot-Verzeichnis haben

Eine erste, wenn auch nicht wirklich überraschende Erkenntnis unter OS 12.2 und 12.3 war, dass man im Falle von

chroot_local_user=YES

aus Sicherheitsgründen gehindert wird, auf ein chroot-Verzeichnis eingeschränkt zu werden, auf dem der FTP-User Schreibrechte hat.

Aber Achtung: Das ist in unserem Beispiel das Verzeichnis aus dem Statement

local_root=/srv/www/htdocs/webs

Das wäre auch so, wenn der FTP-Account ein tieferliegendes Heimatverzeichnis hätte !
Merke:
Das Verzeichnis aus dem “local-root”-Statement kann durchaus ein anderes als das Heimatverzeichnis des FTP-Users sein! Passt man hier nicht auf, kann es bzgl. erforderlicher Rechtesetzungen schon mal zu Missverständnissen und Fehlern kommen. Der zu authentifizierende FTP-User darf bei Benutzung von “local_root” keine Scheibrechte auf demjenigen Verzeichnis haben, das in diesem Statement angesprochen wird. Die Rechte bzgl. eines ggf. abweichenden Heimatverzeichnis ist bei gesetztem “local_root” zweitrangig !

Die Einschränkung bzgl. der Schreibrechte versteht man! Das ist übrigens schon seit der vsftp-Version 2.5.x so und ist u.a. auch bei der Einrichtung aktueller sftp-Varianten mit chroot-Definitionen zu beachten.

Man kann das Verweigern einer Verbindung zu einem beschreibbaren chroot-Verzeichnis unter aktuellen vsftp-Versionen auch nicht mehr wie früher durch irgendwelche zusätzlichen Parameter wie “allow_writeable_chroot=YES” umgehen. Was dann natürlich Konsequenzen für das Setup der Verzeichnisstruktur hat, die von außen per FTP zugänglich sein soll.

Das eigentliche Problem ist aber, dass man als vsftp-Unbedarfter ggf. nicht entfernte Schreibrechte auf dem chroot-Verzeichnis nicht unbedingt als die eigentliche Ursache von Fehlermeldungen erkennt ! Solche Rechte können z.B. auch über eine unglücklich gesetzte Gruppenzugehörigkeit
ins Spiel kommen. Genau das war bei mir der Fall.

Hat man nun unglücklicherweise bei der Einrichtung des FTP-Users

  • entweder Standardrechte auf seinem Heimatverzeichnis gesetzt und belassen,
  • oder bei der Zuordnung zu einer Gruppe Schreibrechte auf dem chroot-Verzeichnis übersehen,
  • oder nicht erkannt, dass es in der obigen Konfiguration um die Schreibrechte auf dem Verzeichnis aus dem “local_root”-Statement und nicht wirklich um das Heimatverzeichnis des FTP-Users geht,

so kann das unter bestimmten Umständen in Filezilla und unter OS 12.2 zu TLS-bezogenen Fehlermeldungen führen, was dann doch sehr missverständlich ist. Ich jedenfalls bekam beim Experimentieren mit sftp haufenweise TLS-Fehlermeldungen, die meist mit dem eigentlichen Problem – wie hier einer risikoreichen Zugangsberechtigung – im Kern gar nichts zu tun hatten und in die Irre führten. [Die Funktionstüchtigkeit seiner TLS-Einrichtung und seiner Zertifikate sollte man natürlich auch mal unabhängig von vsftp testen!]

Auf dem OS 12.3 erhielt ich dann aber nach einigen Experimenten in einer aktuellen Filezilla-Version die Meldung:

500 OOPS: vsftpd: refusing to run with writable root inside chroot ()

Die half dann wirklich weiter. Weiteren Aufschluss brachten folgender Artikel und die dortige Diskussion
https://www.benscobie.com/fixing-500-oops-vsftpd-refusing-to-run-with-writable-root-inside-chroot/

sowie ein genereller Artikel zu Sicherheit im Zshg. mit chroot()
http://www.onlamp.com/excerpt/PUIS3_chap16/index3.html

Hilfreich war zudem ein Blick in die Original-FAQ-Hinweise zum akt. vsftp-Packet in der Version 3.0.2 unter
vsftpd.beasts.org/users/cevans/untar/vsftpd-3.0.2/FAQ
aus der ich nachfolgend zitiere:

Q) Help! I’m getting the error message “refusing to run with writable root”.
 
A) vsftpd is protecting against dangerous configurations. The cause of this message is usually dodgy ownership of the ftp home directory. The home directory should NOT be owned by the ftp user itself. Neither should it be writable by the ftp user. A way to fix this is:
chown root ~ftp; chmod -w ~ftp
Another cause might be an attempt to use chroot_local_user without setting up the directory ownership properly.

Ok, das ist klar. Natürlich sollte ein FTP-User nicht an seinem Root-Verzeichnis herumpfuschen können. Das gilt, wie gesagt, auch für sftp-Installationen aus der openssh-Suite (s. etwa: http://www.techrepublic.com/blog/opensource/chroot-users-with-openssh-an-easier-way-to-confine-users-to-their-home-directories/229)

Also verändert man in Fällen, die meiner Konstellation vergleichbar sind, am besten die Owner und die Rechte auf dem local_root-Verzeichnis, wie oben beschrieben.

Ein weiteres interessantes Thema:

Q) Help! What are the security implications referred to in the “chroot_local_user” option?
A) Firstly note that other ftp daemons have the same implications. It is a
generic problem.

The problem isn’t too severe, but it is this: Some people have FTP user accounts which are not trusted to have full shell access. If these accounts can also upload files, there is a small risk. A bad user now has control of the filesystem root, which is their home directory. The ftp daemon might cause some config file to
be read – e.g. /etc/some_file. With chroot(), this file is now under the control of the user. vsftpd is careful in this area. But, the system’s libc might want to open locale config files or other settings…

OK, hieraus verstehe ich, dass User mit “Shell” = “/bin/false” unter gewissen Umständen zu einem Problem werden können. (Auf anderen als Opensuse Systemen muss man ggf. /bin/fale auch in die Datei /etc/shells aufnehmen).

Tatsächlich ist es mir in einigen ersten schnell hintereinander durchgeführten experimentellen Anläufen mit Filezilla unter OS 12.2 gelungen, Dateistrukturen oberhalb des chroot-Verzeichnisses zu sehen, die ich eigentlich nicht mehr hätte sehen dürfen. Ich hatte dabei zunächst TLS abgeschaltet und den FTP-User auf “/bin/false” gesetzt und keine chroot-Einschränkung vorgenommen. Danach bei laufender Filezilla-Sitzung “chroot_local_user=YES” auf dem Server gesetzt und den FTP-Service per “systemctl restart vsftp.service” neu gestartet. Die Filzilla-Sitzung wird dabei unterbrochen – man kann sie aber sofort restarten. Und dann geschehen manchmal die seltsamsten Dinge bzgl. der Anzeige des Dateibaums. Und man sieht, wie gesagt, ggf. Dinge, die man nun nicht mehr sehen sollte. Irgendwie waren dabei wohl Filezilla- und Server-Caches im Spiel. Ein Refresh in Filezilla führte nämlich jedesmal zur Reduktion der Filesystem-Darstellung.

Was immer man daraus schließen mag … ich sehe es als jedenfalls wichtig an, dass an an chroot-Konfigurationen nicht im lauenden Betrieb rumgebastelt wird, wenn nicht sichergestellt ist, dass dann alle potentiellen Zwischenspeicher geleert sind. Man muss halt den svftp-Service sauber aufsetzen und nicht im produktiven Betrieb an Berechtigungen rumbasteln. Zudem ist der oben diskutierte grundsätzliche Entzug der Ownerschip und der Schreibrechte am chroot-Verzeichnis auch hier von Bedeutung: Waren die Rechte von vornherein richtig, d.h. restriktiv gesetzt, traten die beobachteten Probleme mit der Sicht auf unerlaubte Verzeichnisbereiche erst gar nicht auf.

Konsequenz: Entzug der Schreibrechte

Man nimmt dem FTP-User und auch der Gruppe, der er zugeordnet ist, die Schreibrechte am Heimatverzeichnis. Hier “/srv/www/htdocs/webs” und legt darunter ein oder mehrere schreibbare Verzeichnisse an. In meinem Fall kein Problem. (Was aber machen Leute, die nach einem Upgrade vorhandene Verzeichnisstrukturen nicht umorganisieren können oder wollen ?)

Unter OS 12.2 lief das Ganze dann in meinem Beispiel auch ohne Problem mit der dortigen PAM, der vsftp-Version 3.0.2-3.4.1 und dem Kernel 3.4.6.

Identische vsftp-version Konfiguration wie unter OS 12.2 läuft nicht unter OS 12.3 und Kernel 3.7.10

Nachdem der vsftp-Server unter OS 12.2 dann endlich so lief, wie ich mir das wünschte, und die Rechte auf hochgeladene Verzeichnisse und Dateien per vsftp-umask, SGID-Bit und setfacl so erzeugt wurden, wie ich das für sinnvoll und notwendig befand, wollte ich die gleiche Konfiguration auch unter Opensuse 12.3 zum Laufen bringen.

Erstmal ging zu meinem Schrecken aber gar nichts. Hier ein Auszug aus der Filezilla-Kommunikation mit dem Server:

Status: TLS/SSL-Verbindung hergestellt.
Antwort: 331 Please specify the password.
Befehl: PASS *******
Antwort: 230 Login successful.
Befehl: SYST
Antwort: 215 UNIX Type: L8
Befehl: FEAT
Antwort: 211-Features:
Antwort: AUTH TLS
Antwort: EPRT
Antwort: EPSV
Antwort: MDTM
Antwort: PASV
Antwort: PBSZ
Antwort: PROT
Antwort: REST STREAM
Antwort: SIZE
Antwort: TVFS
Antwort: UTF8
Antwort: 211 End
Befehl: OPTS
UTF8 ON
Antwort: 200 Always in UTF8 mode.
Befehl: PBSZ 0
Antwort: 200 PBSZ set to 0.
Befehl: PROT P
Antwort: 200 PROT now Private.
Status: Verbunden
Status: Empfange Verzeichnisinhalt…
Befehl: CWD /
Antwort: 250 Directory successfully changed.
Befehl: PWD
Antwort: 257 “/”
Befehl: TYPE I
Antwort: 200 Switching to Binary mode.
Befehl: PASV
Fehler: GnuTLS error -15: Ein unerwartetes TLS-Paket wurde empfangen.
Fehler: Verbindung zum Server getrennt: ECONNABORTED – Connection aborted
Fehler: Verzeichnisinhalt konnte nicht empfangen werden

Nach etlichen Recherchen fand ich dann, dass ab Kernel 3.5.X wohl der Parameter zur seccomp sandbox

seccomp_sandbox=NO

auf NO gesetzt werden muss, was ich allerdings für produktive Server etwas beunruhigend finde. Siehe auch:
https://bugzilla.redhat.com/show_bug.cgi?id=845980
und
https://bugzilla.novell.com/show_bug.cgi?id=806758
und
https://bugzilla.novell.com/show_bug.cgi?id=786024.

Jedenfalls können wir mit der genannten Einstellung dann auch unter OS 12.3 auf den vsftp-Server zugreifen.

Problem mit syslog_enable=YES

Als ich dann jedoch

syslog_enable=YES

setzen wollte, kam die nächste Überraschung. In Filezilla taucht danach nämlich folgende Meldung auf :

Status: Connecting to 192.168.0.37:21…
Status: Connection established, waiting for welcome message…
Response: 500 OOPS: priv_sock_get_cmd
Error: Critical error
Error: Could not connect to server

Tja, und dafür habe ich keine Lösung gefunden als eben

syslog_enable=NO

Logging muss man dann eben anders machen. Kann ich aber mit leben ….

Ältere Filezilla-Versionen vertragen die neue Standardoption “require_ssl_reuse=NO” nicht

Bei Filezilla-Versionen 3.5.X kommt es zu Problemen mit der FTP-Verbindung, wenn

require_ssl_reuse=YES

gesetzt ist. Das ist aber in den aktuellen vsftp-Versionen als “Default” der Fall. Hier hilft dann im Falle einer Fehlermeldung ein explizites :

require_ssl_reuse=NO

oder man muss sich unter OS 12.3 die aktuellere Filezilla Version 3.7.0.1 von folgendem Repository

http://download.opensuse.org/repositories/network/openSUSE_12.3/

installieren. Mit dieser neueren Filezilla-Version geht dann auch “require_ssl_reuse=YES”.
(Ergänzung 08.06.2013: Es geht zumindest besser. Gestern bekam ich aber auch mit der neuen Filezilla-Version bei einem längeren Transfer ein Problem.)

Fazit

Ich verstehe das Bestreben der vsftp-Entwickler nach maximaler Sicherheit gut. Aber das sollte nicht zu so vielen Problemen mit aktuellen Distributionen führen. Die Doku dieser Distributionen muss diesbzgl. auch besser werden und die erforderlichen Einstellungen und potentiellen Probleme in den Release-Notes beschreiben.

Wenn ich mal meine Erfahrungen auf produktive Systeme übertrage, stelle ich mir allerdings die Situation als schlimm vor, die entsteht, wenn solche Systeme auf OS 12.3 mit 3.5-Kernels upgegraded werden. Das dann entstehende Desaster hat evtl. schon einige Admins in den Wahnsinn
getrieben. Es war auf Basis der Fehlermeldungen nämlich nicht immer so leicht, sich die richtigen Einstellungen zusammenzureimen. Nicht gerade Werbung für Opensource !

Abschließender Hinweis zu vsftp-Tests mit Änderungen der Credentials des FTP-Users auf LDAP-Servern

Ein abschließender Hinweis noch zu Experimenten mit Änderungen an den Shell-Zuordnungen oder an den Credentials des FTP-Users:

Ich hatte meinen FTP-User über einen separaten LDAP-Server konfiguriert. Unter Opensuse 12.3 kommt SSSD zum Einsatz. In der Standardkonfiguration hatte SSSD bei mir auf dem Server mit vsftp die Credentials gecacht. Dann kann es bei geänderten Passwörtern zu Problemen kommen, wenn der Remote-FTP-Client das neue und auf dem LDAP-Server auch geänderte Passwort benutzt, aber der vsftp-Server wegen des sssd-Chachings von dem neuen Passwort ggf. noch nichts mitbekommen hat!

In einer solchen Situation muss man die SSSD-Cache-Information, z.B. zu einem User, gezielt mittels des Kommandos

sss_cache -u USER

löschen. Unter USER ist die User-Id anzugeben.
(Das Thema der Verwendung gechachter Information durch SSSD hat mich beim Testen von Zugriffsberechtigungen für bestimmte vsftp-User mal kurzzeitig außer Fassung gebracht und mir gezeigt, das sssd-Caching auch mal ein substanzielles Problem darstellen kann !)

Zu anderen Varianten des sss_cache Kommados für das Purgen von Cache-Daten in bestimmten SSSD-Domainen sehe man in die man-Seiten und
https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/sssd-cache.html

Unter Opensuse muss man übrigens das Paket “sssd-tools” installieren, um “sss_cache” nutzen zu können.

Ergänzung 04.09.2013 zu Firewall-Einstellungen:
Wegen eines aktuellen Erlebnisses: Natürlich muss man auch die Firewalls sowohl auf dem Server als auch auf dem Client angemessen einstellen. So ist auf dem Server der Port 21 für die Clients zu öffnen – wie auch der Range der Ports > 1024, die für den passiven FTP-Austausch benutzt werden. Die entsprechenden Port-Festlegungen hat man in der “/etc/vsftp.conf” vorgenommen. Standardmäßig ist dort der Range

pasv_min_port=30000
pasv_max_port=30100

eingestellt. Aber natürlich kann ma auch einen anderen Range wählen. Man darf dann nur nicht vergessen, die Firewall(s) anzupasssen !

Ergänzung 07.11.2014 zum Parameter connect_from_port_20
Ein Leser hat mich darauf aufmerksam gemacht, dass die Einstellung

connect_from_port_20=NO

ein kleinen Sicherheitsvorteil bringen mag. Das wird ein wenig kontrovers diskutiert. Siehe z.B.:
http://forums.opensuse.org/showthread.php/466059-VSFTP-Not-Allowing-External-Connections
In vielen vsftpd Installationsanweisungen im Internet findet man aber die Einstellung “NO”. Da ich in der NO-Einstellung auch keinen gravierenden Nachteil erkennen kann, habe ich die oben angegebenen Einstellungen entsprechend abgeändert.

PHP-Applikationen und Caching – II

Im letzten Beitrag dieser Serie zu verschiedenen Caching-Verfahren für PHP-Applikationen hatte ich bereits einige Verfahrensansätze theoretisch vorgestellt und deren Vor- und Nachteile diskutiert. Bevor ich in weiteren Beiträgen einzelne Verfahren betrachte, erscheint es mir in einem Linux-Blog sinnvoll, kurz auf die Voraussetzungen einer Linux-Testumgebung – im besonderen eines Apache2-Servers – einzugehen.

LAMP

Aus meiner Sicht ist zum Testen vpn PHP-Caching-Verfahren eine typische, lokal installierte LAMP-Umgebung mit phpMyAdmin völlig ausreichend. Man muss nicht unbedingt ein Netzwerk zur Verfügung haben. Unter der Rubrik “LAMP / Webentwicklung” habe ich bereits früher ein paar Beiträge zusammengestellt, die beschreiben, man sich die Komponenten einer LAMP-Umgebung unter Opensuse lokal auf einem Entwicklungs-PC oder einem Laptop einrichten kann. Die Grundaussagen der Artikel

https://linux-blog.anracom.com/category/php-und-mysql/apache/
https://linux-blog.anracom.com/2010/08/28/lokale-mysql-phpmyadmin-installation/

gelten im wesentlichen auch unter Opensuse 12.2.

Testapplikation

Ein PHP-Entwickler, der sich mit Caching befassen will, hat sicher auch die eine oder andere datenbankgestützte PHP-Applikation parat,

  • deren Einsatz den Server belastet und messbar Serverzeit kostet,
  • die Webseiten als Output liefert
  • und deren Datenbestand gepflegt werden kann.

Falls nicht, empfehle ich, sich eine einfache Applikation zu basteln, die eine Datenbanktabelle abfragt und das Listenergebnis schlicht in formatierter Form auf einer HTML-Seite ausdruckt. Durch einfach Loops mit “sinnlosen” aber zeitintensiven Operationen für unsere Tests erzeugt man Serverlast in einem spürbaren Bereich. Z.B. kann man ja die Datenbank-Tabelle Liste nicht nur einmal sondern 100 mal abfragen :-). Das reicht, um die Grundlagen zu studieren. Hat man keine eigene Applikation zur Datenbestandspflege, so greift man für gezielte Modifikationen einiger Datenbankeinträge eben auf phpMyAdmin zurück.

Auswertungstools am Browser

Bzgl. der Anzeige und des Auswertens von Caching-Informationen am Browser lohnt sich ein Blick auf die Entwicklertools von Firefox und im besonderen von Chromium/Chrome.

Die Chrome-Browser bringen für die Darstellung der Transferzeiten zum/vom Server, zur Cache-Nutzung und zu lokalen Aufbauzeiten für eine Webseite sehr schöne Grafik-Tools mit, die ich persönlich sehr schätze. Man erreicht die Tools über den Menüpunkt
“Tools  >>  Entwicklertools  >>  Network”.

Im Firefox sollte man sich folgende AddOns installieren und sich auch mit der “Web-Konsole” vertraut machen:

  • Firebug – (akt. Vers.: 1.11.1) – hier erhält man auch grafische Tools zur Analyse der Transferzeiten von und zum Server
  • Web Developer – (akt. Vers.: 1.2.2) – ziemlich umfassende Toolpalette für Web-Entwickler, die per Menü oder Toolbar zugänglich ist
  • ggf. : Toggle Web Developer Toolbar 4 – ein Button zum An/Abschalten des Developer-Toolbars

Die AddOn-verwaltung findet sich im Linux-FF unter dem Menüpunkt “Extras  >>  Entwicklertools  >>  AddOns ”

Vergleichsweise muss man die Wirkung des Cachings aber auch auf dem MS IE 8/9
nachvollziehen. Dazu nutzt man die sog. “Windows Internet Explorer Developer Tools” und im besonderen das “Network Capture”-Tool. Ich gehe auf deren Verwendung nicht genauer ein. Mehr Informationen erhält man aber unter

http://msdn.microsoft.com/de-de/library/ie/gg589512%28v=vs.85%29.aspx
und im besonderen unter
http://msdn.microsoft.com/de-de/library/ie/gg130952%28v=vs.85%29.aspx

Voraussetzungen für Experimente auf einem eigenen Apache 2-Server

Zur Grundeinrichtung eines Apache-Serves siehe
http://doc.opensuse.org/documentation/html/openSUSE/opensuse-reference/cha.apache2.html
http://thomas-huehn.de/web/caching-tutorial/#impl-server
oder
https://linux-blog.anracom.com/category/php-und-mysql/apache/

Welche Einstellungen man nach der Grundkonfiguration eines eigenen Apache2-Server eigentlich für Caching-Experimente verwenden? Einerseits müssen Header-Anweisungen für den Einsatz der verschiedenen Caching-Verfahren vom Web-Server für eine gegebene (virtuelle) Web-Domaine unterstützt werden. Andererseits muss der Server Anfragen von Clients mit Bezug zum Ablaufdatum von Seiten korrekt abhandeln und die richtigen Header-Antworten für die Clients generieren.

Auf einem Apache-Server müssen dazu bestimmte Module geladen sein, nämlich:

  • header
  • expires

Siehe hierzu:
http://httpd.apache.org/docs/2.2/mod/mod_headers.html
http://httpd.apache.org/docs/2.2/mod/mod_expires.html

Diese Module fügt man unter Opensuse entweder händisch in die Konfigurationsdatei “/etc/sysconfig/apache2” ein, oder benutzt YaST oder aber verwendet “a2enmod” auf der Kommandozeile zur Apache-Konfiguration. Zu “a2enmod” siehe die man-Seiten oder
http://www.mediamill.de/blog/2008/04/22/aktivieren-und-deaktivieren-von-apache2-modulen-a2enmod-a2dismod/
http://linuxpoison.blogspot.de/2010/07/how-to-enable-disable-modules-into.html
http://doc.opensuse.org/documentation/html/openSUSE/opensuse-reference/cha.apache2.html

Ferner muss (!) für die auf dem Server unterstützten virtuellen Domainen oder aber global angegeben werden, dass “Expiration”-Hinweise unterstützt werden sollen. Hierfür ist die Anweisung

ExpiresActive On

zuständig. Sie ist in den globalen Konfigurationsdateien des Apache-Servers und/oder in den Dateien/Konfigurationsabschnitten für die virtuellen Domainen zu setzen.

Dann findet sich ggf. noch das spezielle Thema eines lokalen Apache2 auf einem Entwicklungs-PC mit SSL. Wie und wo man ein Dummy-Zertifikat anlegt, steht unter
https://linux-blog.anracom.com/category/php-und-mysql/apache/.

Ich gebe zur Orientierung nachfolgend ein paar Auszüge aus relevanten Dateien für den lokalen Apache2-Server an, den ich auf meinem Opensuse 12.2-betriebenen Entwicklungs-Laptop mit mir rumschleppe:

Auszug “/etc/sysconfig/apache2”:

APACHE_MODULES=”actions alias auth_basic authn_file authz_host authz_groupfile authz_default authz_user autoindex cgi dir env expires include log_config mime negotiation setenvif ssl suexec status userdir asis imagemap php5 perl proxy proxy_ajp deflate headers rewrite proxy_balancer"
 
APACHE_SERVER_FLAGS=”SSL”

Auszug listen.conf:

Listen 80
 
# RMO 443: SSL richtet sich nach den Suse Sysconfig-Variablen und dem entsprechenden Startup-Script
<IfDefine SSL>
  <IfDefine !NOSSL>
    <IfModule mod_ssl.c>
      Listen 443
    </IfModule>
  </IfDefine>
</IfDefine>
 
ExpiresActive   On

Die letzte Zeile wirkt global. Will man das nicht, muss man die Zeile weglassen.

Auszug aus “/etc/apache2/vhosts.d/vhost-ssl.conf”:

<VirtualHost *:443>
 
  # General setup for the virtual host
    DocumentRoot “/srv/www/htdocs/Entwicklung/myproject/main/”
    ServerName myproject
    ServerAdmin admin@mymaindomain.com
    ErrorLog /var/log/apache2/error_log
    TransferLog /var/log/apache2/access_log
 
    ExpiresActive   On
 
    #   SSL Engine Switch:
    SSLEngine   on
 
 
    # SSL Cipher Suite:
    SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
    # Server Certificate:
    SSLCertificateFile /etc/apache2/ssl.crt/server.crt
    # Server Private Key:
    SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
    # SSL Engine Options:
    #SSLOptions +FakeBasicAuth +ExportCertData +CompatEnvVars +StrictRequire
    <Files ~ “\.(cgi|shtml|phtml|php3?)$”>
       SSLOptions +StdEnvVars
    </Files>
    <Directory “/srv/www/cgi-bin”>
       SSLOptions +StdEnvVars
    </Directory>
    # SSL Protocol Adjustments:
      SetEnvIf User-Agent “.*MSIE.*” \
         nokeepalive ssl-unclean-shutdown \
         downgrade-1.0 force-response-1.0
    # Per-Server Logging:
    CustomLog /var/log/apache2/ssl_request_log ssl_combined
 
</VirtualHost>

Hier wird die für Caching-Tests wichtige Option “ExpiresActive On” explizit für die Test-Domaine namens “myproject” gesetzt.

Damit haben wir nun alle Voraussetzungen beieinander, um im nächsten Beitrag mit spezifischen PHP-Caching-Verfahren und zugehörigen Tests zu beginnen.

PHP-Applikationen und Caching – I

Einige Web-Hosting-Anbieter – u.a. 1&1 – haben mit ihren aktuellen Webhosting-Angeboten eine Politik verbunden, die die Performance eines angebotenen Web-Hosting-Leistungspakets von dessen Preis abhängig macht. Dies wirkt sich vor allem bei Web-Seiten aus, die dynamisch mittels PHP, Perl oder anderen serverbasierten Scriptsprachen auf der Basis von Datenbankdaten generiert werden.

Einige preisbewusste Kunden diskutieren deshalb immer häufiger mit uns, was man tun kann, um die Performance von serverbasierten Web-Applikationen zu verbessern. In unserem Fall sind dies vor allem PHP5-Applikationen. Wir meinen, dass in vielen Fällen ein systematischer und situationsgerechter Einsatz von verschiedenen “Caching”-Verfahren zu einer substanziellen Verbesserung der Situation führen kann.

Ich stelle deshalb in diesem und in nachfolgenden Artikeln einige Caching-Ansätze für PHP-Applikationen zusammen, die in unserer praktischen Entwicklungsarbeit eine Rolle spielen.

Im vorliegenden Teil I der Beitragsserie gehe ich zuerst auf generelle Faktoren ein, die die Performance von Web-Applikationen beeinflussen. Caching-Verfahren wirken sich auf verschiedene dieser Faktoren aus.

Ich beschreibe danach die Grund-Prinzipien einiger gängiger Caching-Verfahren für PHP-Anwendungen. Ziel ist es aufzuzeigen, wie man den Interaktionsaufwand mit dem Web-Server durch die Kombination dieser Cache-Methoden sukzessive reduzieren kann.

Zudem betrachte ich auch ein paar fachliche und technische Voraussetzungen für Caching. Dabei ergibt sich, dass und warum ein konservativer Ansatz für bestimmte Caching-Verfahren oder deren Kombination sich sowohl an zeitbasierten Regeln als auch an Merkmalen für Änderungen der Datenbestände des Servers orientieren sollte.

Dieser erste Teil ist als Einführung also am ehesten für Leute gedacht, die sich erstmalig mit der Thematik des Cachens im Zusammenhang mit PHP-Applikationen befassen.

In den nachfolgenden Teilen der Beitragsserie widme ich mich dann dem Coding einzelner, spezifischer Caching-Lösungen unter PHP.

Performance von PHP-basierten Webseiten aus Sicht des Nutzers

Der Betrachter von Webseiten servergestützter Webapplikationen begreift die Performance der Applikationen und ihrer einzelnen Seiten hauptsächlich unter dem Aspekt, wie lange es dauert, bis der Browser nach einer Benutzerinteraktion mit einer bereits dargestellten Seite das gewünschte Resultat – in der Regel eine Webseite oder Teile davon – darstellt. Diese “Antwortzeit” ist natürlich ganz entscheidend davon abhängig, ob überhaupt ein Datentransfer zum oder vom Server und ein Seitenaufbau auf dem Server stattfinden:

In vielen modernen Applikationen werden kleinere lokale Zwischenschritte am Browser von einer lokalen Javascript-/JQuery-Logik übernommen, die per DOM Seiteninhalte manipuliert. Oftmals werden auch nicht ganze Webseiten neu generiert, sondern per AJAX/DOM nur Teile einer geöffneten Seite mittels der Ergebnisse einer asynchronen Serveranfrage im Hintergrund modifiziert. Von der Interaktion der Webseite mit dem Server bekommt der Anwender am Browser dabei bei einem asynchronen Datenaustausch im Hintergrund nur bedingt etwas mit.

Kritisch mit der Performance wird es in der Regel immer dann,

wenn vor der nächsten Userinteraktion eine (synchrone) Interaktion mit dem Server stattfindet oder stattfinden muss. Hier addieren sich Datentransferzeiten zwischen Browser/Client und Web-/Datenbank-Servern zu den erforderlichen Zeiten, um das jeweilige Programm am Server durchzuführen.

Wann erfolgt ein solcher Neu-Aufbau von Webseiten durch den Server ? Eine typische, einfache, aber regelmäßig auftretende Situation während der Benutzerinteraktion hierfür ergibt sich ggf. bereits durch den Seitenwechsel eines Nutzers am Browser zu Web-Seiten, die PHP-
basiert sind.

Nicht ist störender als beim Hin- und Herwechseln zwischen Webseiten als bei denjenigen Seiten, die am Server durch PHP-Programme generiert werden, unverhältnismäßig lang warten zu müssen ! Und das, obwohl sich ggf. seit dem letzten Besuch der PHP-basierten Seite vielleicht gar nichts an deren Inhalt geändert hat.

Serverbasierte Applikationen wie PHP-Applikationen trifft die Seiten-Neugenerierung am Server besonders, weil die meisten Browser den Output von serverbasierten Script-Programmen nicht ohne Zusatzmaßnahmen cachen oder dies nur gemäß einer eigenen Heuristik tun. Die Webserver der meisten Hoster sind zudem praktisch nie so eingestellt, dass für PHP-Seiten defaultmäßig ein Caching angewiesen wird. Um diesen Punkt muss man sich der Entwickler schon selbst kümmern !

Bei ein wenig Nachdenken wird man feststellen, dass Caching in Kombination mit “Ajax/PHP” oder gar “PHP/SOAP/REST/Webservices” ein komplexeres Thema ist als Caching im Falle des Abrufes ganzer PHP-Seiten. Wir klammern Ajax und Web-Services aus den Betrachtungen dieser Beitragsserie weitgehend aus.

Hingewiesen sei auch darauf, dass bestimmte Caching-Verfahren natürlich nicht nur in Bezug auf Browser sondern auch gegenüber anderen Clients (Apps, andere serverbasierte Applikationen) interessant sind. Ob und inwieweit der Client explizite HTTP-Header-Anweisungen zum Cachen tatsächlich in ähnlicher Form wie ein Browser nutzen kann, lassen wir mal dahingestellt.

Performance-limitierende Faktoren für serverbasierte Web-Applikationen und für dynamisch generierte Webseiten

Die Performance von Webseiten, deren Inhalte z.B. vollständig oder teilweise durch PHP-Programme auf dem Webserver generiert werden, wird durch eine Vielzahl von Faktoren beeinflusst. Zu nennen sind u.a.:

  1. Die Auslastung des Servers und dessen Puffer-Mechanismen
  2. die benötigte Zeit zum aktiven Laden der dynamisch eingesetzten Script-Module des Webservers,
  3. die Ladezeit von benötigten Include-Dateien am Web-Server,
  4. die benötigte Zeit für die Interpretation und Durchführung des Server-Skript-Codes,
  5. die Zeit für den Verbindungsaufbau vom Webserver zu Datenbankservern und die evtl. Nutzung eines Verbindungspoolings,
  6. die benötigte Zeit für Datenbanktransaktionen und die Zeit für Datentransfers zwischen Webserver und Datenbankserver und umgekehrt,
  7. das Caching-Verhalten des Web-Servers bzgl. der PHP-Module und bzgl. des Ladens der Include-Dateien,
  8. die Effizienz von Sessionspeichern und zugehörigen Technologien (RAM, Filesystem, Datenbank),
  9. das Caching von Provider- und Proxy-Servern,
  10. die Geschwindigkeit der Internetanbindung und Datentransferzeiten zwischen Browser und Webserver,
  11. das Caching am Browser selbst (Nutzung des Browser-Caches).

Die Performance-Politik von Providern beeinflusst praktisch alle Punkte bis auf die letzten zwei.

Wie stark ist nun die Abhängigkeit der Performance von unterschiedlichen Hosting-Paketen und deren Preis? Wir haben je nach Vertrag unserer Kunden Unterschiede in der Größenordnung bis zu einem Faktor 5 für das gleiche Programm unter verschiedenen Hosting-Paketen registriert:

Ein Programm, das in einem lokalen Netzwerk mit Gigabit-Netzwerk, nicht ausgelastetem Web- und Datenbankserver ca. 70 bis 80 Millisekunden (ohne das Nutzen von Caching-Mechanismen) benötigt, erforderte typischerweise ca. 120 – 220 Millisekunden unter besten Bedingungen beim Provider. Je nach Vertrag und auch Auslastung der Server
des Providers kamen jedoch auch Zeiten deutlich oberhalb von 500 Millisekunden oder gar 1,5 Sekunden vor.

Bei preismäßig “günstigen” Verträgen bewegt man sich also u.U. in einem vom Kunden deutlich wahrnehmbaren Reaktionsbereich der Webserver-Leistung. Eine Optimierung von Web-Applikationen durch den Entwickler ist also angesagt, wenn der Kunde “preisgünstige” Angebote wahrnehmen will oder muss. Optimierung kann grundsätzlich an vielen Punkten ansetzen – doch nicht immer kann ein Entwickler auf einfache Weise spürbare Effekte erzielen:

Etliche der genannten Punkte sind partiell natürlich abhängig von dem, was der Provider an Netz- und Server-Technik nutzt und anbietet. Dennoch kann der Entwickler einiges tun:

Optimierungsmaßnahmen bzgl. des obigen Punktes 3) benötigen z.B. eine intelligente Klassen- und Programm-Politik auf der PHP-Seite. Unter besten Bedingungen kann das Laden von Klassenbibliothek-Files bei umfangreichen Systemen durchaus ein performance-limitierender Faktor sein. Hat man den Webserver selbst unter Kontrolle, so mag sich hier ein Griff zu Tools wie APC lohnen.

Unter Punkt 4 ist natürlich nicht nur eine performante Server-Hard- und Software gefragt. Selbstverständlich lassen sich auch PHP-Programme durch intelligente Maßnahmen des Entwicklers optimieren. Dies kann jedoch sehr aufwändig werden – und Kosten/Nutzen-Betrachtungen sind wichtig. Und welcher Entwickler geht schon ohne echte Not an die Grundfesten von Applikationen heran, die seit Jahren im Einsatz sind ?

Um eine optimale SQL-Performance muss sich der Entwickler zu einem essentiellen Anteil in jedem Falle selbst kümmern. Die Wahl geeigneter Tabellen-Varianten des RDBMS, die Wahl der Aufgabe angemessener Sperrmechanismen, Performance-Optimierung des Datenmodells, wo sinnvoll auch Einsatz von Denormalisierung und optimierte Indizierung sind hier erste Stichworte.

Man sollte sich als Kunde jedoch darüber im Klaren sein, dass der Aufwand für eine nachträgliche Performance-Optimierung von Web-Applikationen beträchtliche Ausmaße annehmen kann. Für die entsprechenden Kosten kann man sich dann ggf. auch ein besseres Hosting-Paket oder einen eigene Root-Server leisten.

Will man als Entwickler in der Kooperation mit dem Kunden schnell und effizient positive Effekte hinsichtlich der Performance erzielen, lohnt es sich daher durchaus, Caching-Methoden zu untersuchen und diese über klar abgegrenzte, überschaubare Funktionen oder Klassen-Methoden in die PHP-Applikationen zu implementieren. Der Aufwand dafür hält sich nämlich in Grenzen.

Ziele von Caching-Verfahren

Das grundlegende Ziel des Einsatzes von Caching-Verfahren macht sich an folgender Frage fest:

Wie kann ich die Interaktionsdauer mit dem Web-Server durch das Ausnutzen von Pufferspeichern – also Caches – und die Analyse von Änderungsinformationen zur Datenbasis der Webseiten so weit als möglich reduzieren ?

Strategien in diesem Zusammenhang können sein:

  • bereits erzielte Ergebnisse – sprich Webseiten – in direkt verwertbarer Form zwischenzuspeichern und aus entsprechenden Pufferspeichern zu laden, statt den Server zu einer Seitengenerierung aufzufordern,
  • den Kommunikationsaufwand und Datenaustausch zwischen Browsern und Servern auf das notwendige Maß zu reduzieren

und vor allem:

  • die Vermeidung von ressoucenintensiven Programmabläufen und Datenbanktransaktionen auf den Servern des Providers, soweit das unter dem Aspekt einer dynamischen Änderung der Datenbasis vertretbar ist.

Pufferspeicher – zeitbasierte Regeln – Änderung der Datenbasis von Webseiten

Um die eben genannten
Ziele zu erreichen, ist es zum einen notwendig, Pufferspeicher und Caches zu nutzen. Hierfür kommen Browser-Caches, Server-Session-Speicher oder auch selbst-verwaltete “Datei-Caches” am Server in Betracht. Intelligentes Daten- und Datei-Caching kann PHP-seitig teilweise auch im PHP-Code-Ablauf realisiert werden. Aber natürlich muss man primär die “Public-” bzw. “Private-” Caches im Sinne von Proxy- und Browser-Caches für die generierten Webseiten und deren Elemente ausnutzen.

Neben der Notwendigkeit der Nutzung von “Pufferspeichern” ist es aber auch erforderlich, den Bedarf nach einer Aktualisierung von Webseiten und deren Inhalten

  • anhand von Regeln – z.B. für Zeitintervalle – zu steuern
  • und aufgrund der Analyse von definierten Änderungsmerkmalen der Datenbasis am Server festzustellen.

Beide Aspekte sind auszutarieren: Ein zu langes Zeitintervall für die Nutzung von Pufferspeichern steht ggf. in Konflikt mit dem Anspruch, aktuelle Informationen auch dann zu liefern, wenn sich die Datenbasis einer Webseite geändert hat.

Fachliche Voraussetzungen für Caching

Der Aspekt der Änderung der Datenbasis für den Inhalt einer Webseite oder generell von serverbasiertem Output führt uns zu den fachlichen Voraussetzungen für den Einsatz von Caching-Verfahren. Aus meiner Sicht gilt ganz allgemein:

Voraussetzung für den Einsatz von Caching-Verfahren ist, dass sich der Inhalt einer generierten Webseite aus Client- bzw. Kundensicht nicht laufend und zeitnah ändern muss, sondern über einen definierbaren Zeitraum konstant bleiben darf.
 
Auf der Client- oder Kundenseite darf kein Schaden entstehen, wenn eine Änderung erst nach einem definierten Zeitintervall am Client oder Browser bekannt wird.

Wie groß das zu wählende Zeitintervall ist, hängt natürlich vom Einsatzzweck der Website ab. Caching-Zeitintervalle können sich zwischen Sekunden und Wochen bewegen. Bei einem Newsticker wird das Zeitintervall konstanter Information natürlich viel kleiner sein als bei einer Informationsseite, deren Inhalt sich bekanntermaßen nur zu Beginn eines jeden Monats ändert.

Macht man sich jedoch klar, dass die typische Interaktionsdauer eines Kunden mit Web-Informationsseiten vielleicht zwischen 1 und 15 Minuten liegt, so wird klar, dass bereits das Ausnutzen von Caching-Zeiten im Bereich weniger Minuten bis zu einer Viertelstunde einen großen Effekt auf den subjektiven Performance-Eindruck eines Kunden haben können.

Wir erkennen hier übrigens, dass Ajax tatsächlich ein schwieriges Thema im Zshg. mit Caching ist:
Wann setze ich denn Ajax ein? Meist doch dann,

  • wenn eine Benutzereingabe zu einer direkten “Aktualisierung” von Datenbeständen auf dem Server und nachgelagert auch von bestimmten Inhalten der aktuell am Browser geöffneten Webseite führen soll (synchron oder asynchron). Caching ist hier in der Regel kontraproduktiv. Man steuert vielmehr aktionsgerecht selbst, was zwischen Server und Browser transferiert werden soll.
  • wenn eine Abfrage von listenartigen Vorschlägen aufgrund von Benutzereingaben gestartet wird. Das Problem ist hier, dass das Ergebnis solcher Abfragen noch während der Benutzereingabe Modifikationen erfahren soll – z.B. im Sinne von sich laufend modifzierenden Filtern. Hier hängt der Sinn eines möglichen Cachings vor allem davon ab, ob man die Filterung am Server oder am Client vornimmt und davon, wie vorhersagbar und wohldefiniert bestimmte Benutzerabfragen absehbar sind.
  • generell wenn Abfragen asynchron im Hintergund vorgenommen werden sollen, ohne die Webseite zu verlassen. Hier stellt sich die Frage, ob und wie das Ergebnis – oft ein XML-formatierter Datenstrom oder per JSON sequentialisierter
    Datenstrom – wo und wie gecached werden könnte.

Einige Caching-Verfahren

Kommen Browser- und Proxy-Caches zum Einsatz werden statische Webseiten ohne besondere Vorkehrungen nur einmal oder periodisch vom Server geladen, danach kommen ihre Inhalte für definierte Zeitintervalle aus dem Cache des Browsers oder eben eines Proxys. Bei dynamisch generierten Webseiten – wie PHP-Seiten – hängt die Caching-Politik vom Browserhersteller und von Einstellungen der Proxys wie des Webservers selbst ab – falls keine besonderen Vorkehrungen getroffen werden.

Gezieltes, anweisungsgesteuertes Caching im Cache des Browsers macht ggf. den Unterschied zwischen einigen zehn Millisekunden für den Seitenaufbau im Gegensatz zu einer Sekunde aus – pro Seite ! Das ist ziemlich durchschlagend. Unter bestimmten Bedingungen kann es aber auch interessant sein, Webseiten aus einem selbst verwalteten File-Cache aus dem Webserver zu ziehen.

In einer “HTTP 1.1”-Umgebung sind für normale PHP-Applikationen vor allem folgende vier Verfahren zur Ausnutzung von Caching-Mechanismen interessant, wobei alle Methoden explizit die Unterstützung des jeweiligen PHP-Programms (und des Webservers) erfordern. Wir ordnen die Verfahren nach dem Grad der Reduktion der Interaktion mit einem Web/Datenbank-Server und der Vermeidung von unnötigen Prozessen auf dem Server an :

  1. Methode 1 – Explizite Caching-Anweisung an “Public” und “Private” Caches mit einem limitierenden Zeitintervall:
    Der Browser oder ein chache-gestütztes Client-System erhalten den Hinweis, den Server über ein definiertes Zeitintervall hinweg nicht abzufragen, sondern explizit ihren Cache zu benutzen. Erst nach Ablauf des vorgegebenen Zeitintervalls wird der Server erneut kontaktiert: die geforderte Seite wird dann über das angesprochene PHP-Programm vollständig neu generiert (oder ggf. aus einem serverseitigen Filecache geladen) und auf dem aktuellen Stand zum Browser, in dessen Cache und die Caches von Proxy-Systemen etc. übertragen.
  2. Methode 2 – Check auf Änderungen von Webseiten und “Not Modified”-Rückmeldung im HTTP-Header an den Browser:
    Die auf dem Server angesprochenen PHP-Programme überprüfen anhand bestimmter Merkmale als allererstes, ob sich die Datenbasis der dynamisch zu generierenden Website seit der letzten Serveranfrage geändert hat. Ist dies nicht der Fall, erhalten der Browser und andere Cache-Systeme per HTTP-Header einen expliziten Hinweis (Code 304-Meldung), ihren Inhalt weiter zu verwenden, aber bei der nächsten Seitenabfrage den Server erneut bzgl. möglicher Änderungen abzufragen. Nur falls in der Zwischenzeit Änderungen aufgetreten waren, wird der geänderte Webseitencode entweder neu aus Datenbankinhalten erzeugt oder aus einem intermediären Datei-Puffer (s. Punkt 4) des Webserver geladen und zum Client übertragen.
  3. Methode 3 – Kombination der Methoden 1 und 2:
    Methode 3 kombiniert die erste und die zweite Methode: Grundsätzlich wird ein zeitlimitiertes Caching zugelassen. Nach Ablauf des Zeitintervalls überprüft das angesprochen PHP-Programm aber zunächst, ob sich die Datenbasis seit der letzten Seitenauslieferung überhaupt geändert hat. Wenn nicht, wird erneut ein clientseitiges Caching für ein definiertes Zeritintervall erlaubt – wobei die Seite nicht (!) neu generiert oder geladen wird. Im Falle einer Änderung der Datenbasis wird die Seite dagegen neu auf dem Server erzeugt und in die Caches übertragen – das Caching-Zeitintervall auf der Kunden- oder Proxy-Seite beginnt erneut.
  4. Methode 4 – PHP-gesteuertes File-Caching am Server:
    Wohldefinierte Ereignisse auf dem Server werden genutzt, um den HTML/XHTML/XML-Code der benötigten Webseiten oder Informationen auf der Basis von erkannten
    Änderungen der Datenbasis neu zu generieren. Der durch das PHP-Programm generierte HTML/XHTML- oder auch XML-Code wird dann für ein definiertes Zeitintervall oder aber bis zum nächsten wohldefinierten Änderungs-Event als “Datei” auf dem Server in einem selbstverwalteten “Cache”-Verzeichnis abgelegt. Bei der nächsten Browserabfrage wird vom PHP-Programm direkt der Datei-Inhalt aus dem Verzeichnis-Cache geladen und zum Browser übertragen, falls keine sonstige Änderung der Datenbasis erfolgt ist. Ressourcenintensive Prozesse zur Webseitengenerierung werden so nur periodisch oder aber aufgrund definierter Änderungsereignisse fällig.
  5. Methode 5 – Kombination der Methoden 3 und 4:
    Grundsätzlich wird ein zeitlimitiertes Caching zugelassen. Nach Ablauf des Zeitintervalls überprüft das angesprochen PHP-Programm aber zunächst, ob sich die Datenbasis geändert hat. Ist ein aktuelles HTML/XHTML/XML-File zur geänderten Datenbasis vorhanden, wird dieses gezogen. Ist das nicht der Fall, wird die Seite neu generiert, en passant am Server als File abgelegt und zudem zum Client übertragen.

Einschränkungen, Vor- und Nachteile der Verfahren

Die einzelnen Verfahren weisen Vor- und nachteiel auf, die ich nachfolgend kurz diskutiere:

Einschränkungen für den Einsatz der ersten Methode – Caching während eines definierten Zeitintervalls

Beim Einsatz der ersten Methode, versucht man, eine Interaktion mit dem Server über definierte Zeitintervalle hinweg zu vermeiden. Diese Methode hat den Vorteil eines schnellen Seitenaufbaus auf der Basis lokaler Informationen und Objekte im Cache des Browsers. Das wirkt sich am Browser vor allem beim Wechsel zu und zwischen (eigentlich PHP-basierten) Webseiten sehr vorteilhaft auf die Performance aus. Durch die Nutzung des Browser-Caches wird nicht zuletzt auch der Server spürbar entlastet.

Der Nachteil der Methode 1 besteht jedoch darin, dass während des vorgegebenen Zeitintervalls eben auch keinerlei Serverinteraktion mehr stattfindet (soweit der Browser/Proxy die Caching-Vorgaben des Servers respektiert). Das cachende System – i.d.R. der Browser – erfährt (bei normaler Bedienung) innerhalb des vorgegebenen Zeitintervalls nichts (!) mehr über eventuelle Änderungen der Datenbasis des Webseiten-Inhalts. Die aus dem Cache gezogene Information ist also möglicherweise schon veraltet. Und selbst wenn der Browser die Seite neu anfordern würde, kämen ggf. veraltete Inhalte aus Proxy-Caches zurück.

Eine kluge, dem Einsatzzweck der Web-Applikation angemessene Vorgabe des Caching-Zeitintervalls an beteiligte Systeme ist also eine wichtige Voraussetzung der Anwendung der Methode 1.

Die zweite Voraussetzung für den Einsatz dieser Methode ist die, dass die Benutzerinteraktion nicht selbst zu einer Datenbestandsänderung führt und dass für den Aufbau der geforderten Webseite nicht unmittelbar eine komplette Neukalkulation und Neu-Generierung der Seite auf dem Server erforderlich ist. (Gezielt gesteuerte Ajax-Prozesse können den Cache natürlich immer umgehen. Ich lasse das aber mal außer Acht).

Man erkennt deshalb,

dass diese erste Methode des Cachings  nicht  für Previews und Voransichten von Webseiten geeignet ist , die mit Hilfe von CM-Systemen oder anderen Pflegemodulen aus Datenbankinhalten generiert werden.

Will man den Effekt von Datenbankänderungen sofort sehen, muss das Caching der zugehörigen Webseiten durch bestimmte PHP-Übergabe-Parameter, die bei den normalen Web-Adressen nicht auftauchen, umgangen bzw. explizit abgestellt werden.

Dies ist jedoch leicht realisierbar, da sich das Browser-Caching immer auf eine vollständige URL-Adresse – also
inkl. aller GET-Parametern – bezieht. Bei der Übergabe von POST-Parametern muss eben ein bestimmter Zusatz-Parameter zur Situationsanalyse eingesetzt werden und die Namen für die Previewer-Programme sollten sich dann auch von denen der normalen Web-Generator-Programme unterscheiden.

Vor-und Nachteile der zweiten Methode – Prüfung der Änderung der Datenbasis vor einer Neugenerierung oder Cache-Nutzung

Die zweite Methode für sich erspart einem nicht eine Kommunikation zum und vom Server. Aber sie stellt ein Schlüsselelement dar, um den Aufwand auf dem Server zu begrenzen:

Sie erspart u.U. die Durchführung ressourcenintensiver PHP-Programmschritte und von RDBMS-Prozessen auf dem Web- und den Datenbank-Servern.

Die Latenz der PHP-Applikation hängt dann hauptsächlich von der Internetanbindung und im wesentlichen einer Datei- oder Datenbankabfrage auf Änderungen ab. Beides kann aber erheblich (!) schneller sein, als eine vollständige Neugenerierung der Seiteninformation durch komplexe Programme und Datenbanktransaktionen – wenn sich der Datenbestand nicht geändert haben sollte.

Der Einsatz der zweiten Methode ist aus meiner Sicht grundsätzlich sinnvoll. Hier muss man sich allerdings überlegen, wie und was man auf Änderungen prüft. In meinen Programmen sind das

  • erstens Datenbankänderungen
  • zweitens Änderungen von Files
  • drittens ggf. Änderungen von speziellen Files für die Caching-Methode 4
  • viertens Änderungen der Inhalte von Session-Speichern

Änderungen an anderen Datenbestände, die hier nicht genannt wurden, sind denkbar.

Datenbankänderungen bekommt man grundsätzlich über die Analyse von Timestamps in Tabellen mit. Hierbei muss man aber die Zusammenhänge der Daten – sprich das Datenmodell beachten ! Das Ergebnis des PHP-Programms wird ja i.d.R. von einer Kombination von Tabellen abhängen. Man muss sich also darüber klar werden, welche Timestamps eigentlich für den gewünschten Output relevant sind. Ggf. lohnt es sich, für die einfache Abfrage relevanter Änderungen im Rahmen von Caching-Verfahren spezielle Einträge in selbst verwalteten Timestamp-Tabellen explizit zu setzen oder z.B. über Datenbank-Trigger setzen zu lassen.

Es zeigt sich also, dass die Möglichkeit zum einfachen Erkennen der Änderungen von Datenbeständen für ein späteres Caching schon frühzeitig beim Design einer Applikation berücksichtigt werden muss.

Analoge Argumente gelten für andere Datenbestände, die in Kombination zur Erzeugung eines aktuellen Seiteninhalts wichtig sein mögen.

Ein weiterer Nachteil dieser Methode besteht übrigens ggf. darin, dass man die Verbindung zur Datenbank in einem PHP-Programm sehr frühzeitig aufbauen muss. Dies kann vor allem in sicherheitsrelevanten Applikationen eine Rolle spielen.

Vorteile der dritten Methode – Kombination aus zeitgesteuertem Caching und Prüfung der Datenbasis

Sind die Voraussetzungen für den Einsatz der Methoden 1 und 2 gegeben, bringt deren Kombination aus meiner Sicht nur Vorteile mit sich. Man cached lokal solange, wie das eben vertretbar ist. Erst danach wendet sich der Client/Browser wieder an den Server. Aber dort wird nur wirklich etwas getan, wenn dies aufgrund eingetretener Änderungen notwendig ist. Ansonsten wird erneut auf den am Client vorhanden Cache-Inhalt zurückgegriffen. Besser kann es kaum kommen – deswegen setze ich diese Kombination auch in vielen Applikationen ein.

Vor-und Nachteile der vierten und fünften Methode – Einsatz eines selbstverwalteten File-Cache für PHP-Output

nGrundsätzlich ist es gut, einen selbstverwalteten File-Cache zu realisieren. Sein Einsatz lohnt sich vor allem unter der Voraussetzung, dass sich die Generierung der Webseite zeitlich und logisch weitgehend von der konkreten Nutzer- und Abfragesituation am Client abkoppeln lässt:

  • Eine durch Ereignisse oder Pflegepersonen ausgelöste Datenbestandsänderung führt für sich alleine und unabhängig von der aktuellen Benutzerinteraktion bereits zu einem wohldefinierten Aufbau der betroffenen Webseiten. Diese können im Prinzip also unmittelbar nach der Datenänderung als Files am Server erzeugt werden.
  • Der Inhalt der Seiten ist relativ unabhängig von dynamischen Parameterkonstellationen, die situationsspezifisch erst durch den Anwender der Web-Applikation definiert werden.

Sind diese Voraussetzungen gegeben, so gilt zusätzlich Folgendes:

Sollen oder müssen die generierten Files im selbst verwalteten Cache-Verzeichnis alle möglichen Abfragesituationen abdecken, so erkennt an schnell ein potentielles Mengenproblem. Bei komplexen Applikationen mit großen Datenbeständen kann dies durchaus ein Thema werden. Man muss sich also genau überlegen, welchen Output von PHP-Applikationen man in Puffer-Files überführen will. Für den einen oder anderen mag zudem der technisch erforderliche Einsatz des sog. Output-Bufferings unter PHP5 zur Erzeugung der Files ein unbekanntes Terrain darstellen.

Ein weiteres mögliches Problem ist, dass man den eigenen File-Cache natürlich auch im Rahmen seiner Applikationen selbst verwalten muss. Wie bei jedem anderen Cache wird man hier Zeitregeln und eine Mengenbegrenzung einführen. Dies führt dann zu FIFO-Mechanismen oder ähnlichem.

Insgesamt lohnt sich der Einsatz von selbstverwalteten Filecaches vor allem dann, wenn

  • die Generierung des HTML/XHTML/XML-Outputs substanzielle Ressourcen beansprucht,
  • die Änderung der Datenbasis von der aktuellen Benutzerinteraktion weitgehend entkoppelt ist,
  • die im File hinterlegte Information eine langfristige Gültigkeit hat.

Solche Voraussetzungen sind oft in CM- oder Pflege-Systemen gegeben, in denen Seiten erzeugt werden, deren Inhalt nicht dynamisch durch viele Parameter gesteuert wird und über relativ lange Zeitintervalle hinweg konstant bleibt.

Ein Beispiel hierfür wäre etwa eine Webseite, die einen CV darstellt. Der HTML-Code der Webseite kann unmittelbar nach Änderung der Datenbankinhalte zum CV erzeugt werden und bleibt dann lange konstant, also statisch. Hier lohnt sich die Ablage in Dateiform unmittelbar nach Durchführung der Datenänderung. Das PHP-Programm, das für eine Anfrage des Kunden zu dieser CV-Webseite verantwortlich ist, prüft auf Änderungen der Datei durch andere Pflegeprogramme ab und schickt den vorab generierten und hinterlegten Datei-Inhalt direkt weiter, wenn keine Änderungen vorliegen.

Ein Gegenbeispiel wäre etwa eine Projekt- oder Produktliste, auf die der Anwender interaktiv viele verschiedene Filter anwenden kann und deren Inhalt sich aufgrund äußerer Umstände in kurzen, unperiodischen Zeitabständen ändert. Gegenbeispeile stellen auch Seiten zu sehr vielen unterschiedlichen Items dar, die sich erratisch ändern.

Genannt sei ein weiterer Einsatzbereich des vierten Verfahrens – nämlich Ajax-Transaktionen: Es gibt bestimmte Ajax-Abfragen (z.B. die Generierung von relativ statischen Listen), deren Ergebnis sich nicht immer auf dem alleraktuellsten Status befinden muss. Dann lohnt es sich durchaus, XML- oder JSON-Daten nicht immer neu erzeugen zu lassen, sondern direkt Cache-Files zu entnehmen.

Insgesamt meine ich, dass man vor dem Einsatz der Methoden 4 und 5 zunächst Methode 3 ausprobieren sollte. Methode 3 ist – wie wir sehen werden – auch einfacher zu implementieren als ein selbst verwalteter File-Cache.
Natürlich kann man die Methode 4 auch mit der Methode 3 kombinieren:

Wenn Änderungen am Datenbestand festgestellt werden, kann man zunächst überprüfen, ob seit der letzten Änderung bereits ein neueres File erzeugt wurde und dessen Inhalt dann als Output an den Browser weiterreichen. Ist kein hinreichend junges File gegeben, so kann man ein solches “en passant” anlegen.

Server-Entlastung

Caching ist auch interessant und wichtig für Leute, die ihre eigenen Webserver betreiben. Es liegt nach dem bisher Gesagten auf der Hand, dass ein vernünftiges Caching auf Basis der oben genannten Methoden insgesamt die Auslastung der Web-Server reduziert. Alle genannten Verfahren haben ja gerade zum Ziel, die Interaktion mit dem Server zu verringern. Im zeitlichen Mittel bleiben bei Einsatz von Caching-Verfahren mehr Serverressourcen übrig.

Nun haben wir das notwendige Rüstzeug beieinander, um uns den einzelnen Methoden zuzuwenden. Dies geschieht in einem weiteren Beitrag dieser Reihe unter der Rubrick “Lamp / Webentwicklung”. Bis dann !

Opensuse 12.2 – mysql, logrotate error

Vor einigen Tagen habe ich den Mysql-Community-Server über das entsprechende Opensuse “Database”-Repository upgedated. Seitdem erhalte ich beim Systemstart Meldungen der Art

Nov 15 18:30:04 myserver logrotate: ALERT exited abnormally with [1]
Nov 15 18:30:04 myserver logrotate: #007/usr/bin/mysqladmin: refresh failed; error: ‘Unknown error’
Nov 15 18:30:04 myserver logrotate: /logrotate.d/mysql failed, probably because
Nov 15 18:30:04 myserver logrotate: the root acount is protected by password.
Nov 15 18:30:04 myserver logrotate: See comments in /logrotate.d/mysql on how to fix this
Nov 15 18:30:04 myserver logrotate: error: error running non-shared postrotate script for /var/log/mysql/mysqld.log of ‘/var/log/mysql/mysqld.log ‘

Diese Meldung erhalte ich, obwohl es unter dem “/root”-Verzeichnis die notwendige “/root/.my.cnf”-Datei mit den Account-/Passwort-Daten für den MySQL-Admin (MySQL root-Account) gibt. Die Fehlermeldung führt einen daher in die Irre.

Der Fehler liegt wohl eher im Startup-Skript “/etc/init.d/mysql” für den MySQL-Server.

Das habe nicht ich herausgefunden sondern Archie Cobbs, der das bei Novell als Bug eingestellt hat.

Siehe :
http://lists.opensuse.org/opensuse-bugs/2012-11/msg01186.html
und
https://bugzilla.novell.com/show_bug.cgi?id=789263

Dort findet man auch den notwendigen Hinweis, wie man das Startup-Skript abändern muss, damit Logrotate wieder funktioniert:

The bug is in the line “chmod 660 $log_dir”.
That should be “chmod 770 $log_dir”.

Scheint rechtemäßig logisch zu sein und hat bei mir funktioniert.

Eclipse, SVN, kopierte .svn-Verzeichnisse (beseitigen)

Der Mensch ist ein Gewohnheitstier. Leider auch im Machen von Fehlern. Ein Beispiel für eine solche Fehl-Leistung hat mich gestern wieder im Zusammenhang mit Eclipse und der SVN-Verwaltung neu angelegter, kundenspezifischer PHP-Projekte ereilt. Für die PHP-Entwicklungsarbeit nutze ich primär Eclipse unter Linux.

Beim Zusammenstellen der Verzeichnisse und Dateien für neue PHP-Projekte unter Eclipse kopiere ich manchmal ganze Verzeichnisstrukturen aus anderen, früheren PHP-Projekten. Wenn ich dabei zu bequem vorgehe, erwische ich beim Datentransfer auch alle vorhandenen “.svn”-Verzeichnisse innerhalb der kopierten Verzeichnishierarchie.

Vergesse ich nun (wie gestern geschehen), diese “.svn”-Verzeichnisse zu löschen, bevor ich eine Zuordnung des neuen kundenspezifischen PHP-Projekts zu einem SVN-Repository vornehme, so führt dies zwangsläufig zu Problemen mit der Versionsverwaltung der kopierten Dateien im neuen PHP-Projekt. Je nach Projekt-Setup

  • wundert man sich anschließend ggf. über unabgeschlossene SVN-“Commit”-Vorgänge bzgl. der (kopierten) Verzeichnisse in das eben zugeordnete SVN-Repository
  • oder aber man erkennt an der grafischen Darstellung des PHP-Explorers, dass es zugrunde liegende SVN-Links zwischen dem neuen und den alten Projekten gibt, die man im neuen, kundenspezifischen Projekt aber nicht haben will und auch nicht beabsichtigt hatte.

Um die durch die Kopiererei verkorkste SVN-Situation unter Eclipse zu bereinigen, muss man das neu angelegte Projekt mit den kopierten Verzeichnissen wieder von SVN lösen, die kopierten “.svn”-Verzeichnisse rekursiv beseitigen – also löschen – und dann erneut die Zuordnung des neuen Projektes zu einem SVN-Repository durchführen.

Aber der Reihe nach.

2 Arten der Nutzung vorhandener Arbeitsergebnisse und Dateien in neuen PHP-Projekten unter Eclipse

Beim Anlegen von bestimmten neuen PHP-Projekten unter Eclipse stelle ich mir als Ausgangspunkt meiner Arbeit Verzeichnisstrukturen zusammen, bei denen ich die Früchte bisheriger Arbeit nutze. Hierbei gibt es u.a. zwei Hauptvarianten:

Zugriff auf Dateien zentraler Bibliotheken

Dabei spielt zum einen der Zugriff auf Verzeichnisstrukturen zentraler Klassen- und Funktions-Bibliotheken eine Rolle. Diese Bibliotheken und deren Dateien werden bei mir über eigene spezielle Projekte gepflegt. Sie stellen das Fundament dar, auf dem kundenspezifische Projekte aufbauen können.

Die Dateien der zentralen kunden-unspezifischen Bibliotheken (und ihre dort definierten Klassen/Funktionen) können und sollen von anderen Projekten genutzt werden. Den Zugriff auf solche Ressourcen von einem kundenspezifischen PHP-Projekt aus erreiche ich unter Eclipse durch Ordner-Verlinkung und ein entsprechendes Setup der PHP-Build-Pfade.

Man legt den neuen Ordner im Projekt einfach als Verweis auf ein vorhandenes Verzeichnis an.

Eclipse Verzeichnis Link

(Man ignoriere die Fehleranzeige bei den Projekten – so schlimm wie das erscheint, ist die Situation in meinen Projekten wahrlich nicht 🙂 . Der Fehler vererbt sich durch regelmäßige Referenz auf bestimmte phpmyadmin-Dateien.

Je nach Rechte-Setup kann ich danach von einem spezifischen Kunden-Projekt aus die zentralen Ressourcen bei Bedarf auch modifizieren. Das Ergebnis steht dann automatisch auch allen anderen kundenspezifischen Projekten zur Verfügung. Für die Unterstützung der “Content-Assist”-Funktionalität (Vorschläge beim Coding – Variablen, Klassen etc.) muss man im PHP-Projekt ggf. die Build-Pfade richtig setzen.

Die SVN-Versions-Verwaltung der zentralen Bibliotheken erfolgt in
meinem Fall natürlich getrennt von der SVN-Verwaltung des spezifischen neuen Kunden-Projektes. Die SVN-Verwaltung der zentralen Klassenbibliotheken führe ich über deren eigene Projektumgebung und gemäß des zugehörigen SVN-Setups durch und nicht über die Kundenprojekte.

Die Eclipse SVN-Plugins erkennen die verlinkte Verzeichnis-Struktur der Kundenprojekte und reagieren adäquat:

Die per Link eingebundenen Verzeichnisse und Dateien werden beim Ein- und Auschecken in das spezifische SVN-Repository des neuen kundenspezifischen PHP-Projektes ignoriert. Das ist vernünftig, weil ressourcenschonend und so auch von mir gewünscht. Die Projekte für die zentralen Bibliotheken habe ihr eigene SVN-Repository-Verwaltung – unabhängig von meinem kundenspezifischen Projekt. So weit, so gut.

Kopierte Verzeichnisse und Dateien, die projektspezifisch verändert werden

Zum anderen “kopiere” ich mir aber auch etliche Verzeichnisse mit spezifischen Klassendefinitionen und PHP-Programmen in mein PHP-Projekt. Dabei verlinke ich die entsprechenden Ordner nicht wie bei der Benutzung echter, zentral gepflegter Bibliotheken:

Ich kopiere, weil die entsprechenden Files (Programme, Funktionen, Klassen) projektspezifisch verändert werden sollen.

Die kopierten Verzeichnisse können dabei durchaus eine komplexe Subverzeichnisstruktur aufweisen. Einen Teil der Verzeichnisstrukturen richte ich dabei zudem auf Samba-Verzeichnissen ein, so dass die entsprechenden Dateien unter (virtuellen) Windows-Maschinen parallel auch in Dreamweaver-Projekte eingebunden werden können. (Wie man einen entsrpechenden durchgehenden Arbeitsfluss gestaltet, beschreibe ich vielleicht mal in einem gesonderten Beitrag). Aus Hygienegründen trenne ich das Samba-Verzeichnis vom Eclipse-Workspace-Verzeichnis ab. Unter Eclipse führe ich alle Ressourcen aber wieder durch geeignete Projekt-Setups zusammen.

Dabei schließe ich Dreamweaver-spezifische “_notes”-Verzeichnisse und “*.LCK”-Dateien, aber auch “.svn”-Verzeichnisse aus der Projektstruktur- und Build-Verwaltung über das Setzen von rekursiven “Ressource-Filtern” aus.

Eclipse Resource Filter

Im PHP-Explorer erscheinen die entsprechenden Verzeichnisse und Dateien dann nicht mehr ! (SVN geht natürlich trotzdem).

Die erforderlichen initialen Kopiervorgänge führe ich der Bequemlichkeit halber oft außerhalb von Eclipse auf Kommandozeilenebene oder mit einem Dateimanager durch. Manchmal benutze ich aber auch die Exportfunktionalität innerhalb von Eclipse.

Ein klassisches SVN-Problem entsteht anschließend, wenn ich aus Unachtsamkeit oder Bequemlichkeit die aus den älteren Projekten vorhandenen “.svn”-Verzeichnisse in das neue Projekt mit hinein kopiert habe. Und das können je nach Tiefe der Verzeichnisstruktur sehr, sehr viele “.svn”-Ordner sein (pro vorhandenem Subverzeichnis nämlich ein “.svn”-Ordner!).

Manchmal vergesse ich leider die kopierten “.svn”-Verzeichnisse einfach aufgrund anderer Themen, die mir dazwischenkommen (ja das Alter!), und wegen der gesetzten Ressource-Filter fallen mir die kopierten “.svn”-Ordner anschließend auch nicht mehr ins Auge.

SVN übernimmt die Verwaltung der kopierten Verzeichnisse und Dateien nicht wie gewünscht

Nach den ersten Schritten im Projekt erreicht man dann schnell ein Stadium, in dem man seine Ergebnisse der Versionsverwaltung SVN anvertrauen möchte.

Mein SVN-Plugin stellt unter dem Menüpunkt “SVN >> Share Projects …” eine menügeführte Möglichkeit zur Zuordnung eines Projekts zu einem Repository bzw. einem Unterbereich davon zur Verfügung. Ich “share” also mein
neu angelegtes Projekt und platziere es in einem eigenen SVN-Repository oder einem eigenen Zweig eines vorhandenen Repositories.

Natürlich möchte ich nun, dass bei der Anlage dieses projektspezifischen Repository-Bereichs eine initiale Erfassung auch derjenigen Verzeichnisse und Dateien erfolgt, die in mein kundenspezifisches Projekt kopiert wurden. Genau das funktioniert aber leider nicht wie erwartet, wenn in den Verzeichnissen bereits gültige “.svn”-Ordner existieren!

SVN meint dann, dass diese Verzeichnisse ja schon einer SVN-Verwaltung unterliegen würden und nutzt SVN-interne Links auf die bereits verwalteten SVN-Ressourcen.

Das ist natürlich nicht im Interesse meines neuen Projekts – ich bewege mich da ja in einem völlig anderen Kontext und was dort mit den kopierten Dateien geschieht, hat mit den in den Änderungen, die in der Versionsverwaltung eines anderen Projektes erfasst werden, nichts zu tun. Die SVN-interne Verlinkung ist im vorliegenden Fall sogar schädlich bis gefährlich. (In anderen Fällen als dem beschriebenen mag eine SVN-Verkettung aber durchaus sinnvoll sein).

Leider weiß SVN aber nichts von meiner stumpfsinnigen Kopiererei und meinen Intentionen. Es nimmt die Hinweise in den entdeckten (kopierten) “.svn”-Verzeichnissen ernst.

Man merkt die Verkopplung des neuen Projektes mit älteren auf SVN-Ebene u.a. durch einen roten Pfeil in der rechten unteren Ecke der Dateisymbole im PHP-Explorer.

Eclipse SVN Link

Der Pfeil zeigt die Referenz auf SVN-Ebene zu einem anderen Repository-Container eines anderen Projektes an. Commit-Vorgänge auf dem aktuellen Projekt werden ggf. nicht als abgeschlossen angezeigt, wenn im anderen Projekt durchgeführte Dateiänderungen noch nicht “committed” wurden. Auch sonst kann es zu erheblichen Verwirrungen kommen

Bereinigung des SVN-Problems – Rekursives Entfernen der kopierten “.svn”-Ordner

Was ist in dieser Situation zu tun? Es ist notwendig, das neu angelegte Projekt vom kopierten Ballast anderer Projekt zu befreien und erst danach – also im aufgeräumten Zustand – einem SVN-Repository zuzuordnen. Vorher lösen wir das Projekt aber von der schon vorgenommenen Versionsverwaltung. Folgende Schritte sind somit durchzuführen:

  • Schritt 1: Man muss das neu angelegte PHP-Projekt zunächst vom SVN-Repository “disconnecten”.
  • Schritt 2: Man muss nun alle kopierten “.svn”-Ordner löschen.
  • Schritt 3: Erneute Zuweisung des Projektes an SVN und an ein neu anzulegendes SVN-Repository oder einen neuen Zweig eines vorhandenen Repositories

Das hört sich einfach an. Schritt 2 kann sich ohne Shell-Einsatz aber als aufwändig erweisen.

Hinweis zu Schritt 1:
Die Durchführung von Schritt 1 ermöglicht ein Menüpunkt im verzeichnisspezifischen Kontext-Menü: “Team” >> “Disconnect”.

Eclipse SVN Disconnect

Dabei lässt man im nächsten Schritt auf Rückfrage auch die projektspezifischen “.svn”-Einträge löschen. Dabei bleiben allerdings – wie ein anschließender Blick auf die Verzeichnisstruktur zeigt – die ins Projekt kopierten “.svn”-Ordner unangetastet! Das musste man eigentlich erwarten.

Hinweis zu Schritt 2:
Schritt 2 ist wegen der Vielzahl der “.svn”-Verzeichnisse u.U. sehr zeitaufwändig. Ein rekursives Löschen der “.svn”-Verzeichnisse auf allen (!) Verzeichnisebenen der kopierten Ordner ist erforderlich.

Bei einer hinreichend komplexen und umfangreichen Ordner-Struktur braucht man dann gar nicht anfangen, mit “Dolphin” oder anderen grafischen Dateimanagern
herumzuwurschteln. Ich nutze in einer solchen Situation lieber die Kommandozeile.

Dort gibt es dann im Hauptverzeichnis der kopierten Ordnerstruktur (hier: “/projekte/dw/ntcomp” ) folgende Möglichkeit für die Vorsichtigen:

tux@my:/projekte/dw/ntcomp> find . -name “.svn” -exec rm -r {} \;
rm: reguläre Datei (schreibgeschützt) ?./ntc/scripts/_notes/.svn/prop-base/dwsync.xml.svn-base? entfernen? yes

Hierbei muss man allerdings das Entfernen jeder einzelnen Datei unterhalb der gefundenen “.svn”-Verzeichnisse bestätigen! Auch das ist aufwändig – wenn auch vielleicht schneller als die Arbeit mit grafischen Dateimanagern.

Für die weniger Vorsichtigen tut es auch ein

tux@my:/projekte/dw/ntcomp> find . -name “.svn” -exec rm -fR {} \;

oder ein

tux@my:/projekte/dw/ntcomp> find . -type d -name .svn | xargs -i rm -r {};

In den beiden letzten Fällen muss man die Löschaktion nicht bestätigen und das rekursive Löschen braucht nur Millisekunden. Also Vorsicht: Doppelt und dreifach checken, dass man das Richtige im richtigen Verzeichnis tut – und vorher lieber mal alles sichern! Lieber auch nur den “.” als “./” angeben. Vergisst man bei “./” durch Fehler den “.” hat dies je nach Rechtesituation evtl. gravierende Auswirkungen!

Wenn ich alles richtig gemacht habe, ist mein neues PHP-Projekt schließlich von allem ungewollten SVN-Ballast alter Projekte, aus denen ich Verzeichnisse kopiert habe, befreit.

Und dann funktioniert auch die SVN-Verwaltung des neuen Projektes mit den sich spezifisch entwickelnden Klassen und Programmen wieder wie gewünscht – nämlich vollständig und unabhängig von alten Projekten !

Schritt 3 braucht für diejenigen, die SVN unter Eclipse einsetzen, keine besondere Erläuterung. Man “shared” das Projekt erneut (Menüpunkt “SVN” >> “Share Projects …” ) und hangelt sich durch die menügeführte SVN-Einrichtung für das Projekt.

Viel Spaß weiterhin mit Eclipse, SVN und PHP.