← Alle Beiträge

Was ein voller Feed wirklich kostet

Ich habe eine volle Tabelle in das Produktionsschema gemessen statt sie zu schätzen, und die Messung hat einen meiner eigenen Defaults erledigt

Michel Wijnberg

“Verkraftet es eine Full Table?” ist die Frage, die jedem BGP-Produkt gestellt wird, und fast keines antwortet mit einer Zahl. Der ehrliche Grund: Die Antwort ist unangenehm zu erzeugen, denn man muss eine echte Tabelle in sein echtes Schema laden und nachsehen.

Genau das ist dieser Beitrag. Eine volle IPv4-Tabelle, 1.071.538 Best Paths von 78.538 Origin-ASen, geladen in das Produktionsschema auf derselben PostgreSQL-Hauptversion, die im Labor läuft. Jede Zahl unten ist eine Messung davon, kein Modell davon.

Er enthält außerdem das Nützlichste, was Messen leistet, nämlich Ihnen zu widersprechen. Eine der Zahlen unten war in Osprey bereits als Default ausgeliefert, und die Messung zeigte sie unterhalb des normalen Verkehrs. Sie wurde inzwischen um den Faktor vier angehoben.


Die Einheit sind Sessions, nicht Router

Der mit Abstand häufigste Dimensionierungsfehler, und er gehört zuerst aus dem Weg geräumt.

Ein Router mit vier Full-Table-Upstreams meldet vier Peers über eine BMP-Verbindung. Er kostet das Vierfache eines Routers mit einem Upstream. Nach Gerätezahl zu dimensionieren zählt genau um Ihren Multihoming-Faktor zu niedrig, also um genau das, wofür Sie das Netz gebaut haben.


Was eine volle Tabelle pro Einheit kostet

Die Größen sind PostgreSQLs eigene gemeldete Relationsgrößen, die trotz der Kürzel MB und GB auf 1024 basieren, und die Pro-Zeile-Werte sind daraus abgeleitet. 412 “MB” sind also 412 MiB: 1.071.538 Zeilen mal 403 Byte ergeben 411,8 MiB, nicht die 432 MB, die eine dezimale Lesart nahelegen würde.

EinheitKostenGeltungsbereich
bgp_rib_entry inkl. aller 9 Indexe412 MB (403 B/Zeile)pro überwachter Peer-Session
bgp_best_path inkl. 4 Indexe327 MB (319 B/Zeile)pro AS und Routing-Domain
bgp_rib_entry_history inkl. 3 Indexe376 B pro Intervallpro aufgezeichneter Transition
BMP-Präfixmenge pro Peer, Go-Heap53 MiB (52 B/Präfix)pro überwachter Peer-Session
Engine-RIB-Debounce-Puffer beim Default-Cap267 MiB (560 B/Eintrag)pro Engine, unabhängig von den Feeds

Zwei Dinge aus dieser Tabelle lohnen das Hervorheben.

Indexe sind etwas mehr als die Hälfte des Fußabdrucks. Auf bgp_rib_entry, pro Session: Primärschlüssel 82 MB, peer_prefix 42 MB, prefix_gist 32 MB, prefix 23 MB, origin_as 8,9 MB, staleness 7,5 MB, next_hop 7,3 MB. Das sind die sieben, die zu nennen sich lohnt; die beiden übrigen sind kleiner und ändern nichts am Punkt. Die sieben allein kommen auf 203 MB von insgesamt 412 MB, und der Primärschlüssel allein ist ein Fünftel von allem. Falls Sie sich je gefragt haben, ob ein weiterer Index auf einer Tabelle dieser Größe umsonst ist: Der billigste hier kostet immer noch 7,3 MB pro Session.

Beim Messen fiel auch ein Defekt auf, den niemand bemerkt hatte. idx_bgp_rib_peer_lastseen erwies sich als byte-identisch mit dem staleness-Index: 7,5 MB und ein zusätzlicher Btree-Schreibvorgang auf jeder RIB-Zeile, für nichts. Er wurde entfernt. So etwas ist unsichtbar, bis man die Größen nebeneinander legt.

403 Byte pro Zeile ist mit Communities. Ein realistischer String mit sechs Communities ist enthalten; Routen ohne Communities kosten etwa 55 Byte weniger. Nennen Sie die günstigere Zahl, ohne zu sagen welche es ist, und Ihr Dimensionierungsleitfaden ist 14 % zu optimistisch, bevor überhaupt jemand etwas ausgerollt hat.


Durchgerechnete Deployments

DeploymentLive-DatenbankBMP-Heap
1 Border-Router, 2 Upstreams1,15 GB107 MiB
2 Border-Router, je 2 Upstreams1,98 GB213 MiB
4 Router × 2 Upstreams3,6 GB426 MiB
Route-Reflector-Feed, 20 Full-Table-Sessions8,6 GB1,04 GiB

Das sind Live-Zustandszahlen. Historie ist davon getrennt, und bei der Historie wird es interessant.


Churn lässt sich nicht aus einem Snapshot ablesen

Eine Tabelle sagt Ihnen, was sie zu halten kostet. Über die Kosten des Aufzeichnens sagt sie nichts, denn die hängen davon ab, wie oft sich die Tabelle ändert, und ein Snapshot hat keine Zeitachse.

Churn wurde daher aus öffentlichen MRT-Archiven gemessen: RIPE-RIS-Collector rrc00, 2026-08-10, vier über den Tag verteilte Ein-Stunden-Fenster, 6,8 Millionen UPDATE-Nachrichten über 98 verschiedene Peer-Sessions. Ospreys 30-Sekunden-Debounce wird auf die Stichprobe angewandt. Attribut-Hash-Unterdrückung ist bewusst nicht modelliert, also sind das Obergrenzen dessen, was tatsächlich aufgezeichnet würde.

Ein Intervall ist hier eine persistierte Zustandsänderung nach dem Debounce. Es ist kein rohes BGP-UPDATE, das viele Präfixe auf einmal tragen kann, und kein Präfix-Event, das der Debounce in ein bestehendes Intervall einklappen kann.

Pro SessionAufgezeichnete Intervalle pro TagHistorie-Speicher
Median413.000155 MB/Tag → 14 GB pro 90 Tage
p90840.000316 MB/Tag → 28 GB pro 90 Tage
Schwerste beobachtete8.300.0003,1 GB/Tag → 281 GB pro 90 Tage

Der Debounce leistet echte Arbeit: Er faltet jedes 30-Sekunden-Fenster auf einen Datensatz pro (Session, Präfix), was 45,6 % der rohen Präfix-Events entfernt. Ihn auf einem Full-Table-Ziel abzuschalten multipliziert die Historie-Rechnung um etwa 1,8, und was Sie dafür bekommen, ist feinere Sicht auf Transitionen innerhalb des Fensters. Auf einem DFZ-Feed ist das fast ausschließlich die Art von Detail, die Sie hofften nicht Zeile für Zeile speichern zu müssen.

Zwei Einschränkungen, die ich als Leser haben wollte. rrc00 ist RIPEs Multihop-Collector und seine Peers sind lauter als eine typische IXP-lokale Transit-Session, behandeln Sie diese Zahlen also als pessimistisch. Und die Streuung zählt weit mehr als der Median: Die schwerste Session ist das 20-Fache des Medians. Dimensionieren Sie auf p90, alarmieren Sie auf dem Rand, und lassen Sie sich von niemandem den Median als Kapazitäts- plan verkaufen.


Die Zahl, die falsch war

Osprey hat einen Schutzschalter auf der Historienaufzeichnung. Erzeugt ein Ziel mehr als storm_ceiling_per_hour Intervalle, wird die Aufzeichnung für den Rest dieser Stunde unterdrückt, damit eine flappende Session nicht unbeaufsichtigt eine Platte füllt.

Der ausgelieferte Default war 250.000 pro Stunde.

Und nun daneben die Messung. Die schwerste einzelne echte Session in der rrc00-Stichprobe läuft mit etwa 346.000 Intervallen pro Stunde, also den 8,3 Millionen pro Tag aus der Tabelle oben. Kein Amoklauf. Keine Flap-Schleife. Ein echter Peer, auf einem echten Collector, an einem gewöhnlichen Montag.

Der Default wurde vom Normalbetrieb ausgelöst, von ganz allein. Und der Fehlermodus eines Unterdrückungsschalters ist der leise: Nichts stürzt ab, nichts alarmiert laut, die Historie hört einfach auf vollständig zu sein, und die Lücke ist nur sichtbar, wenn man danach sucht.

Der Default liegt jetzt bei 1.000.000 pro Stunde, abgeleitet aus derselben Messung statt aus Intuition. p90-Churn liegt bei ungefähr 35.000 Intervallen pro Stunde und Session, also läuft das größte durchgerechnete Profil oben, ein Route-Reflector-Feed mit 20 Full-Table-Sessions, bei p90 um die 700.000 pro Stunde. Die neue Obergrenze liegt mit Luft darüber und über der schwersten einzelnen echten Session, und dabei immer noch weit unter dem, was ein echter Amoklauf produziert: Eine Session, die ihre Tabelle in einer Flap-Schleife wiederholt, erzeugt ein Vielfaches davon, der Schalter schaltet also weiterhin ab.

Etwas, das dieselbe Messung klarmacht und das ich lieber ausspreche als impliziere: Ein Ziel, das viele laute Sessions aggregiert, kann die Obergrenze legitim auslösen. Die 98 rrc00-Sessions zusammen erzeugen rund 50 Millionen Intervalle pro Tag, etwa 2 Millionen pro Stunde, deutlich über der neuen Grenze. Wenn Sie Osprey auf so etwas richten, wird der Schalter auslösen, und das zu Recht.

Das gehört ausdrücklich gesagt, denn die beiden Zahlen sehen aus, als widersprächen sie sich: Die Obergrenze ist ein Amoklaufschutz, kein Kapazitätsplan. Sie ist pro Ziel gesetzt, über dem, was Normalbetrieb auf dem größten von uns dimensionierten Profil erzeugt, und unter dem, was eine Session produziert, die ihre Tabelle in einer Schleife wiederholt. Ein aggregierendes Ziel, das sie legitim überschreitet, ist einem Ziel entwachsen, nicht dem Produkt.

Deshalb wird das Auslösen aufgezeichnet und nicht bloß geloggt. Wenn die Obergrenze greift, ist die Historie für den Rest dieser Stunde unvollständig, und diese Lücke wird in ein abfragbares Blind-Spot-Register geschrieben. Ein Monitoring-System, das stillschweigend aufhört zu überwachen, ist schlimmer als eines, das laut aufhört, und der einzige Unterschied ist, ob die Lücke ein erstklassiger Datensatz ist.


Was das nicht sagt

Alles oben sind Bytes: was eine volle Tabelle in PostgreSQL belegt, was der BMP-Server im Go-Heap hält, und wie schnell Historie anwächst. Das ist eine Achse, und eine Dimensionierungsfrage hat mehrere.

Hier nicht gemessen und daher nicht behauptet:

  • CPU, beim Ingest oder bei der Abfrage.
  • Zeit bis zur ersten brauchbaren Antwort, nachdem eine Session hochkommt, also die Zahl, die eine Betreiberin tatsächlich spürt.
  • Der Nachrichtenweg zwischen BMP-Server und Datenbank: was Bus und Live-Websocket-Zustand kosten, während eine Tabelle konvergiert.
  • Query-Latenz gegen eine Tabelle dieser Größe, im Unterschied zu ihrem Fußabdruck.

Das sind eigene Messungen, und ich habe sie nicht gemacht. Ein Dimensionierungsleitfaden, der stillschweigend etwas anderes andeutet, indem er Ihnen eine selbstsichere Speicherzahl gibt und Sie annehmen lässt, der Rest skaliere genauso, tut genau das, wogegen dieser Beitrag argumentiert.


Warum man einen Default veröffentlicht, den man falsch hatte

Weil die Alternative auf eine bestimmte Weise schlechter ist.

Ein aus Schätzungen gebauter Dimensionierungsleitfaden ist unfalsifizierbar. Niemand kann ihn prüfen, also findet niemand den Fehler, also geht der Fehler in den Versand. Dass dieser gefunden wurde, liegt daran, dass die Zahlen durch Laden einer echten Tabelle und Wiedergabe echter Updates entstanden sind, und einen echten Update-Strom kümmert nicht, was die Konfigurationsdatei für vernünftig hält.

Die Messung kostete etwa einen Tag. Sie fand einen doppelten Index, einen um den Faktor vier danebenliegenden Default, und sie erzeugte die erste ehrliche Antwort dieses Produkts auf “verkraftet es eine Full Table”. Nämlich: ja, bei 412 MB und 53 MiB Heap pro Session für den Live-Zustand, und irgendwo zwischen 14 GB und 281 GB pro 90 Tage für Historie, ganz abhängig davon, wie laut Ihre Peers sind. Diese Spanne ist die echte Antwort, nicht ihr Mittelwert.


Der Punkt

Jede Zahl in diesem Beitrag ist eine Messung des ausgelieferten Schemas, und eine davon hat dem ausgelieferten Code widersprochen.

Das ist das Argument dafür, das eigene Produkt zu messen statt über es nachzudenken. Nicht weil Nachdenken nachlässig wäre, sondern weil ein durch Nachdenken gewählter Default keine Möglichkeit hat, Ihnen zu sagen, dass er falsch war. Eine echte Tabelle tut es an einem Tag.


Das ist der letzte Beitrag dieser Reihe. Der nächste beginnt darunter: wie ein Recorder dem Protokoll eines anderen beitritt, ohne Teil davon zu werden, eine Vordertür nach der anderen.