Hallo,
auf der Arbeit entwickele ich gerade ein System, das Produktregeln und
Verfügbarkeiten checkt. Ich stehe noch am Anfang und probiere
verschiedene Ansätze. Ziel ist es, 20000 Anfragen pro Sekunde von einem
Server durchrechnen (plus Daten holen aus Redis) zu lassen, und das
Ganze soll durch das Hinzufügen von Servern skalierbar sein. Ein Server
hat 32 Kerne und 128 GB Arbeitsspeicher.
Programmiersprache ist Java, ich habe einen ersten Versuch (mit einem
realistischen Beispielprojekt) mit dem vert.x Framework unternommen, da
komme ich auf ~8000 Anfragen pro Sekunde. Problem bei vert.x ist, dass
die einzelnen Verticles über den Eventbus kommunizieren, das bedeutet
jedesmal Serialisierung und Deserialisierung. Also kann ich die
Arbeitspakete nicht so feingranular machen, dass alle CPU Kerne benutzt
werden.
Mein nächster Ansatz ist jetzt der Disruptor von lmax. Da fällt die
Serialisierung weg, sollte also schneller sein. Nun liest man im
Internet, dass das vor allem durch die vielen Cache Hits performant sein
soll. Ich will also bei der Dimensionierung der RingBuffers prüfen, ob
ich viele Cache-Hits habe.
Es gibt dafür ja verschiedene Tools (perf, valgrind, perfmon unter
Windows, ...).
Nun meine Frage:
Wer von euch hat denn schon mal eine Anwendung bezüglich ihrer
Cache-Hits analysiert? Mit welchem Tool? Welche Cache-Hit Ratio konntet
ihr erreichen (ist natürlich von der Problemstellung abhängig, aber mich
interessieren trotzdem die Werte aus der Praxis)? Und falls jemand ein
Java-Programm analysiert hat: Gibt es da Fallstricke? Muss man den
Just-In-Time Compiler ausschalten?
Postet doch mal bitte eure Erfahrungen!
Viele Grüße! Klaus
Klaus H schrieb:> Wer von euch hat denn schon mal eine Anwendung bezüglich ihrer> Cache-Hits analysiert? Mit welchem Tool?
Die Performance-Counter der CPU können bei der Analyse helfen. Zumal es
bei Optimierung auf dieser Ebene noch andere Fallstricke gibt, als nur
das Caching-Verhalten. So ist bei 32 Kernen und damit 64 parallelen
Threads die Parallelisierung der Aufgabe von grosser Bedeutung. Aber da
gibt es ein paar Fallen, die genau dies behindern können. Die
Optimization Guides der CPU-Hersteller helfen etwas beim Verständnis,
aber nicht alle Effekte sind dort klar beschrieben.
Betreffend JIT-Compiler: Manche Tools bieten dir Rückschlüsse auf Teile
des Quelltexts, die schlechte Lokalität haben. Das funktioniert dann
angeblich nicht mehr, wenn sie die Instruktionen zur Laufzeit ändern.
Die von mir verwendeten Frameworks sehen einen Thread pro Kern des OS
(also auch virtuelle Kerne) vor, es sollen also keine Threadwechsel
stattfinden. Gegen Vert.x spricht, dass ich die Arbeit nicht so klein
stückeln kann, dass alls Kerne etwas zu tun bekommen (denn dann ist der
Serialisierungsaufwand größer als der Zeitgewinn durch
Parallelisierung).
"Perf" arbeitet ja mit den Performance Countern. Ich denke mal, ein
Server erreicht da andere Zahlen als z.B. ein Shell-Script. Deswegen
interessieren mich da eure Werte, sonst würde ich einfach ein paar Shell
Scripte analysieren.
Klaus H schrieb:> Betreffend JIT-Compiler: Manche Tools bieten dir Rückschlüsse auf Teile> des Quelltexts, die schlechte Lokalität haben. Das funktioniert dann> angeblich nicht mehr, wenn sie die Instruktionen zur Laufzeit ändern.
JIT ja m.W. heisst nicht, dass der ursprüngliche Bytecode inplace
gepatcht wird, sondern irgendwo im Heap neuer nativer Binärcode erzeugt
wird. Der kann dann logischerweise ein recht anderes Verhalten haben.
Klaus H schrieb:> Die von mir verwendeten Frameworks sehen einen Thread pro Kern des OS> (also auch virtuelle Kerne) vor, es sollen also keine Threadwechsel> stattfinden.
Konflikte zwischen Threads können nicht nur zwischen den Threads eines
Cores auftreten, sondern auch zwischen verschiedenen Cores. Nur andere,
d.h. dabei können systematische Cache-Konflikte auftreten.
Verticles/Eventbus/Disruptor/... kenne ich nicht. Entscheidend für
solche Konflikte ist die Frage, wie die Threads auf gemeinsamen Speicher
zugreifen. Da kann es schon vorkommen, dass gemeinsamer Speicher mehr
kostet als das Threading bringt und eine Optimierung über TSX kann
sinnvoll werden (wenn die CPU das unterstützt).
OK, danke für die Antworten, verstehe ich dich dann richtig, dass
Optimierungsversuche ohne Analyse der Performance Counter sinnlos sind?
Oder gibt es noch weitere Kennzahlen, auf die ich achten muss?
Zur Info: Der lmax Disruptor besteht aus einem Ringpuffer, und die
einzelnen Threads arbeiten auf unterschiedlichen Positionen des Rings.
Da die Daten an einem Index im Ring dann nacheinander von jedem Thread
mal bearbeitet werden, soll laut Theorie ein gutes Cache-Verhalten
erzielt werden.
Den Ring-Puffer kann man auf beliebige 2er-Potenzen dimensionieren. Je
nachdem, wie groß die Daten im Puffer sind, wird das gut mit dem Cache
harmonieren oder nicht (denke ich). Also im Idealfall passen die Daten
in eine Cache-Line und der Ringpuffer kann groß dimensioniert werden (8
MB L3-Cache / 32 Byte/CacheLine = 256 k Ringpuffer als Maximum). Wenn
die Daten aber relativ groß sind, dann muss der Ringpuffer verkleinert
werden.
Ich werde es wohl einfach ausprobieren müssen :-)
Klaus H schrieb:> OK, danke für die Antworten, verstehe ich dich dann richtig, dass> Optimierungsversuche ohne Analyse der Performance Counter sinnlos sind?
Es heisst, dass ich zu deiner Umgebung mit soundsoviel mir unbekannten
Werkzeugen keinen zielgerichteten Kommentar abgeben kann. Wenn das Tools
für parallele Programmierung sein sollten, dann wären spezielle Foren
dafür der einzig sinnvolle Platz. So arg viele Mikrocontroller mit 32
Threads gibt es noch nicht, und die einzigen dieser Art haben keine
Caches.
> Zur Info: Der lmax Disruptor besteht aus einem Ringpuffer, und die> einzelnen Threads arbeiten auf unterschiedlichen Positionen des Rings.> Da die Daten an einem Index im Ring dann nacheinander von jedem Thread> mal bearbeitet werden, soll laut Theorie ein gutes Cache-Verhalten> erzielt werden.
Dummerweise haben solche Datenstrukturen mitunter einen Engpass: die
Metadaten davon (Puffer-Index etc) und die entsprechende Absicherung per
Mutex/Spinlock. Bei zeitlich dichtem Zugriff darauf kann der Vorteil der
Parallelität im Cache-Pingpong verloren gehen. Genau das führte
letztlich zur Entwicklung von TSX.
Die andere Frage ist ja, ob es da eine so gute Idee ist, eine die
Hardware völlig abstrahierende Highlevelsprache wie Java zu nehmen. Wenn
man sich über Dinge Gedanken macht, die nicht nur plattformspezifisch
sind, sondern auch auf jeder CPU wieder anders laufen, dann widerspricht
das doch schon der Grundidee von Java: write once, run everywhere.
High-Performance-Hacks gehen damit nicht besonders gut, dazu ist Java
auch nicht gedacht.
Da müßte man schon C nehmen, da hat man die völlige Controlle über das
Speicherlayout. Allerdings wird bei beliebigem Multithreading auf
skalierbar drölfzig Kernen die Anwendung dann niemals das
Entwicklungsstadium verlassen.
Ich würd mir da weniger Gedanken um lowlevel-Optimierungen wie
Cache-Hits machen, die sich sowieso mit jeder anderen CPU wieder anders
benehmen, sondern um Optimierung auf Architektur-Ebene. Dafür ist Java
doch da, da man sich nicht um den Lowlevelkram kümmert. Das bedeutet im
Grundsatz schonmal das Benutzen möglichst weniger gemeinsamer
Ressourcen. Algorithmische Optimierung schlägt Lowlevel-Optimierung
ohnehin meistens.
@Nop:
Früher hätte ich dir da Recht gegeben, aber heute sehe ich das etwas
anders. Natürlich ist es Unsinn, eine Java Businessanwendung genau auf
eine Hardware zuzuschneiden und es ist durch die Sprache auch nicht
möglich. Aber der Java-Code kann bestimmte Eigenschaften haben, die ihn
auf heutiger Server-Hardware performant macht (Benutzung von möglichst
vielen Kernen, wenige Locks, wenige Threadwechsel,...).
Hier noch etwas Feedback zum Tool "Intel SDE":
Es gibt keine Schwierigkeiten, das zu Installieren. Laufen lässt man
dann das Programm mit dem Befehl
sde -omix out.mix -ofootprint out.foot -- d:\dev\jdk8\jre\bin\java -jar
d:\disruptor-1.0-SNAPSHOT.jar
Wichtig ist, den Pfad zu java absolut anzugeben, das SDE ignoriert den
PATH und findet dann evtl. ein JRE in der falschen Version.
Die Anwendung muss terminieren, damit das tool seine Statistiken
schreibt. Also beendet sich mein Projekt nach 20 Requests selbst mit
System.exit(0).
Das ganze ist sehr sehr sehr langsam, erstens wegen der Instrumentierung
und zweitens kann SDE auch Instuktionen der CPU simulieren, man kann dem
Programm damit vorgaukeln, es würde auf einer moderneren CPU laufen.
Der Footprint enthält Informationen über Caches, aber da ist das
Linux-Tool perf schon etwas gesprächiger. Ich hänge mal einen footprint
an diesen Beitrag an, falls es jemanden interessiert.
Für die Analyse von Java-Applikationen hat Intel noch eine interessante
Homepage:
https://software.intel.com/en-us/articles/java-applications-require-multi-level-analysis
Besorge Dir mal das Buch "Java Performance: The Definitive Guide" von
Scott Oaks. Es erklärt viele Fallstricke, Dinge die man eventuell besser
machen kann und alle Tuning Parameter der JVM. Es erklärt auch, wie man
die Peformance von Java Programmen mit Java, Profiler und Betriebsystem
Mitteln analysiert.
Und besorge Dir das Buch "Effective Java" von Joshua Bloch. Auch dieses
Buch erklärt einige Dinge, die sich performance verbessernd auswirken.
A. K. schrieb:> Wenn das Tools> für parallele Programmierung sein sollten, dann wären spezielle Foren> dafür der einzig sinnvolle Platz. So arg viele Mikrocontroller mit 32> Threads gibt es noch nicht, und die einzigen dieser Art haben keine> Caches.
Du bist im falschen Forum: "Programmierung auf PCs, Algorithmen,
allgemeine Programmierfragen ohne direkten Mikrocontroller-Bezug."
Markus L. schrieb:> Du bist im falschen Forum: "Programmierung auf PCs, Algorithmen,> allgemeine Programmierfragen ohne direkten Mikrocontroller-Bezug."
Ist mir klar. Dennoch gehe ich davon aus, dass solche Fragen besser dort
aufgehoben sind, wo man Werkzeuge und Programmiertechnik für hohen
Parallelisierungsgrad zum Thema hat.
Klaus H schrieb:> Aber der Java-Code kann bestimmte Eigenschaften haben, die ihn> auf heutiger Server-Hardware performant macht (Benutzung von möglichst> vielen Kernen, wenige Locks, wenige Threadwechsel,...).
Ja, das meinte ich mit meinem dritten Absatz, mit Optimierung auf
Architekturebene. Da stimme ich Dir auch zu z.B. mit der Minimierung von
Locks, weswegen ich ja anregte, möglichst wenige gemeinsame Ressourcen
zu nutzen. Diese machen ja Locks überhaupt erst nötig.
Und Architektur ist mal wieder nicht genau definiert. Für mich ist es
schon eine Optimierung auf Architekturebene, wenn ich anderen
Entwicklern sagen kann:
"Unsere Logik teilen wir in Arbeitspakete auf, die jeweils max. eine I/O
Operation machen. Diese Arbeitspakete werden dann in einer Reihenfolge
(oder bei parallelen Paketen auch Reihenfolgenagnostisch) als Consumer
an den Ringpuffer gehängt. Und es gibt nur einen Producer".
Auf jeden Fall vielen Dank für alle bisherigen Antworten, das hilft mir
schon beim Durchdenken weiter. Wenn ich die Analyse mit "perf" mal
gemacht habe gebe ich hier nochmal Feedback.
Vielleicht ist hier jemand an Feedback interessiert:
Ich habe als Ausgangsbasis Benchmarks mit JMH (openJDK) erstellt.
Zunächst sollte man den "comp" JustInTimeCompiler-Profiler aktivieren,
und die Warmup-Interationen solange erhöhen, bis nach dem Warmup kein
Code mehr kompiliert wird.
In meinem Fall gab es dann noch deutliche Schwankungen des Durchsatzes
der einzelnen Iterationen. Deshalb habe ich den Speicher für die JVM
begrenzt (von 1 GB runter auf 320 MB). Dann läuft die GarbageCollection
häufiger, aber schneller. Auf dafür bringt JMH einen Profiler mit.
Am Ende hat man dann ein Artefakt, das Vergleichsmessungen ermöglicht.
An dem Punkt stehe ich jetzt, jetzt muss ich die Kollegen vom Build
überreden, dass sie die JMH-Tests über ein Jenkins-Plugin ausführen. Und
ich werde mit dem linux-Perf die CacheHits untersuchen.
Siehe auch http://www.brendangregg.com/linuxperf.html
@Klaus
Dein optimierungsansatz ist zwar löblich, allerdings wird man in der
Praxis einfach zwei server nehmen um den Durchsatz zu erreichen. Viel
wichtiger als die Geschwindigkeit ist die Stabilität bei solchen
geschäftskritischen Anwendungen.
Überleg dir daher ob es sich überhaupt lohnt an der Stelle
weiterzumachen oder ob man eher an der globalen Architektur arbeiten
sollte um es effizienter zu bekommen..
Ist das nicht so ein Thema mit Java und Cache Misses? Da man in Java
prinzipiell alles mit Referenzen zu pflastert (gibt ja keine Möglichkeit
eigene Wertetypen zu definieren), liegen die Speicherbereiche weiter
auseinander.
Klaus H schrieb:> Programmiersprache ist Java, ich habe einen ersten Versuch (mit einem> realistischen Beispielprojekt) mit dem vert.x Framework unternommen, da> komme ich auf ~8000 Anfragen pro Sekunde. Problem bei vert.x ist, dass> die einzelnen Verticles über den Eventbus kommunizieren, das bedeutet> jedesmal Serialisierung und Deserialisierung. Also kann ich die> Arbeitspakete nicht so feingranular machen, dass alle CPU Kerne benutzt> werden.
Was sagen denn die klassischen Systemwerkzeuge {io,vm,mp}stat, top und
SAR?
L1-Cache schrieb:> Ist das nicht so ein Thema mit Java und Cache Misses? Da man in Java> prinzipiell alles mit Referenzen zu pflastert (gibt ja keine Möglichkeit> eigene Wertetypen zu definieren), liegen die Speicherbereiche weiter> auseinander.
Ist nur ein Thema, wenn die referenzierten Daten wesentlich kleiner als
eine Cache-Line sind (64 Bytes), und deshalb eine Art Fragmentierung
auftritt. Bei Cache-Lines selbst ist unerheblich, ob die nebeneinander
liegen, oder weit entfernt.
Klaus H schrieb:> auf der Arbeit entwickele ich gerade ein System, das Produktregeln und> Verfügbarkeiten checkt. Ich stehe noch am Anfang und probiere> verschiedene Ansätze. Ziel ist es, 20000 Anfragen pro Sekunde von einem> Server durchrechnen (plus Daten holen aus Redis) zu lassen
wäre es nicht auch ein Ansatz, diese Produktregeln und Verfügbarkeiten
zumindest teilweise vorzuberechnen und in einer Datenbank abzulegen?
Gerade wenn sich die Daten nur seltener ändern, dafür aber oft abgefragt
werden, ist sowas oft eine effiziente Möglichkeit die Abfragen zu
beschleunigen.
Toll, dass hier noch ein paar Beiträge kommen! Die Vorberechnung von
Produktregeln ist keine Option, die Daten müssen mindestens tagesaktuell
sein. Da sich durch Kombinatorik so viele Möglichkeiten ergeben, müßte
die Vorberechnung so schnell sein, dass das keinen Vorteil gegenüber
einer Live-Berechnung bietet.
Zum Thema Cache-Misses und Java: Durch die Referenzen liegen die Objekte
verstreut im Speicher. Für die Cache-Lines ist das, wie A.K. schon
gesagt hat, egal. Aber für die Cache-Lokalität ist es besser, wenn die
Objekte nah beieinanderliegen, weil die CPU dann schon spekulativ
angrenzende Speicherbereiche in den Cache legt. Dafür gibt es meines
Wissens 2 Optimierungsmöglichkeiten:
- Arbeiten mit binären Arrays, in denen die Daten stecken. Um die Arrays
kann man sich noch Wrapper mit Getter/Setter legen.
- Einmalige Erzeugung von Objekten in einer Schleife. Ohne Garantie kann
man darauf hoffen, dass so erzeugte Objekte nah beieinander im Speicher
liegen. Die zu verarbeitenden Daten werden dann in Objekte aus diesem
Pool kopiert.
Ist halt immer ein Kompromiss :-). Deshalb will ich erstmal messen, ob
meine Anwendung beim Caching Performance verschenkt, bevor ich etwas
optimiere.
@TestX:
ja, es werden im Moment schon 2 Server eingesetzt, und es kann weiter
skaliert werden. Aber was sind schon die Kosten eines Entwicklers gegen
Hardware und nötige Lizenzen. Vor allem, wenn die Firma noch im
Budgetdenken feststeckt und dann die nötige Hardware erst in einem Jahr
beschafft werden kann.
Klaus H. schrieb:> Die Vorberechnung von> Produktregeln ist keine Option, die Daten müssen mindestens tagesaktuell> sein. Da sich durch Kombinatorik so viele Möglichkeiten ergeben, müßte> die Vorberechnung so schnell sein, dass das keinen Vorteil gegenüber> einer Live-Berechnung bietet.
Ja, komplett vorberechnen funktioniert meist nicht.
Aber kann man nicht evtl. teilweise vorberechnen? Also z.B. Gruppen
bilden und dann deren Teilergebnisse vorberechnen und speichern. Dann
muss die Live-Abfrage nur noch die vorberechneten Gruppen abfragen und
kombinieren anstatt immer von Adam und Eva an neu zu berechnen.
> ja, es werden im Moment schon 2 Server eingesetzt, und es kann weiter> skaliert werden. Aber was sind schon die Kosten eines Entwicklers gegen> Hardware und nötige Lizenzen. Vor allem, wenn die Firma noch im> Budgetdenken feststeckt und dann die nötige Hardware erst in einem Jahr> beschafft werden kann.
Das wäre mal ne Aufgabe die Du an Deinen Chef vergeben kannst: hier die
Strukturen anpassen und auf die Höhe der Zeit bringen.
Dynamisch Rechenleistung bei Bedarf anzufordern (entweder aus dem
eigenen RZ oder zugemietet bei Dienstleistern) und zuzuschalten ist da
eigentlich Stand der Technik (und deren Organisation). Nen Jahr für die
Entscheidung für nen neuen Rechner zu brauchen war schon im letzten
Jahrzehnt nicht mehr zeitgemäß.
Klaus H schrieb:> auf der Arbeit entwickele ich gerade ein System, das Produktregeln und> Verfügbarkeiten checkt. Ich stehe noch am Anfang und probiere> verschiedene Ansätze. Ziel ist es, 20000 Anfragen pro Sekunde von einem> Server durchrechnen (plus Daten holen aus Redis) zu lassen, und das> Ganze soll durch das Hinzufügen von Servern skalierbar sein.
Vielleicht liege ich falsch, aber nach dem, was ich auf die Schnelle
über vert.x und LMAX' Disruptor gelesen habe, dienen diese beiden
Werkzeuge der Kommunikation zwischen verschiedenen Threads und
Prozessen. Leider weiß ich zu wenig über Deinen Anwendungsfall, aber
möglicherweise könnten Ansätze aus dem Distributed Computing wie zum
Beispiel Apache Spark oder Apache Storm die Forderung nach horizontaler
Skalierung besser erfüllen.
Die horizontale Skalierung läuft über Redis. Die Arbeitspakete werden in
eine Redis-Queue gePUSHt (blödes Wort), und und somit können beliebig
viele Instanzen, die nichts voneinander wissen, die Arbeitspakete
abarbeiten. Hat sich voll bewährt, Redis ist sehr schnell
(Zugriffszeiten bei Datenpakenten von 1 kB: < 1ms), nur bei der
Serialisierung/Deserialisierung muss man sich etwas Gedanken machen.
Wenn die Redis-Performance nicht mehr ausreicht, dann kann die Queue
immer noch nach Produkttyp auf mehrere Redis-Instanzen verteilt werden.
Ich weiß nicht, ob hier im Forum noch mehr Diskussion gewünscht wird,
denn im Moment habe ich keine Fragen mehr. Ich kann halt, falls
gewünscht, Erfahrungen posten:
Der LMAX Disruptor funktioniert analog zu einer CPU-Pipeline. Ich teile
mein Arbeitspaket in mehrere Teilschritte auf, und jedes Element im
Ringpuffer durchläuft dann jeden Teilschritt (voneinander unabhängige
Teilschritte können auch Out-of-Order durchlaufen werden). Wie bei einer
Pipeline bestimmt dann der längste Teilschritt den "Takt". Also muss
immer wieder der längste Teilschritt bestimmt werden, und dann entweder
optimiert oder weiter aufgeteilt werden (wobei die max. Anzahl der
Teilschritte wiederum durch die Anzahl der CPUs begrenzt wird).
Mein längster Teilschritt (mit I/O über einen lokalen TCP-Socket) liegt
im Moment bei 900 ns, die kürzesten bei ~100 ns. Mein Ziel ist es, dass
die Teilschritte < 500 ns dauern. Dazu habe ich auch ein paar Ideen...
Klaus H. schrieb:> Die horizontale Skalierung läuft über Redis.
Klar, aber das betrifft ja nur den Speicher. Meine Idee zielte eher
darauf ab, auch die CPU-Last horizontal zu skalieren.
Welchen Redis-Client benutzt Du denn in Java?
Ich benutze Jedis.
Die Arbeitspakete liegen in einer Redis-Queue, und an diese Queue kann
man dann beliebig viele Instanzen hängen, die die Arbeitspakete
abarbeiten. Ist es das, was du mit "horizontaler Skalierung" meinst?
Die Consumer am Ringpuffer laufen jeweils in einem eigenen Thread, damit
wird pro Teilschritt eine CPU verwendet. Wenn es keinen Stau im
Ringpuffer gibt, als die Teilschritte gleich schnell sind, dann ergibt
sich eine optimale CPU-Auslastung. Nehmen wir an, der Prozess braucht 8
CPUs, und der Server hat 32. Dann werden mit Hilfe von Docker 2
Container deployed, die zusammen 16 CPUs belegen. Oder, je nach Last,
können auch 4 Container deployed werden. Oder auch 8 Container auf 2
Servern.
--- schrieb:> Das einfachste wäre natürlich eine CPU mit mehr Cache zu nehmen.> SPARC M7 hat 32 Cores und 256 Threads.> Und nicht dieses lächerliche Intel-Gehampel.
Je größer der Cache, desto langsamer der Cache.
Je länger der Tag, desto mehr erzählt Larry.
Larry schrieb:>> Und nicht dieses lächerliche Intel-Gehampel.>> Je größer der Cache, desto langsamer der Cache.
Unabhängig vom Wahrheitsgehalt dieser Aussage:
SPARC M7: 16KB L1 Cache pro Core
Intel: 64KB L1 Cache pro Core
Klaus H. schrieb:> Ich benutze Jedis.> Die Arbeitspakete liegen in einer Redis-Queue, und an diese Queue kann> man dann beliebig viele Instanzen hängen, die die Arbeitspakete> abarbeiten.
Du meinst eine Redis "List", an die ein Provider mit RPUSH Elemente
anhängt, die Deine Consumer sich dann mit BLPOP abholen?
> Ist es das, was du mit "horizontaler Skalierung" meinst?
Mit "horizontaler Skalierung" meine ich eine Skalierung, die über die
Anzahl der Maschinen skaliert, im Gegensatz zu einer vertikalen
Skalierung, welche über den Austausch von vorhandenen über größere
Maschinen skaliert.
> Die Consumer am Ringpuffer laufen jeweils in einem eigenen Thread, damit> wird pro Teilschritt eine CPU verwendet.
Mir ist noch nicht ganz klar, wozu Du den Ringpuffer benötigst.
Technisch gesehen eine Redis List, aber nach dem "Reliable Queue"
Pattern von Redis:
https://redis.io/commands/rpoplpush
Der Ring-Puffer soll Objekte, die "nebeneinander" im Speicher liegen,
beinhalten. Das begint, dass nicht jedes Mal ein neues Objekt erzeugt
wird, sondern bestehende werden wiederverwendet. Da bietet sich ein
Ringpuffer an.