codehunter
Goto Top

Cache Control bei PHP session start ändern

Ladezeiten dynamischer Webseiten verbessern und Server-Traffic reduzieren durch verändertes Cache Control beim Apache-PHP-Gespann

Hallo zusammen!

Ich betreibe eine umfangreiche, CSS-Layout-basierte Website mit vielen kleinen Bildchen, CSS-Dateien usw. Seit langem ärgerte ich mich darüber, daß die Seite so träge war und lange Ladezeiten hatte weil die Browser bei jedem Pagehit die komplette Seite inkl. aller Bildchen herunter luden anstatt ihren Cache zu benutzen.

Daß das was mit den HTTP-Cache-Headern zu tun hat war mir schnell klar. Nur findet man im Netz fast ausschließlich Anleitungen, wie man ein permanentes Neu-Laden erzwingt - hier ist aber genau das Gegenteil gefragt.

Also habe ich zuerst mal mit Wireshark die Kommunikation angeschaut und festgestellt, daß der Server bei jedem Pagehit die HTTP-Header "Cache-Control: no-cache, must-revalidate" sowie "Pragma: no-cache" schickte. Eine entsprechende Anweisung gab es aber im gesamten Script nicht. Schlimmer noch: Explizit anders lautende Cache-Anweisungen wurden ignoriert. Ich habe dann durch Auskommentieren das Problem auf die PHP-Anweisung session_start eingegrenzt. Diese bringt den Apache-Webserver dazu, andere HTTP-Cache-Header zu senden (warum auch immer, notwendig ist das eigentlich nicht).

Daher habe ich mein Script direkt um den Aufruf von session_start herum ergänzt:
  session_start ();
  $mez_to_gmt_offset = 3600; // MEZ = GMT + 1 Stunde (3600 Sekunden), kann man sich auch dynamisch berechnen lassen
  $cache_lifetime = 3600; // Lebenszeit des Browser-Cache 1 Stunde (3600 Sekunden)
  header(strftime("Expires: %a, %d %h %Y %H:%M:%S GMT", time() + $mez_to_gmt_offset + $cache_lifetime)); // Zeitpunkt festlegen ab dem der Browser-Cache verfällt  
  header(strftime("Last-Modified: %a, %d %h %Y %H:%M:%S GMT", time() + $mez_to_gmt_offset)); // Zeitpunkt der HTML-Generierung  
  header("Cache-Control: max-age=$cache_lifetime, private"); // Festlegen dass der Cache nur einen bestimmten Zeitraum lang gültig ist  
  header("Pragma: cache"); // Eigentlich veraltete Pragma-Anweisung, die hier aber eine große Rolle spielt (siehe unten)  

Nach viel experimentieren habe ich diese Header-Konstruktion als funktionierend herausbekommen. Je nach Browser scheint die Reihenfolge sogar eine Rolle zu spielen. Wichtig ist, daß man die beiden Zeitpunkte (Generierung der HTML-Ausgabe des Scriptes sowie Verfallsdatum des Cache) angibt sonst weiß der Browser nicht genau was zu tun ist und ignoriert die Cache-Anweisung. "Cache-Control: max-age=3600, private" gibt noch einmal die Lebensdauer des Cache an sowie eine Anweisung, was zu tun ist wenn der Cache verfällt - "private" bedeutet, dass der Cache Session-bezogen ist (Danke an dog für den Hinweis)

Wirklich interessant ist die letzte Header-Anweisung "Pragma: cache": Im RFC 2616 Sektion 14 ist für Pragma als einzig gültiger Wert "no-cache" definiert. Sendet man "Pragma: no-cache" dann ignorieren alle Browser die obigen Lifetime-Anweisungen und laden die Seite dennoch jedesmal neu. Lässt man die Pragma-Anweisung weg, so folgen einige Browser (IE, Safari) der Lifetime-Anweisung, andere dagegen nicht (Firefox, Opera). Also habe ich (zugegebenermaßen blind geraten) einfach mal "Pragma: cache" angegeben. Und siehe da, alle Browser - zumindest die mir verfügbaren Versionen - folgten der Cache-Lifetime. Warum das so ist und einige Browser sich hier ausnehmend NICHT standardkonform verhalten ist mir schleierhaft.

Summa summarum: Die Ladezeiten der einzelnen Seiten haben sich drastisch von durchschnittlich 3,5 Sekunden auf etwa 0,5 Sekunden reduziert. Besonders angenehm fällt nun beim Opera-Browser auf, daß das CSS-Layout nicht erst progressiv zusammengebastelt wird sondern sofort ordentlich aussieht. Beim Server-Traffic ist der Unterschied noch stärker zu sehen: nun noch 27 MB pro Tag wo es vorher 450 MB waren.

Vielleicht hat jemand eine Erklärung für das seltsame Verhalten einiger Browser mit der "Pragma: cache"-Anweisung. Mir persönlich reicht es, daß dieser Workaround funktioniert.

Grüssle
Cody

Content-Key: 144101

Url: https://administrator.de/contentid/144101

Printed on: April 20, 2024 at 02:04 o'clock

Member: dog
dog Jun 03, 2010 at 08:18:21 (UTC)
Goto Top
(warum auch immer, notwendig ist das eigentlich nicht).

Natürlich ist das notwendig.
Würde PHP bei einer offenen Session erlauben eine Seite zu cachen könnte das auch ein Proxy machen.
Damit könnte eine Session zwischen allen Benutzern eines Proxys geteilt werden, was katastrophal wäre.
Wenn du schon ein Caching bei Sessions erlaubst dann immer nur mit der Option
Cache-Control: private

gibt noch einmal die Lebensdauer des Cache an (eigentlich unsinnig da durch die beiden Zeitpunkte bereits definiert)

Auch hier hätte nachschauen geholfen:

The expiration time of an entity MAY be specified by the origin server using the Expires header (see section 14.21). Alternatively, it MAY be specified using the max-age directive in a response [...] If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive
Member: Codehunter
Codehunter Jun 03, 2010 at 08:34:49 (UTC)
Goto Top
Zitat von @dog:
Wenn du schon ein Caching bei Sessions erlaubst dann immer nur mit der Option
> Cache-Control: private

Danke für den Hinweis! War mir nicht bewusst dass Caches unter Umständen geshared werden können. Ich ändere die Anleitung entsprechend.
Member: RoterFruchtZwerg
RoterFruchtZwerg Jun 03, 2010 at 11:37:40 (UTC)
Goto Top
Okay... Du hast ein Session-Basiertes System, das hat man üblicherweise dann, weil man Nutzern (personalisierte) dynamische Inhalte präsentieren will. Damit geht (i.d.R.) einher, dass sich Seiteninhalte spontan ändern können. Ich sehe daher keinen Sinn darin, die dynamisch generierten Seiten cachen zu lassen.

Dein ursprünglich beschriebenes Problem, dass du haufenweise CSS-Dateien und vermutlich endlos Grafiken hast, die andauernd geladen werden, hat damit aber doch gar nichts zu tun. Du musst unterscheiden, ob die aufgerufene Ressource eine dynamische ist (Quelltext deiner Startseite z.B.) oder eine statische (deine CSS, JavaScript und Grafikdateien vermutlich).
Natürlich macht es sinn, die statischen Dateien cachen zu lassen, aber doch nicht die dynamischen?
Was du jetzt erreichst, ist ein cachen der dynamischen Seiten. Für die statischen Inhalte hast du aber gar nichts geändert, denn ich denke nicht dass oben genanntes PHP-Schnipsel in deine CSS und Grafik-Dateien eingebunden ist, oder?

Sinn voll wäre es, die notwendigen Header zum cachen von statischen Dateien mittels .htaccess Direktiven für die jeweiligen Dateitypen (css,js,gif,png,jpg,ico,...) zu setzen, das cachen von dynamischen Seiten aber weiterhin, wie ursprünglich, zu unterbinden.

Wenn es dann immer noch langsam ist, solltest du dir über die Ausführungszeit deiner dynamischen Skripte Gedanken machen. Denn dass dein jetziger Ansatz augenscheinlich funktioniert, legt nahe, dass nicht die vielen einzelnen Ressourcen das Problem sind, sondern die Ausführungszeit deiner Skripten.

Tipp: Google Chrome kann eine schöne Timeline der Lade und Ausführungszeit aller geladenen Ressourcen erstellen. Ist hierbei sicher hilfreich.
Member: Codehunter
Codehunter Jun 03, 2010 at 12:39:54 (UTC)
Goto Top
Die Ausführungszeiten der Scripte sind ja gerade NICHT das Problem sondern die umfangreichen Resourcen die nachgeladen werden. Das äußert sich ja auch indirekt in den drastisch gefallenen Traffic-Mengen.

Der dynamische Anteil beschränkt sich im wesentlichen auf eine Sprachumschaltung die im Normalfall einmal pro Session geschieht. Das Interessante bei der Geschichte ist ja, dass die dynamischen Anteile im HTML durchaus auch bei aktiviertem Cache funktionieren, die statischen Anteile jedoch nicht neu geladen werden. (Modified-Header wird ja jedesmal auf neueres Datum gesetzt) Also im Grunde genau das was ich erreichen wollte.

Um deine Frage zu beantworten: Der o.g. PHP-Code ist tatsächlich auch für CSS- und JS-Dateien relevant da diese ebenfalls von PHP erzeugt werden - die Bilder allerdings nicht. Daher wäre der Ansatz, die Header über die .htaccess zu ändern auch interessant. Nur leider habe ich dazu bisher nichts finden können.
Member: dog
dog Jun 03, 2010 at 12:48:26 (UTC)
Goto Top
Stichwort bei statischen Elementen: Versioned URLs.
Der Rest ist:
ExpiresActive On
ExpiresDefault "access plus 3 years"  
Member: RoterFruchtZwerg
RoterFruchtZwerg Jun 03, 2010 at 12:52:43 (UTC)
Goto Top
Zitat von @Codehunter:
Um deine Frage zu beantworten: Der o.g. PHP-Code ist tatsächlich auch für CSS- und JS-Dateien relevant da diese
ebenfalls von PHP erzeugt werden - die Bilder allerdings nicht.

Was du vielleicht erwähnen hättest können, denn das ist kein Regelfall. Wenn also "normale" Leute deinen Tipp beherzt hätten, hätte ihnen das eben rein garnichts gebracht. Daher die Unterscheidung zwischen statischen und dynamischen Ressourcen und nicht einfach "PHP-Skript" ;)

Für dich funktioniert deine Lösung, aber Andere können das ohne diese Information weder nachvollziehen noch reproduzieren.
Member: Codehunter
Codehunter Jun 03, 2010 at 13:28:18 (UTC)
Goto Top
Zitat von @RoterFruchtZwerg:
Was du vielleicht erwähnen hättest können, denn das ist kein Regelfall. Wenn also "normale" Leute deinen
Tipp beherzt hätten, hätte ihnen das eben rein garnichts gebracht. Daher die Unterscheidung zwischen statischen und
dynamischen Ressourcen und nicht einfach "PHP-Skript" ;)

Ich denke das ist nicht ganz richtig. Ursache aller Probleme ist da nicht die Art und Weise wie die HTML-, CSS- und JS-Codes entstehen sondern die Tatsache, daß session_start() die Header-Cache-Anweisungen manipuliert. Insofern dürfte das Problem bei jedem PHP auftreten welches dieses Verhalten zeigt. Ich habe meinen Solve-Code vollkommen außerhalb meines großen PHP-Projektes entwickelt und getestet und konnte den Effekt auch dort reproduzieren.

Einfach gesprochen: Sobald man im PHP session_start() verwendet beginnen die Browser, die gesamte Resourcen-Wolke ohne Cache zu laden. Wie wir aber schon festgestellt haben ist das bei statischen Inhalten nutzlos. Irritierend ist eben, dass dieses Verhalten nicht dokumentiert ist. Man findet nur Verweise auf session_cache_expire() welches sich aber auf den serverseitigen Session-Cache bezieht und nicht auf den clientseitigen.
Member: RoterFruchtZwerg
RoterFruchtZwerg Jun 03, 2010 at 13:41:12 (UTC)
Goto Top
Zitat von @Codehunter:
Einfach gesprochen: Sobald man im PHP session_start() verwendet beginnen die Browser, die gesamte Resourcen-Wolke ohne Cache zu
laden.

Nein, wie kommst du da drauf? Der Browser behandelt jede Ressource einzeln.
In deinem Fall, wo du das Session-Script auch in CSS und JS Dateien eingebunden hast, mag die beobachtete Behandlung zwar für einen Großteil deiner Wolke zutreffen, aber nicht auf die Gesamte.

Wie wir aber schon festgestellt haben ist das bei statischen Inhalten nutzlos. Irritierend ist eben, dass dieses Verhalten
nicht dokumentiert ist.

PHP kann nicht wissen ob deine Inhalte wirklich statisch oder dynamisch sind, also muss es annehmen dass alles innerhalb deiner Session dynamisch ist, ich finde das durchaus nachvollziehbar. In wie weit es (nicht) dokumentiert ist, weiß ich aber nicht.

Ich will nochmal deutlich machen: Dein Ansatz ist ja nicht falsch. Du beschreibst, wie du den Browser dazu veranlassen kannst, Ressourcen welche das oben gepostete PHP-Schnipsel enthalten, im Cache abzulegen, obwohl die PHP-Session Funktionen ursprünglich anderes wollten. Soweit alles okay.
Lediglich von dieser Tatsache auf einen gesamten Webauftritt zu schließen ist falsch, denn es betrifft ja nur eben die Skripten, welche den Code enthalten und das kann durchaus eine Minderheit an Ressourcen auf einem Server sein.

Es ist daher wichtig darauf hinzuweisen, dass gerade die anderen Ressourcen ohne diesen Code (und das sind eben Bilder und in den meissten Fällen auch CSS und JS usw.) auch beachtet werden sollten. Auch dort kannst du, z.B. wie oben mit .htaccess beschrieben, die notwendigen Header setzen und den Seitenaufruf so beschleunigen.
Member: Codehunter
Codehunter Jun 03, 2010 at 13:53:50 (UTC)
Goto Top
Zitat von @RoterFruchtZwerg:
Ich will nochmal deutlich machen: Dein Ansatz ist ja nicht falsch. Du beschreibst, wie du den Browser dazu veranlassen kannst,
Ressourcen welche das oben gepostete PHP-Schnipsel enthalten, im Cache abzulegen, obwohl die PHP-Session Funktionen
ursprünglich anderes wollten. Soweit alles okay.
Lediglich von dieser Tatsache auf einen gesamten Webauftritt zu schließen ist falsch, denn es betrifft ja nur eben die
Skripten, welche den Code enthalten und das kann durchaus eine Minderheit an Ressourcen auf einem Server sein.

Also mein Testkonstrukt war eigentlich ganz simpel (XHTML-Rumpfdaten mal ausgeblendet):
<?php
  // session_start();
?>
<img src="/test.php" />  

Dann habe ich wie eingangs erwähnt, mir die GESAMTE Kommunikation zwischen Browser und Server mittels Wireshark angeschaut. Tatsache ist, daß alle mir verfügbaren Browser begonnen haben, das statische /test.png bei jedem Pagehit zu laden sobald session_start() nicht mehr auskommentiert war. Der Browser schickte für /test.png einen separaten GET-Request, welcher ebenfalls mit einem "Pragma: no-cache"-Header beantwortet wurde. Also muss doch session_start() mehr tun als nur für den einen Request die Header zu modifizieren.

Dieses Testscript lag auf einem separaten VHost welcher mit Standard-Apache-Konfiguration lief, es wurde nichts mittels .htaccess überschrieben. Zur Gegenprüfung habe ich dann auch noch zwei weitere unabhängige PHP-Installationen auf anderen Servern damit getestet - selbes Ergebnis.
Member: RoterFruchtZwerg
RoterFruchtZwerg Jun 03, 2010 at 14:03:50 (UTC)
Goto Top
Zitat von @Codehunter:
Dann habe ich wie eingangs erwähnt, mir die GESAMTE Kommunikation zwischen Browser und Server mittels Wireshark angeschaut.
Tatsache ist, daß alle mir verfügbaren Browser begonnen haben, das statische /test.png bei jedem Pagehit zu laden
sobald session_start() nicht mehr auskommentiert war. Der Browser schickte für /test.png einen separaten GET-Request, welcher
ebenfalls mit einem "Pragma: no-cache"-Header beantwortet wurde. Also muss doch session_start() mehr tun als nur
für den einen Request die Header zu modifizieren.

Also... PHP hat auf die Ressource "/test.png" keinerlei Zugriff. Es weiß weder, wann und ob dein Browser die Ressource abruft, noch hätte es die Möglichkeit irgendwelche Header dafür zu ändern.
Ich gehe mal davon aus, dass dein Server beim Aufruf des "/test.png" IMMER den Header "Pragma: no-cache" sendet. Da du Wireshark laufen hast, solltest das ja mal eben prüfen können. Und hier solltest du ansetzen, anstelle PHP magische Fähigkeiten nachzureden face-smile
Member: Codehunter
Codehunter Jun 03, 2010 at 14:31:06 (UTC)
Goto Top
Das ist ja der Witz an der Sache, der Apache sendet eben NICHT immer "Pragma: no-cache" beim Aufruf der /test.png sondern nur dann wenn PHP zuvor eine Session geöffnet hat. Ich vermute, daß die Browser alle vom HTML-Dokument abhängigen Resourcen (verlinkte bzw. eingebettete Bilder etc.) im Session-Kontext der übergeordneten HTML-Datei abrufen. Das Session-Cookie wird jedenfalls mitgesendet wenn /test.png abgerufen wird. Wahrscheinlich wird so der Apache dazu gebracht, die Cache-Header für Bilddateien zu ändern. Somit wäre PHP auf Umwegen dann doch wieder das zentrale Element.

Eben wegen dieser etwas undurchsichtigen Verzahnung habe ich auch so lange nach einer PHP-seitigen Lösung gesucht. Die Lösung über die .htaccess den einzelnen Dateitypen unterschiedliches Cache-Verhalten zuzuweisen ist natürlich eleganter - erfordert aber auch dass auf dem Webserver das Modul mod_expire installiert ist (was nicht immer der Fall sein dürfte)
Member: RoterFruchtZwerg
RoterFruchtZwerg Jun 03, 2010 at 15:11:52 (UTC)
Goto Top
Also, was der Browser abruft hat ja noch nichts damit zu tun, was der Server an Headern sendet.
Erst einmal muss man sicherstellen dass der Server die Header zum Speichern der Ressource sendet, danach kann man sich Gedanken machen ob und warum der Browser sie trotzdem zu häufig abruft.

Möglicherweise existiert auf deinem Server wirklich ein Modul, welches Header je nach Gegebenheit setzt. Das einzige woran der Server aber ausmachen könnte, dass die Ressource innerhalb einer Session abgerufen wird, ist aber das Session-Cookie. Und das wird vom Browser auch noch gesendet, wenn du die session_start() Zeile wieder auskommentierst, da es dadurch ja nicht gelöscht wird. Insofern müsste dir das eigentlich aufgefallen sein, dass ein Auskommentieren der Zeile nichts hilft.

Kann es vielleicht sein, dass PHP deine png-Dateien doch interpretiert?
Entweder, weil du deren Aufruf vielleicht durch mod_rewrite in ein PHP-Skript umbiegst, oder weil du in der Apache-Konfiguration vielleicht den Content-Type image/png von PHP parsen lässt?

Nach wie vor finde ich es nämlich seltsam, wenn der Server die Header ändern würde, solange die Datei NICHT von PHP verarbeitet wird.
Member: Codehunter
Codehunter Jun 04, 2010 at 09:50:57 (UTC)
Goto Top
Zitat von @RoterFruchtZwerg:
Also, was der Browser abruft hat ja noch nichts damit zu tun, was der Server an Headern sendet.
Erst einmal muss man sicherstellen dass der Server die Header zum Speichern der Ressource sendet, danach kann man sich Gedanken
machen ob und warum der Browser sie trotzdem zu häufig abruft.

So kenne ich das eigentlich auch.

Möglicherweise existiert auf deinem Server wirklich ein Modul, welches Header je nach Gegebenheit setzt. Das einzige woran
der Server aber ausmachen könnte, dass die Ressource innerhalb einer Session abgerufen wird, ist aber das Session-Cookie. Und
das wird vom Browser auch noch gesendet, wenn du die session_start() Zeile wieder auskommentierst, da es dadurch ja nicht
gelöscht wird. Insofern müsste dir das eigentlich aufgefallen sein, dass ein Auskommentieren der Zeile nichts hilft.

Eben genau das ist NICHT der Fall. Sobald ich die Zeile auskommentiere sendet der Browser ab dem zweiten darauf folgenden Abruf das Session-Cookie nicht mehr mit.

Kann es vielleicht sein, dass PHP deine png-Dateien doch interpretiert?
Entweder, weil du deren Aufruf vielleicht durch mod_rewrite in ein PHP-Skript umbiegst, oder weil du in der Apache-Konfiguration
vielleicht den Content-Type image/png von PHP parsen lässt?

Nur wenn das die Standardkonfiguration vom Apachen wäre face-wink Also: NEIN. Aber um ganz sicher zu sein habe ich mal eine simple Test-PHP in .png umbenannt und aufgerufen, der Output war nicht interpretiert sondern der originale PHP-Code.

Nach wie vor finde ich es nämlich seltsam, wenn der Server die Header ändern würde, solange die Datei NICHT von PHP
verarbeitet wird.

Geht mir ganz genauso, deswegen habe ich auch so lange gebraucht um überhaupt nen Ansatz zu finden - ohne Wireshark keine Chance weil dieses Verhalten weder dokumentiert noch logisch erklärbar ist. Zumindest kann es nicht an einer wilden Serverkonfiguration liegen da sich hier ganz verschiedene Server bei unterschiedlichen Webhostern und mein eigener im Intranet gleich verhalten. Ich vermute einfach dass sich hier im PHP selbst was eingeschlichen hat - ich verwende intern nur Versionen ab 5.2.3 und online 5.2.12.

Zumindest den Teil dass session_start() die Header für PHP-Requests verändert könnte man so erklären. Aber wie Du richtigerweise bemerkst erklärt dies nicht warum es sich auch auf statische Inhalte auswirkt. Der einzige Zusammenhang ist das Session-Cookie das die Browser dann auch für statische Inhalte mitsenden. Dann müßte es so sein dass der Apache per Default den Header "Pragma: no-cache" sendet wenn im Request ein Session-Cookie enthalten war. Das wäre aber ziemlich dumm denn so treibt man nur unnützerweise den Traffic nach oben.

Interessenhalber habe ich mir ein paar willkürlich ausgewählte phpbb-und xtcommerce-Installationen mit Wireshark angeschaut und festgestellt, daß die das selbe Problem zu haben scheinen und so eine Menge Overhead produzieren. Ich habe dann einen befreundeten Webmaster gebeten, meinen Workaround in seinem xtcommerce zu installieren. Er hatte daraufhin den selben spürbaren Performance-Boost ohne Auswirkungen auf die Funktionalität auf die dynamischen Komponenten. Also funktioniert mein Code nicht nur in meiner selbstgefrickelten Site sondern auch bei Standard-PHP-Software.

Bin ich hier unwissenderweise über einen unbekannten Bug im Apache-PHP-Gespann gestolpert und habe es nur irrtümlich für ein Problem an meinem Script gehalten?
Member: RoterFruchtZwerg
RoterFruchtZwerg Jun 04, 2010 at 10:02:35 (UTC)
Goto Top
Zitat von @Codehunter:
Bin ich hier unwissenderweise über einen unbekannten Bug im Apache-PHP-Gespann gestolpert und habe es nur irrtümlich
für ein Problem an meinem Script gehalten?

Naja... Das Problem was ich mit dieser Vorstellung immer noch habe ist, dass der Apache PHP ja gar nicht ausführt, wenn du eine statische Datei lädst. Da kann PHP noch so wild um sich schlagen oder Bugs ohne Ende haben - ich wüsste nicht wie das Einfluss auf statische Inhalte haben kann.
Läuft PHP als CGI oder Modul?
Auch würde mich interessieren, was dazu führt, dass dein Browser beim zweiten Aufruf nach dem Auskommentieren das Session-Cookie nicht mehr sendet. Dafür kann es ja nur zwei Gründe geben:
Entweder sendet PHP bei gesetztem Session-Cookie und ohne session_start() ein Lösch-Cookie (also das Session-Cookie mit Datum in der Vergangenheit) oder aber PHP setzt einfach kein Cookie und dein Browser entscheidet darauf hin eigenwillig, dass das Cookie nicht mehr gesendet werden muss (was aber irgendwie gegen Spezifikationen verstößen würde).
Member: Codehunter
Codehunter Jun 04, 2010 at 10:45:40 (UTC)
Goto Top
Zitat von @RoterFruchtZwerg:
Naja... Das Problem was ich mit dieser Vorstellung immer noch habe ist, dass der Apache PHP ja gar nicht ausführt, wenn du
eine statische Datei lädst. Da kann PHP noch so wild um sich schlagen oder Bugs ohne Ende haben - ich wüsste nicht wie
das Einfluss auf statische Inhalte haben kann.

Vollkommen richtig. Das kann eigentlich nur etwas sein das sich im Zusammenspiel mit dem Browser clientseitig (weiter) entwickelt. Quasi "Der Server tut komische Sachen also mach ich mal irgendwas bevor ich gar nix mache"

Läuft PHP als CGI oder Modul?

CGI

Auch würde mich interessieren, was dazu führt, dass dein Browser beim zweiten Aufruf nach dem Auskommentieren das
Session-Cookie nicht mehr sendet. Dafür kann es ja nur zwei Gründe geben:
Entweder sendet PHP bei gesetztem Session-Cookie und ohne session_start() ein Lösch-Cookie (also das Session-Cookie mit Datum
in der Vergangenheit) oder aber PHP setzt einfach kein Cookie und dein Browser entscheidet darauf hin eigenwillig, dass das Cookie
nicht mehr gesendet werden muss (was aber irgendwie gegen Spezifikationen verstößen würde).

Ich denke letzteres ist der Fall. Zumindest soweit ich die Wireshark-Protokolle interpretieren kann und in den TCP-Paketen nicht noch irgendetwas binär kodiertes übertragen wird das weniger offensichtlich ist als die Klartext-Header (so tief steck ich im TCP-Protokoll nun auch nicht drin) dann hört der Server einfach auf den Cookie und den Header zu senden und der Client seinerseits verlässt daraufhin die Session und aktiviert wieder seinen Cache. Es ist nicht so daß sich die Cache- und Cookie-Header verändern würden (Datum in der Vergangenheit usw.) sondern sie verschwinden gänzlich aus den HTTP-Headern, sowohl in der Server->Client- als auch in der Client->Server-Richtung.

Evtl. funktioniert mein Workaround dann so als würde sich das Ganze dann wieder mehr dem Standardverhalten annähern. Immerhin werden dann in jedem Fall Header mit gültigen Lifetime-Angaben gesendet und fehlen nicht einfach komplett. Die manuell gesetzte Lifetime in meinem obigen Code wird übrigens von den Browsern korrekt interpretiert: Nach einer Stunde wird der Cache invalide und der Browser lädt die gesamte Resourcen-Wolke einmal neu.