WordPress-Postings in zufälliger Reihenfolge

Wenn ein Blog eine Menge Artikel hat, kann es passieren, dass die älteren Beiträge so tief im Archiv versunken liegen, dass der interessierte Leser die kaum noch zur Kenntnis nehmen kann.

Das ist gerade bei Artikeln, die eher zeitlos sind, schon ein richtiges Problem. Mein Blogprojekt „orga-dich“ hatte genau dieses Problem.

Zufällige Reihenfolge

WordPress ermittelt die Postings, die in der Blogroll, also in der Liste der Beiträge, angezeigt werden, über ein wp_query-Objekt. Davon gibt es erfahrungsgemäß mehrere innerhalb eines Blogs, beispielsweise zum Auslesen aller Menüseiten oder innerhalb bestimmter Seitenelemente, die ebenfalls Artikel oder Seiten enthalten sollen.

Aber es gibt auch eine „Master-Query“, die für die Hauptseite zuständig ist. Und eine Query kann auch Parameter haben, die wir übersteuern können…

Oha, functions.php, ick hör dir trapsen

Innerhalb eines WordPress-Monsters können wir uns mit sogenannten Hooks an verschiedenen Stellen der Verarbeitung „beteiligen“.

Und einer davon ist der „posts_orderby„-Hook: Über den können wir zusätzliche Parameter für die Sortierung an das Query-Objekt mitgeben – also frisch ran ans Werk.

Captain Hook

Einen filter zu einem hook zu schreiben ist banal:

add_filter( 'posts_orderby', 'zufaellige_postings' );

Damit haken wir uns in den „posts_orderby„-Hook ein und geben bekannt, dass die PHP-Function „zufaellige_postings“ ausgeführt werden soll, sobald dieser Hook/diese Action ausgeführt wird.

Diese Funktion sieht für unseren Bedarf auch noch recht übersichtlich aus:

function zufaellige_postings( $orderby ) {
   if( is_front_page() ) {
      $orderby = 'RAND';
      return $orderby;
   }
}

Das „is_front_page()“ sorgt dafür, dass wir nur die Query beackern, die für die Anzeige der Startseite mit der Beitragsliste zuständig ist. Wir wollen ja nicht sämtliche DatenbankPost-Queries zufällig gestalten 🙂

That’s it – oder?

Hm – im Prinzip war es das. Aber wir haben ein Problem. Startseiten enthalten ja nicht grundsätzlich alle Postings des Blogs. Sondern immer nur eine Auswahl, der Rest möchte über eine Seitenwahl ausgewählt werden, die im Fachjargon „Pagination“ heißt:

pagination
pagination

Also dieser Bereich mit den Zahlen (1 2 … 4) und Pfeilen. Damit navigiert der geneigte Leser zwischen den vielen hunderttausend Postings eines Blogs.

Wenn wir unsere Zufallsmethode jetzt live schalten, wird auch alles schön gewürfelt – leider aber auf jeder Seite von Neuem! Das kann ja nicht gewollt sein…

Schöne Lösung aus dem Netz der Netze

Und tatsächlich, Ehre wem Ehre gebührt, ein User mit dem klangvollen Namen „hlashbrooke“ (Hugh Lashbrooke) hat auf GitHub eine passende Lösung gefunden.

Er speichert eine zufällig generierte Seed-Zahl in einer Session-Variable und übergibt sie immer erneut an das Query-Objekt als „rand„-Parameter. Dadurch beginnt der Zufallsgenerator immer wieder beim selben Ausgangswert (= Seed) und generiert dieselben Zufallszahlen.

Wie wir wissen, generieren übliche Algorithmen keine ernsthaft zufälligen Zahlenfolgen – sie brauchen einen Ausgangspunkt (den sog. „Seed“) und wenn dieser gleich ist, wird die Reihenfolge der „zufälligen“ Zahlen auch immer gleich sein.

Auf diese Weise wird bei einem seitenweisen Abruf der Postings die einmal erstellte Reihenfolge beibehalten – und liefert entsprechend nicht für jede Seite neue, zufällige Postings sondern liefert die Postings in einer zufälligen Folge innerhalb der gesamten Query.

So sieht das Spaß dann aus:

session_start();

add_filter( 'posts_orderby', 'randomise_with_pagination' );
function randomise_with_pagination( $orderby ) {

	if( is_front_page() ) {

	  	// Reset seed on load of initial archive page
		if( ! get_query_var( 'paged' ) || get_query_var( 'paged' ) == 0 || get_query_var( 'paged' ) == 1 ) {
			if( isset( $_SESSION['seed'] ) ) {
				unset( $_SESSION['seed'] );
			}
		}
	
		// Get seed from session variable if it exists
		$seed = false;
		if( isset( $_SESSION['seed'] ) ) {
			$seed = $_SESSION['seed'];
		}
	
	    	// Set new seed if none exists
	    	if ( ! $seed ) {
	      		$seed = rand();
	      		$_SESSION['seed'] = $seed;
	    	}
	
	    	// Update ORDER BY clause to use seed
	    	$orderby = 'RAND(' . $seed . ')';
	}

	return $orderby;
}
?>

Leider geil – und funktioniert super! Und ist auch angemessen dokumentiert, danke liebe(r) Hugh Lashbrooke (da sind noch mehr coole Sachen zu finden).

So sieht es dann aus

Im Ergebnis kommt das schon ganz nice.

orga-dich postings
orga-dich postings

Opcache precompile – jetzt wirklich

Beim letzten Beitrag wollte ich eigentlich auf das Primen des Opcaches kommen – aber ich wurde augehalten 😀

Opcache primen

Nachdem wir jetzt den Opcache vernünftig eingestellt haben, möchten wir den doch bitte auch ordentlich nutzen.

Und wenn ich eines nicht besonders gerne habe, sind das freigeräumte Resourcen, die dann doch erst mal nicht und/oder erst nach langer Nutzungszeit genutzt werden… ich möchte Nägel mit Köpfen machen.

Also nutzen wir doch die Möglichkeit, dass wir den Opcache bereits vorbefüllen können, bevor der PHP-Interpreter seine Griffel an ein jungfräuliches Scriptchen legt.

Jede PHP Datei also ausführen?

Äh… denkbar… aber… ich möchte dann bitte nicht der Server sein, denn dass wird sicher ein wenig heiß und unbequem…

Es gibt eine elegantere Lösung.

Elegantere Lösung

In PHP gibt es ein lustiges Kommando

opcache_compile_file

Das macht auch genau das, was man vermutet, wenn man es so liest: Es compiliert ein PHP-Script und – was man nicht liest – lädt es beim Opcache ab.

Jetzt müssen wir doch nur sämtliche PHP-Dateien einer Hierarchie zusammensuchen und diesem netten Herren übergeben, der sich dann um das kümmert, was unser Herzenswunsch ist.

PHP-Dateien zusammensuchen

Wir könnten jetzt natürlich das Rad neu erfinden und einen banalen rekursiven Algorithmus basteln, der alle PHP-Dateien in einer Baumstruktur heraussucht. Fingerübung.

Aber das hat glücklicherweise schon jemand anderes gemacht und hat dem Ungetüm selbstredend einen modernen, beschreibenden und unfassbar langen Namen gegeben:

RecursiveIteratorIterator

Rant.

Als ich anfing mit der Programmierung, haben wir alle recht schnell verstanden, dass hinter einem

mov

eigentlich ein „move“ steht. Und hinter einem

br

ein Branch (also „goto“ – damals war das noch nicht böse, heute in Assembler auch noch nicht). Möchte jemand wissen, dass es danach, für heutige Gehirne auch ungewöhnlich, weitergeht, nämlich „erst das Ziel, dann die Quelle“?

mov ax, 01h
int 33h

Heißt nix anderes als „schieb 01h in Register ax und rufe den Interrupt 33h auf“ – das zusammen schaltete den Mauscursor ein 🙂 Irgendwie ist das doch knackig auf den Punkt, oder?

Damals (in der „guten, alten Zeit ™“) hat man auch noch gewusst, dass man hier nicht kommentieren muss, dass „Register AX mit 01h gefüllt“ wird – sondern man schrieb davor „Mauscursor einschalten“, damit jeder mittelmäßig begabte Programmierer nach einem noch wusste, was dieser Buchstabensalat elegante Code macht.

Heute muss man den jungen (mittelalten und vielen älteren) Programmierern alles möglichst mit Buntstiften und in aller Ausführlichkeit – möglichst unter Verwendung sämtlicher Buchstaben des Alphabets – schon beim Kommando selbst erklären. Da werden dann aus gut und schnell schreib- und erlernbaren Kommandos riesige Buchstabensalatwüsten, die ohne (Auto-)Completion und Referenzen, so umfangreich wie die Bibel nebst Kommentar der ersten siebenhundert Päpste, nicht mehr beherrschbar sind.

HMPF. Ich werde alt.

Dann mal happy coding!

Ich könnte ja jetzt fies sein und sagen „baut euch damit jetzt den passenden Code“, aber ich will ja mal nicht so sein:

/**
 * OpCache - alle Dateien in den Cache hieven
 */

$path = realpath('.');

$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
foreach ($objects as $name => $object) {
 $path_parts = pathinfo($name);
 if (strtolower($path_parts['extension']) == 'php') {
 echo "$name";
 if (@opcache_compile_file($name)) {
 echo " compiled\n<br>";
 } else {
 echo " error\n<br>";
 }
 }
}

Wieso twitterst du opcache_compile_file?

*kopf* *tisch*

Ich möchte nicht sehen, wenn beim Compilieren was schief läuft, denn die Funktion liefert mir einen passenden Returncode… wozu dann noch die Konsole oder das Log vollmüllen?

Übrigens

Das Prime-Script bekommt man fehlerfrei nur ausgeführt, wenn es der tatsächliche Webserver ausführt, weil der Zend-Opcache in der oben genannten Konfiguration keine CLI-Scripte cached!

wget https://www.deinvollcoolerdomainname.de/opcache_prime.php

Übrigens Teil 2

Auf unserem Server prime ich übrigens nicht (mehr). Es hat sich gezeigt, dass einfach zu viele Scripte tatsächlich im Alltagsbetrieb nie angegriffelt werden – da lohnt es sich einfach nicht, den wertvollen Arbeitsspeicher dafür aufzuwenden.

Hintergrund ist, dass im WordPress-Universum Plugins nicht immer nur unbedingt genau eine Sache gut machen (etwas, was ich an Unix und seiner Philosophie so liebe) – manchmal können die neben einer bestimmten Sache noch ein Dutzend anderer, die ich nie brauche. Und die dafür nötigen Scripte müssen natürlich auch nicht gecached werden.

Übrigens sage ich „gekäitscht“ und nicht „gekääääscht“. Diesbezüglich hat mich ein Kollege letztens versucht zu korrigieren. Der lebt jetzt in Sibireien und sucht nach Öl. Naja. Nicht wirklich.

Opcache precompile

Grad hatte ich ja davon erzählt, dass ich wissen wollte, wie groß die PHP-Scripte in einer bestimmten Hierarchie sind.

Das habe ich jetzt für ein paar andere Verzeichnisbäume durchgelutscht und bin auf das Ergebnis gekommen, das

Opcache-Limit einzustellen 😀

Nun ja. Ganz fürchterlich großes Kino, eine Konfigurationsdatei anpacken und fertig.

Möchte man meinen, ja.

Aber doch nicht mit dem Dicken. Da gibt es doch so fürchterlich viele lustige Schalterchen, an denen man herumspielen kann, also suchen wir doch einfach mal eine Einstellungskombination, die möglichst maximalen Nutzen bei minimalem Schaden Speicherverbrauch möglich macht.

Was wollen wir?

Wir wollen sämtliche und alle noch so unnützen PHP-Scripte, die irgendwann und irgendwie mal ausgeführt werden, im OpCache haben.

Stecken wir uns doch einfach mal vernünftig hohe Ziele 🙂

Was wissen wir?

Zunächst mal, wir haben uns gerade die Größen besorgt, die unsere PHP-Scriptdateien insgesamt haben.

In meinem Fall waren das so 140MB und ’nen bisschen Kleingeld. Unterstellen wir des Weiteren, das die Scripte vollständig inklusive dem ganzen Müll (also den fast immer sinnlosen Kommentaren… was hab ich davon, wenn mir jemand sagt, dass jetzt sofort eine Schleife losgeht – statt mir zu sagen, wieso). Denn die Erfahrung lehrt, wenn die Kommentare beim Cachen abgeklemmt werden (was möglich ist), läuft nicht mehr alles, weil irgendein Held auf irgendwelche Kommentare angewiesen ist.

Ich könnte manchmal so brechen… wie auch immer. Ist halt so.

Und was wir auch noch wissen, naja, ich weiß es, ihr noch nicht – die Anzahl der PHP-Scripte. Das bekommt ihr hin, oder? Ich hab das so gemacht:

find . -type f -name '*.php' | wc -l

Das spuckt mir aus, wie viele Zeilen Output der find geworfen hat – und die wiederum entspricht dann der Anzahl der PHP-Dateien. Geht sicher eleganter, aber „it gets the job done“ und muss nicht erst durch ein Gremien beraten und durch ein weiteres beschlossen werden – „einfach mal machen“, meine Devise (…und manchmal hinterher doch noch mal nachdenken…).

Ach so, das Ergebnis. Bei mir waren es so 10.000 Dateien.

Wie…?

Mit vim.

Ok – jetzt mal im Ernst, WIE???

Hmpf. Ok.

vim /usr/local/etc/php56/conf.d/opcache.ini

Und bei der Anzeige erst mal einen frischen Kaffee holen und auf dem zweiten Bildschirm (ein dritter wäre mal eine Maßnahme) schon mal Google anwerfen und jede einzelne Option nachrecherchieren.

Ja wirklich. Jede. Und auch die, die nicht in der Standard-Konfiguration steht. Denn da sind wahre Schätze verborgen 😀

Ok. Dann eben nicht, ich finde es ja immer total spannend die ganzen Optionen auszuprobieren…

Endergebnis

In meinem Fall kam ich mit folgendem Erguss heraus, da ist vermutlich immer noch Spielraum für mehr Nerdyness, aber so tut es es erstmal und nimmt akzeptable Resourcen in Anspruch.

zend_extension = opcache.so

[opcache]
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 196
opcache.interned_strings_buffer = 64
opcache.max_accelerated_files = 12288
opcache.max_wasted_percentage = 10
opcache.use_cwd = 1
opcache.validate_timestamps = 1
opcache.revalidate_freq = 30
opcache.revalidate_path = 0
opcache.save_comments = 1
opcache.enable_file_override = 1
opcache.optimization_level = "0xffffffff"
opcache.inherited_hack = 1
opcache.dups_fix = 0
opcache.blacklist_filename =
opcache.max_file_size = 0
opcache.consistency_checks = 0
opcache.force_restart_timeout = 180
opcache.log_verbosity_level = 0
opcache.preferred_memory_model =
opcache.protect_memory = 0
opcache.mmap_base =
opcache.restrict_api =
opcache.enable_file_override = On
opcache.file_cache = "/var/opcache/cache.dat"

Die Fetten sind die, auf die es wirklich ankommt. Die Roten (ok und Fetten) sind die, bei denen ich noch überlege, ob ich mir ein kleines Auswertungsscript schreiben soll, das die maximal und mit machbarem Aufwand parsbaren, konstanten Strings aus den Scripten extrahiert und die Längen dann summiert… hebe ich mir für heute Nacht auf – nicht.

Die memory_consumption muss man leider aus mir nicht erklärbaren Gründen locker mal doppelt so hoch wählen wie die tatsächliche Scriptgröße. Auch noch eine offene Baustelle:

  • Klären, wieso memory_consumption so viel höher eingestellt werden muss.

Kleiner Pro-Tipp: Nicht das fast-shutdown aktivieren!!11eins. Das shreddert ziemlich schwer bemerkbar und nur in seltsam seltenen Situationen die Verarbeitung. Mal ganz abgesehen davon:

If enabled, a fast shutdown sequence is used that doesn’t free each allocated block, but relies on the Zend Engine memory manager to deallocate the entire set of request variables en masse.

Sich auf die Garbage Collection der Runtime zu verlassen hat irgendwie was von Java. Und da haben wir was gegen (*duck* & *weg*).

Du hast alles falsch gemacht, Mirko!

Falls jemandem auffällt, dass ich den Opcache dazu anhalte, dass er alle 30 Sekunden nachsieht, ob ein Script vielleicht zwischenzeitlich auf der Platte geändert wurde und erneute in den Cache geladen werden sollte – ja, das trifft zu. Ist auch besser so in einer Umgebung, die ständig von einem bestimmten, seltsamen Menschen befummelt wird und der nicht ständig den Opcache neu per Hand befüllen möchte.

Ajax Backendanfragen cachen – unmöglich…?

Auf unserer Webseite benutzen wir viele Elemente, die dynamisch mit Content aus dem Backend versorgt werden.

Beispielsweise die Liste unserer Informationsartikel:

ya info
ya info

Die Elemente dieser Liste werden per Ajax aus dem Backend gelutscht – einzeln.

Requests ohne Ende

Also hustet so eine „Grid“-Übersicht mal eben ein paar Dutzend Backendanfragen in Richtung Webserver – und dann passiert das hier:

  • Webserver findet, dass admin-ajax.php eine PHP-Script ist und fährt erst mal eine Instanz des PHP-Interpreters hoch
  • Das Script wiederum möchte gerne das WordPress-Imperium im Zugriff haben, also wird die Maschinerie in Gang gesetzt und das WordPress-Backend macht sich breit (ein paar hundert PHP-Scripte werden geladen und Dutzende Datenbankabfragen losgeschickt – bevor irgendetwas tatsächlich passiert).
  • Dann verarbeitet admin-ajax.php netterweise auch mal den Ajax-Request bzw. leitet den an die zuständige Stelle weiter.
  • Diese fragt dann ihrerseits diverse Tabellen ab – über WordPressroutinen üblicherweise. Also wp_query und Konsorten, die allesamt nicht gerade den Ruf genießen hochperformant optimierten SQL anzuwenden – kann jeder bewundern, wenn er sein MySQL-Slowlog anschmeißt und sich dann mal per explain die dicksten Statements erklären lässt…
  • Wenn dann alle Beteiligten glauben, dass der Content so weit generiert ist, beendet sich das PHP-Konglomerat und stellt eine Antwort für den Ajax-Request bereit.
  • Der wiederum wird dann vom rufenden Javascript in die Seite gelötet.

Das stellen wir uns jetzt mal ein paar Dutzend vor und wir wissen, wie schön es ist, ein sich langsam inkarnierendes Postgrid zu bewundern – Ladezeiten im mehrstelligen Sekundenbereich sind die Folge.

Da möchte man doch mal – Cache?

Klassischer Anwendungsfall eines Caches ist, dass Daten, die sowieso gleich sind, nicht aus der langsamst möglichen Ecke geholt werden sondern von dort, wo es sonnig und warm ist; idealerweise aus dem Arbeitsspeicher, wenn es nicht anders geht, so zumindest aus der Tiefkühltruhe und nur noch schnell in die Micowelle (also servierfertig und nur dezent zu bearbeiten).

Angeblich geht das mit WordPress-Ajax nicht.

Selten so gekichert… natürlich geht das. Man muss nur die schulterlangen Gummihandschuhe anziehen, Nasenklammern drauf und dann in der Suppe wühlen, bis man die richtigen Stellen gefunden hat, an denen man was anflanschen muss.

Jetzt passiert das hier:

  • Ajax-Request schlägt im Backend auf.
  • admin-ajax.php wird immer noch durch eine neue PHP-Interpretinstanz interpre… ausgeführt.
  • ohne WordPress hochzufahren wird erkannt, dass die gewünschte Action mit dem passenden Schlüsselzeugs schon in der „Datenbank“ herumlungert und die Antwort umgehend an den Anfrager zurückgeworfen.

Dabei heißt „Datenbank“ hier ein Key im Redis-Inmemory-Store.

redis cache
redis cache

Aber einmal muss ich durch den Cache…

Die allererste Anfrage muss natürlich durch den gesamten Dschungel, sonst weiß der Redis ja nicht, wie die Abkürzung aussieht.

Das muss aber nicht zwingend mit dem ersten Besucher passieren, der sich auf die Webseite traut.

node.js precache
node.js precache

Das kann ein nächtliches Script machen, was bei der routinemäßigen Wartung mitläuft und die relevanten Ajax-Requests simuliert, die tagsüber so auf das Backend einschlagen. Dieses Script wiederum nutzt node.js und jsdom und diverse andere ähm „Hilfsmittel“ – darüber schreibe ich ein anderes Mal, mir ist schon schlecht 😉

Optimierungsorgie Teil 2

Wie vorhin schon mal erwähnt, habe ich in den letzten zwei Wochen einige Optimierungsarbeiten an unserer MySQL-Datenbank vorgenommen.

Unsinn hoch Drei – Probierphase

Interessanterweise waren viele der Maßnahmen völlig für den Eimer – das habe ich aber erst seeeeehr spät festgestellt.

Beispiele

  • mysqltuner.pl immer wieder laufen lassen, der mir stoisch eine Erhöhung der tmp_table_size (und damit auch max_heap_table_size) empfiehlt.
  • Empfehlung, den innodb_buffer_pool_size auf 80% des installierten Arbeitsspeichers in den Wind geschlagen (20% für PHP? Nie im Leben) und gegen 50 und 60% getauscht.
  • query_cache an – und wieder aus – und wieder an – und wieder aus – man ist sich da im Expertennetz nicht einig: Einige verteufeln, andere bringen wieder den Standardspruch „your milage may vary“.
  • diverse Buffer hoch und wieder runter – meistens hoch

Irgendwann war dann die Datenbank so aufgeblasen, dass nur noch 2 oder 3 PHP-Instanzen ausführbar gewesen wären, wenn meine NAS mich das denn hätte so stark begrenzen lassen. Da ich für php mal sportlich

memory_limit = 300M

vorgesehen hatte (man weiß ja, WordPress mag es gerne ausufernd – falsch… aber das wusste ich da noch nicht) und in der Tat sich die PHP-Prozesse gerne mal so um die 200M reingetan haben, war irgendwann auch für den Mathe-Versager Mirko klar: Das passt alles nicht rein.

Hier liefen also ein halbes Dutzend PHP-Prozesse, eine Datenbank, mit der ich vermutlich die Deutsche Bank hätte versorgen können, ein OpCode-Cache, in den sämtlich 15.000 PHP Scripte reingepasst haben und noch diverse andere Dinge, die zusammen vermutlich so 12 bis 16GB Arbeitsspeicher hätten haben wollen – und ein paar lustige CPU-Kerne gleich noch oben drauf.

Katastrophenalarm – Wir werden geDDOSed.

Eines Morgens so gegen 11 dann der wahrgewordene Alptraum: Unser Webserver wird angegriffen!!!1eins

Wir verzeichnen dutzende Anfragen gleichzeitig, alle landen in getrennten PHP-Prozessen (soso, plötzlich können da auch 20 Prozesse gespawned werden, obwohl die Begrenzung viel niedriger lag). Die Datenbank braucht für Ömmelsabfragen plötzlich 70 Sekunden und mehr (vorher: Bruchteile einer Sekunde!).

Also ich per SSH in den Server (wie praktisch, dass ich auf dem Server ein renice-Script laufen habe, dass mir alle paar Minuten die SSH- und anderen kritischen Prozesse auf bequemere Prioritäten schiebt) und mit sehr viel Geduld im Minutenbereich die Datenbank freundlich um Beendigung gebeten. Daraufhin dann auch gleich den Webserver (der sowieso ohne Datenbank lustige Ausfälle zeigt bis hin zum Verzweiflungsselbstmord).

Puh. Intrusion Prevention auf die NAS gebügelt, irgendwer muss mal den Türsteher machen (Firewall wäre naheliegend, aber mit dem Käse läuft dann grad mal gar nix mehr so richtig – auch eine Möglichkeit Ruhe zu haben…). Die wiederum lutscht sich auch knapp 600 bis 800MB Arbeitsspeicher, wovon wir bekanntlich eigentlich schon im Zehnerpotenzbereich zu wenig haben.

Dann war Ruhe.

Nächster Tag – geht schon wieder los!

Am nächsten Morgen, wieder so gegen 11 – der nächste Angriff.

Da ich zwei Tage vorher eine Hoax-Mail mit der Bitte um Überweisung eines frechen Betrags erhalten habe mit der Ankündigung mir im Gegenzug das Geschäft dann nicht zu zerstören, habe ich langsam bisschen Muffensausen bekommen. Ich dachte „ok, das war kein Hoax, die A-Geigen meinen das so“.

Also wieder gleiches Programm. Mit Geduld und Spucke in die NAS und alles totgelegt. Auch gleich mal einen Neustart der NAS gemacht (nach wochenlanger Uptime… ich bekam feuchte Augen, echt) und die letzten Sicherheitsupdates installiert.

Aber dann hatte ich einen Idee… zwei Tage hintereinander, zwei mal praktisch dieselbe Uhrzeit… das klingt doch verdächtig nach „Cron“.

cron um 11
cron um 11

Und was sehe ich da? Da läuft ein Backupscript von mir höchstpersönlich.

Was macht das… isset Schuld?

ya-backup
ya-backup

Naja. Also…. wenn das jetzt den Server derartig überlastet, dann weiß ich auch nicht.

Dann fiel mir ein, was ich die Tage davor gemacht habe.