Commit Graph
1 Commits
Author SHA1 Message Date
vulncheck e7ad196d0e perf(urs): score the fleet in one pass instead of once per asset
Feldmeldung: Dashboard und Compliance-Seite brauchen seit dem Einspielen
der Mittwochs-Commits acht Sekunden statt drei. Die Logs zeigen, woran es
liegt, und es sind nicht die Mittwochs-Commits: von allen Calls beider
Seiten ist genau einer langsam, /api/v1/compliance/urs braucht 4,9 bis 6,0
Sekunden, alles andere zusammen unter 300 ms. Beide Seiten haengen ihr
gesamtes Promise.all daran, also wartet die ganze Seite mit.

list_urs hat pro Asset gescort: eine Asset-Abfrage, eine Vulnerability-
Abfrage, eine Result-Abfrage, dann pro Policy ein Lazy-Load der Checks
plus eine Impact-Abfrage. Nachgemessen mit einer 120-Host-Flotte, drei
Policies, 120 Checks: 961 Statements fuer einen Aufruf. Auf SQLite im
selben Prozess sind das 600 ms und faellt niemandem auf; gegen ein
Postgres ueber einen Socket sind es bei 3 ms Round-Trip 5,3 Sekunden —
exakt der gemessene Wert.

Deshalb war es vorher schnell und ist es jetzt nicht: der teure Zweig
zaehlt erst, seit SCA-Daten und Impact-Bewertungen geladen sind. Ohne
gecachte Checks kehrt _weighted_score_for_policy sofort zum gespeicherten
Score zurueck und die zwei Abfragen pro Policy entfallen. Wer URS und
Compliance anbindet, schaltet den Pfad scharf.

compute_urs_bulk() macht denselben Score in fuenf Abfragen, unabhaengig
von der Flottengroesse: Assets, offene Vulnerabilities als Spaltenabfrage,
Results, deren Checks, die Impact-Tabelle. Der Lesepfad schreibt dabei
nicht mehr — bisher hat compute_ass weighted_score auf den geladenen Zeilen
gesetzt, was ein GET per Autoflush zu UPDATEs gebracht hat. Persistiert
wird nur noch, wo es gewollt ist (recompute-all und der naechtliche Job).
compute_avs/compute_ass/compute_urs bleiben als duenne Wrapper ueber
denselben Code, damit Einzel- und Massenpfad nicht auseinanderlaufen
koennen. Die CPR-Formel steht jetzt als cpr_score() auf Modulebene, sodass
der Massenpfad sie ueber eine Spaltenabfrage laufen lassen kann, statt pro
Finding ein ORM-Objekt zu bauen — eine Quelle, keine zweite Kopie.

Dabei aufgefallen: /compliance/assets hat get_compliance_summary()
aufgerufen, das Ergebnis weggeworfen und dieselbe Aggregation nochmal
inline gemacht — beide Kopien mit einer Hostname-Abfrage pro Asset in der
Schleife. Zwei N+1-Schleifen fuer eine Liste. Jetzt eine gemeinsame
_asset_rollups(), zwei Abfragen.

Messung 265 Assets, 3 ms Round-Trip: 2121 Statements / 11,7 s → 6
Statements / 0,18 s, Ergebnis Zeile fuer Zeile identisch.

- tests/test_urs_bulk_scoring.py pinnt Statement-Zahl unabhaengig von der
  Flottengroesse, Gleichheit mit dem Einzelpfad in allen drei avs_modes,
  und dass ein Lesen nichts schreibt
- tests/test_compliance_rollup_queries.py pinnt dasselbe fuer das Rollup
2026-08-28 14:24:02 +02:00