Last steuern statt Systeme verbiegen: Rate Limiting mit Apache Pulsar

Last steuern statt Systeme verbiegen: Rate Limiting mit Apache Pulsar
By Matthias Petermann / on 23.12.2025

Warum wird mein ERP plötzlich zum Flaschenhals?

In Integrationsprojekten mit Apache Pulsar taucht früher oder später eine unbequeme Frage auf:

Warum reicht ein einzelnes externes System aus, um ein ansonsten stabiles ERP in die Knie zu zwingen?

Das Grundszenario ist meist simpel. Ein ERP-System konsumiert ein Topic, über das operative Transaktionen ausgelöst werden. Im Normalbetrieb stammen diese Nachrichten aus internen Workflows – kontrolliert, vorhersehbar, gut dimensioniert.

Probleme entstehen erst dann, wenn externe Systeme dasselbe Topic nutzen, etwa für:

  • Korrekturbuchungen
  • Massenimporte
  • nachträgliche Datenbereinigungen

Das ERP selbst besitzt oft kein ausgeprägtes Rückstau- oder Priorisierungskonzept. Die Folge: Sobald externe Last ungebremst eintrifft, wird das System überfordert.

🚨 Typisches Symptom
Das ERP ist nicht “kaputt”, sondern schlicht nicht dafür gebaut, unkontrollierte Last aus fremden Systemen zu absorbieren.

Warum ein ERP-Ticket das eigentliche Problem verfehlt

Der klassische Lösungsweg ist schnell skizziert:

  • Ticket beim ERP-Hersteller eröffnen
  • Lastszenario beschreiben
  • auf Verbesserung hoffen

Selbst wenn das erfolgreich wäre, bleiben strukturelle Fragen unbeantwortet:

  • Dürfen externe Systeme ungefiltert in interne Prozessketten eingreifen?
  • Können wir interne und externe Nutzung überhaupt unterscheiden?
  • Haben wir Steuerungs- und Beobachtungsmöglichkeiten?
⚠️ Architektur-Falle
Mehr Robustheit im ERP verbessert Symptome, ändert aber nichts an fehlender Governance auf der Integrationsschicht.

Perspektivwechsel: Integration als Infrastrukturthema

Apache Pulsar ist mehr als ein Transportmechanismus. Durch Namespaces, ACLs und Pulsar Functions lässt sich Pulsar als aktive Datendrehscheibe einsetzen.

Die zentrale Idee dieses Artikels lautet:

Wenn Last problematisch ist, muss sie vor dem ERP kontrolliert werden – nicht im ERP.


Testaufbau: Das Problem reproduzierbar machen

Dieser Abschnitt beschreibt den vollständigen Testaufbau, so dass das Szenario 1:1 reproduzierbar ist. Es werden bewusst keine Annahmen über vorhandene Infrastruktur gemacht.

ℹ️ Ziel des Testaufbaus
Der Testaufbau dient nicht der Performance-Messung, sondern der qualitativen Beobachtung von Lastverhalten, Fairness und Steuerbarkeit der Datenströme.

1. Pulsar lokal starten

Für die Erprobung wird ein einzelner lokaler Pulsar-Broker im Standalone-Modus verwendet. Damit sind Broker, BookKeeper und ZooKeeper in einem Prozess gebündelt.

podman run -it --rm \
  -p 6650:6650 \
  -p 8080:8080 \
  docker.io/apachepulsar/pulsar:4.0.8 \
  bin/pulsar standalone
  • Port 6650: Pulsar Binary Protocol (Clients)
  • Port 8080: Admin-API, Functions Worker
⚠️ Standalone-Modus
Der Standalone-Modus ist nicht hochverfügbar und bildet keinen Cluster ab. Für das hier untersuchte Verhalten ist das jedoch ausreichend.

2. ERP-Namespace anlegen

Nach dem Start wechseln wir in die Pulsar-Shell:

pulsar-shell

Zunächst wird der Namespace für das ERP angelegt:

admin namespaces create public/erp

3. Definition des Topics

Im Test wird ein Topic verwendet:

  • ERP-Eingangstopic (intern)
    persistent://public/erp/command

Topics müssen in Pulsar nicht explizit angelegt werden – sie entstehen implizit beim ersten Zugriff.


4. Simulation des ERP-Consumers

Das ERP wird durch einen einfachen Pulsar-CLI-Consumer simuliert, der alle empfangenen Nachrichten auf STDOUT ausgibt.

export PULSAR_URL=pulsar://127.0.0.1:6650

pulsar-cli consumer \
  -t persistent://public/erp/command \
  -s erp
  • Subscription-Name: erp
  • Subscription-Typ: default (Shared)

Jede empfangene Nachricht wird sofort ausgegeben.

ℹ️ Warum CLI statt Applikation?
Die Pulsar-CLI verhält sich exakt wie ein normaler Client, verzichtet aber auf jede zusätzliche Logik. Damit wird das Broker-Verhalten unverfälscht sichtbar.

5. Interner Producer (Normalbetrieb)

Der interne Producer simuliert reguläre ERP-Workflows mit geringer, konstanter Last.

seq 1 10000000 | while read i; do
  echo "erp-client-message-$i" \
    | pulsar-cli producer \
        -t persistent://public/erp/command
  sleep 5
done

Eigenschaften:

  • eine Nachricht alle 5 Sekunden
  • konstante Last
  • keine Burst-Phasen

Der Consumer empfängt entsprechend:

erp-client-message-1
erp-client-message-2
erp-client-message-3

6. Externer Producer (Störfall)

Der externe Producer simuliert ein Fremdsystem, das ungebremst Nachrichten erzeugt.

seq 1 10000000 | while read i; do
  echo "external-client-message-$i" \
    | pulsar-cli producer \
        -t persistent://public/erp/command
done

Eigenschaften:

  • keine Pausen
  • maximale Sendegeschwindigkeit
  • keinerlei Rücksicht auf den Consumer

Das Ergebnis am ERP-Consumer:

external-client-message-433
erp-client-message-9
external-client-message-434
external-client-message-435
external-client-message-436
external-client-message-437

Interne Nachrichten gehen im Strom externer Last nahezu unter.

💡 Beobachtung
Pulsar garantiert Reihenfolge pro Producer, aber keine Fairness zwischen Producern.

Dispatch Rate als Kontrollversuch

Zum Vergleich wird ein Dispatch-Limit direkt auf dem ERP-Topic gesetzt:

admin topics set-dispatch-rate \
  persistent://public/erp/command \
  --dispatch-rate-period=1 \
  --msg-dispatch-rate=1

Der Consumer erhält nun nur noch eine Nachricht pro Sekunde – unabhängig von der Quelle.

❌ Ergebnis
Das Limit wirkt technisch korrekt, trifft aber interne und externe Last gleichermaßen. Die Priorisierung ist vollständig verloren. Dieser Ansatz wird daher als ungeeignet bewertet.

Anschließend wird das Limit wieder entfernt:

admin topics set-dispatch-rate \
  persistent://public/erp/command \
  --dispatch-rate-period=1 \
  --msg-dispatch-rate=-1

Architekturentscheidung: Trennung der Eingangskanäle

Grundidee: Externe Producer werden durch Pulsar kontrolliert, nicht durch das ERP

Externe Systeme publizieren ihre Nachrichten in ein eigenes Pulsar-Topic (persistent://public/external/command) und nicht direkt in das ERP-Topic. Dieses Topic wird ausschließlich von einer Pulsar Function konsumiert, die als einziger autorisierter Producer in das interne ERP-Topic schreibt. Rate Limiting wird auf dem Dispatch der Function angewendet und begrenzt damit exakt die Nachrichtenrate, mit der externe Events das ERP erreichen.


Ein dedizierter Namespace für das externe System

Wir führen einen zweiten Namespace ein:

admin namespaces create public/external

Damit entstehen zwei explizite Kanäle:

Herkunft Topic
Intern persistent://public/erp/command
Extern persistent://public/external/command
🤔 Governance durch Struktur
Trennung durch Namespaces macht Nutzung sichtbar, steuerbar und erzwingbar – allein durch ACLs.
ℹ️ Warum identische Topic-Namen?
Beide Topics heißen bewusst command. Die Unterscheidung erfolgt ausschließlich über den Namespace. So bleibt die semantische Bedeutung identisch, während die Governance-Struktur explizit wird.

Routing mit einer Pulsar Function

Externe Nachrichten sollen weiterhin ins ERP gelangen – aber kontrolliert. Dazu nutzen wir eine Pass-Through-Pulsar-Function, die Nachrichten unverändert weiterleitet.

public final class Copy implements Function<byte[], byte[]> {
    @Override
    public byte[] process(byte[] input, Context context) {
        if (input == null || input.length == 0) {
            return null;
        }
        return input;
    }
}

Deployment:

admin functions create \
  --tenant public \
  --namespace external \
  --name route.copy \
  --jar /abs/path/route-copy.jar \
  --classname net.d2ux.stream.generic.route.Copy \
  --inputs persistent://public/external/command \
  --output persistent://public/erp/command
ℹ️ Wichtiger Effekt
Die Function ist ein Consumer des externen Topics. Dispatch Rate wirkt damit vor dem ERP.
ℹ️ Quellcode der Pulsar Functions

Der vollständige Quellcode der in diesem Artikel verwendeten und weiterer beispielhafter Pulsar Functions ist befinden sich in folgendem Repository:

https://forge.ext.d2ux.net/OpenLab/pulsar-stream-functions

Die Beispiele sind minimal gehalten und zeigen typische Einsatzmuster wie Routing, Filtering und kontrollierte Weiterleitung von Nachrichten.


Rate Limiting dort, wo es sinnvoll ist

Jetzt setzen wir das Limit ausschließlich auf dem externen Topic:

admin topics set-dispatch-rate \
  persistent://public/external/command \
  --dispatch-rate-period=5 \
  --msg-dispatch-rate=1

Ergebnis:

  • interne Nachrichten laufen ungebremst
  • externe Last wird dosiert
  • das ERP bleibt stabil
external-client-message-433
erp-client-message-9
external-client-message-434
erp-client-message-10
external-client-message-435
erp-client-message-11
external-client-message-436
erp-client-message-12
external-client-message-437
✅ Architektonischer Gewinn
Kein Code im ERP geändert. Keine Herstellerabhängigkeit. Volle Kontrolle auf Infrastrukturebene.

Zusätzlicher Nutzen: Beobachtbarkeit

Pulsar Functions liefern Metriken:

pulsar_function_received_total{tenant="public",namespace="external",name="route.copy"} 2919
ℹ️ Sichtbarkeit
Wir sehen exakt, wie viel externe Last tatsächlich ankommt – und wann.

Fazit

Der gezeigte Ansatz verdeutlicht, welchen Mehrwert ein architektonischer Perspektivwechsel bieten kann. Statt Überlastsituationen ausschließlich auf Applikationsebene zu adressieren, wird die Integrationsschicht selbst zum Ort der Steuerung.

Apache Pulsar ermöglicht es, Datenströme, Lastverhalten und Zugriffswege explizit zu strukturieren.
Durch die Trennung interner und externer Kanäle sowie gezieltes Rate Limiting lassen sich externe Einflüsse kontrollieren, ohne bestehende Kernsysteme zu verändern.

Der Fokus dieses Artikels lag bewusst auf dem Last- und Laufzeitverhalten der beschriebenen Architektur. In produktiven Umgebungen müssen die dargestellten Mechanismen jedoch durch geeignete Sicherheitsmaßnahmen ergänzt werden. Insbesondere gehören dazu klar definierte ACLs auf Namespace-Ebene, um verbindlich festzulegen, welche Systeme Topics publizieren oder konsumieren dürfen.

Erst das Zusammenspiel aus Struktur, Governance und Security macht aus einer technischen Lösung eine tragfähige Integrationsarchitektur.

🤔 Merksatz
Nicht jede Überlast ist ein Applikationsproblem.
Oft ist sie ein Hinweis auf fehlende Steuerung in der Architektur.

Bildnachweis: Das Pulsar-Logo ist eine eingetragene Marke der Apache Software Foundation. Verwendung im Rahmen redaktioneller Berichterstattung gemäß den offiziellen Richtlinien.