[Ich lenke mal auf de.comp.datenbanken.misc um - mit PHP hat das gar
nichts mehr zu tun]
On 2017-06-30 04:56, Michael Vogel <
i...@spamfence.net> wrote:
> Am 28.06.2017 um 11:30 schrieb Peter J. Holzer:
>> On 2017-06-27 16:17, Michael Vogel <
i...@spamfence.de> wrote:
>>> Mein Server liegt auf einem Server, der eindeutig im I/O-Bereich
>>> überlastet ist. Die Ursache liegt dabei nicht an einer Fehlkonfiguration
>>> des Servers, sondern an den Prozessen. Es laufen dort vier soziale
>>> Netzwerke parallel.
>>
>> Einen Server, die "eindeutig im I/O-Bereich überlastet" ist, würde ich
>> als Systemadministrator schon als "Fehlkonfiguration" bezeichnen. Kann
>> natürlich sein, dass man wenig dagegen tun kann, weil das Budget knapp
>> ist. Aber Arbeitszeit ist auch nicht gratis - auch nicht bei einem
>> Freizeitprojekt.
>
> Ich bin nur "Freizeitadmin", aber nach allem, was ich zum Server sagen
> kann, ist er nicht fehlkonfiguriert, sondern einfach nur überlastet.
Wenn Du (z.B.) 4 Services, die jeweils 100 Requests pro Sekunde
bearbeiten sollen, auf einem Host installierst, der nur 300 Requests
pro Sekunde schafft, dann ist das für mich eine Fehlkonfiguration.
Konfigurationsänderungen, die das Problem beheben könnten, wären z.B.:
* Verlagern mindestens eines der Services auf einen anderen Host
* Ausbau des vorhandenen Hosts (zusätzliche und/oder schnellere Disks,
mehr RAM, ...), damit er mehr Requests pro Sekunde abarbeiten kann.
* Reorganisation der Disks: Anderes RAID-Level, bestimmte "heiße"
Tabellen besser verteilen, ...
Die Konfiguration eines Systems besteht nicht nur aus Config-Files.
> Ich könnte mir natürlich einen (noch) größeren Server besorgen.
> Allerdings läuft die Software an der ich arbeite ja nicht nur auf
> meinem Rechner, sondern noch auf hunderten anderer Systeme. Dadurch,
> dass mein System relativ langsam ist, kann ich viel besser erkennen,
> welche Programmierungen welche Auswirkungen in Bezug auf Performance
> haben. Das sehe ich als Vorteil.
Ob Deine User das auch so sehen? Aber egal. Das ist Deine Entscheidung.
>>>>> Wie gut ist "Berkeley DB" dafür geeignet? Siehe
>>>>>
http://php.net/manual/de/intro.dba.php
>>>>
>>>> BDB war einmal eine der MySQL-Engines. Wurde zugunsten von InnoDB
>>>> entfernt (hatte WIMRE auch Lizenzgründe). Seitdem wurde es offenbar
>>>> recht kräftig weiterentwickelt, ich kann also zum aktuellen Status
>>>> nichts sagen. Bauchgefühl sagt: Wird wohl nicht viel anders als InnoDB
>>>> sein.
>>>
>>> Sperren gehen auch?
>>
>> Eigentlich willst Du da keine Sperren, Die sind eher eine unerwünschte
>> Nebenwirkung.
>
> Wenn ein Worker gerade die Queue abarbeitet, während der Scheduler die
> Queue auffüllt, muss das ja schon irgendwie synchronisiert werden, damit
> kein Chaos entsteht.
Richtig. Du willst, dass das "irgendwie synchronisiert" wird. Locks sind
eine Methode zur Synchronisation. Du schließt daraus, dass Du Locks
willst. Das ist aber ein Fehlschluss. Locks sind nicht das Ziel, sondern
ein Weg zum Ziel. Wenn Du das Ziel auf anderem Weg erreichen kannst, ist
das genauso gut und vielleicht sogar besser.
Konkret hast Du das Problem, dass Deine Locks entweder zu lange dauern
oder zu viel locken. Prozesse warten daher darauf, dass ein anderer
Prozess endlich einen Lock freigibt, statt sinnvolle Arbeit zu
verrichten. So habe ich Deine Beschreibung zumindest verstanden (wobei
das ein bisschen im Widerspruch zur Beobachtung steht, dass das System
jetzt bereits überlastet ist - wenn die Prozesse weniger warten würden,
wäre das System ja noch stärker belastet).
Du willst also Locks möglichst minimieren. Idealerweise willst Du sie
ganz loswerden, das wirst Du allerdings mit einem RDBMS eher nicht
schaffen, weil jedes RDBMS intern Locks verwendet - allerdings
(hoffentlich) sehr viel feinere, als Du das als Applikations-
programmierer kannst.
Gehen wir mal ein paar Möglichkeiten durch:
1. Die Worst-Practice Methode:
Du hast eine Queue-Tabelle. Wenn ein Worker einen Job abarbeiten
will, sperrt er die ganze Tabelle, schaut nach, ob er einen Job
findet, wenn ja, arbeitet er ihn ab und löscht ihn aus der Tabelle.
Ganz zum Schluss gibt er die Tabelle wieder frei.
Das ist offensichtlich kompletter Unsinn: Es kann immer nur ein
Worker gleichzeitig arbeiten und man kann nicht einmal neue Jobs
einwerfen, solange ein Job arbeitet.
Das muss besser gehen.
2. select for update
Der obige Ansatz sperrt die Tabelle länger als notwendig (während
der Job abgearbeitet wird, muss sie nicht gesperrt sein) und er
sperrt mehr als notwendig (die ganze Tabelle, es müsste aber nur
eine Zeile gesperrt werden. Das lässt sich leicht verbessern:
Job holen:
select id, ...
from job_queue
where status = 'new'
limit 1
for update;
update job_queue set status = 'in progress' where id=...;
commit;
Job löschen:
delete from job_queue where id=...;
commit;
Damit ist der Lock sehr viel kürzer (er existiert nur mehr für die
Zeit zwischen select und update, und sehr viel feiner granuliert
(nur mehr eine Zeile).
Aber es gibt da immer noch ein Problem: Wenn zwei Worker
gleichzeitig einen neuen Job suchen, werden sie wahrscheinlich den
gleichen finden, und der zweite wird auf den ersten warten, nur um
festzustellen, dass die Zeile, die er gerade locken wollte, die
where-Klausel nicht mehr erfüllt.
3. select for update skip locked
PostgreSQL löst das Problem mit der Option skip locked. Der zweite
Worker wird in dem Fall einfach die bereits vom ersten gelockte
Zeile auslassen und die nächste Zeile, die die where-Klausel
erfüllt, sperren. Aus Sicht des Anwendungsprogrammiers ist das
Lock-Frei: Er wartet nie, sondern bekommt immer "sofort" eine
Antwort: Entweder einen Job oder eben keinen, wenn nichts zu tun
ist.
4. Andere Ansätze:
Das Hauptproblem bei Ansatz 2 ist, dass es zu deterministisch ist:
Alle Worker versuchen sich auf den gleichen Job zu stürzen, auch
wenn mehrere in der Queue sind. Das könnte man z.B. durch Zuordnung
zu verschiedenen Worker-Klassen (eventuell sogar in getrennten
Queue-Tabellen!), zufällige Sortierung, o.ä. umgehen.
Oder man könnte einen optimistischen Update machen:
update job_queue
set status='in progress', worker_id=...
where status = 'new'
limit 1;
commit;
select * from job_queue where worker_id=...;
Keine Garantie, dass das besser ist als select for update, aber es
ist einen Versuch wert.
>> PostgreSQL kennt "select for update ... skip locked", das für Job-Queues
>> und ähnliche Anwendungen ideal sein sollte. Schon vorher wurden damit
>> recht ansehnliche Durchsatzzahlen erreicht:
>>
https://gist.github.com/chanks/7585810
>
> Es ist auf alle Fälle bei uns auf dem Plan, dass wir auch Postgres
> unterstützen, aber soweit sind wir noch nicht - und vor allem können wir
> es unseren Usern nicht vorschreiben.
Nein, aber "auch PostgreSQL unterstützen" heißt ja nicht, den Usern
vorzuschreiben, dass sie das nützen müssen. Und selbst wenn in der Doku
steht "für Sites mit vielen Benutzern empfehlen wir PostgreSQL", ist das
immer noch nur ein Rat und kein Zwang. Wenn Du ein komplett anderes
Datenbanksystem für die Queues verwendest (BDB oder Redis oder was auch
immer), dann ist das eher ein Zwang, denn das müssen die Leute dann
wahrscheinlich installieren, außer Du willst mehrere Varianten
unterstützen.
>>> Mir het jemand jetzt auch SHM empfohlen. ggf. kann ich auch was mit
>>> Semaphoren machen, das bin ich gerade am Planen.
>>
>> Hattest Du nicht geschrieben, dass Lösungen, die nur im RAM arbeiten,
>> für Dich nicht in Frage kommen?
>
> Semaphoren und SHM dienen ja nur der Kommunikation und Koordination der
> Prozesse.
Probieren kann man viel, aber ich sehe keinen Grund zur Annahme, dass
es besser ist, wenn Du in der Applikation noch einen zweiten
Synchronisationslayer um den bereits in der Datenbank existierenden
herumbaust, wenn Du dann die Daten doch in der Datenbank speicherst.
Sieht für mich nur nach zusätzlichem Overhead aus - inklusive der Gefahr
heftige Bugs einzubauen - das ist nämlich nicht trivial.
hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | |
h...@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ |
http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel