Zum Inhalt

Polycrate API 0.14.15

Release-Datum: 19. März 2026
Typ: Hotfix

Highlights

  • FTS DB-Bloat Hotfix – Der Reconciliation Loop triggerte nach dem FTS-Rollout (0.14.13) ~750 Tuple-Updates pro 5 Minuten mit vollständigen GIN-Index-Rebuilds; DB-Wachstum ~3,5 GB/Tag, Block I/O verdreifacht
  • Signal-Guard Fixset_state() und add_condition() nutzen jetzt save(update_fields=...) damit der FTS-Signal-Guard state-only Saves korrekt herausfiltert
  • Suche: Infrastruktur-Namen mit Bindestrichsearch_type="websearch" interpretierte - als NOT-Operator; jetzt "plain" für korrekte Ergebnisse bei Namen wie prod-k8s-01, aycloud-fra1

Artefakte

Docker Image

docker pull cargo.ayedo.cloud/polycrate/polycrate-api:0.14.15

Block

polycrate pull cargo.ayedo.cloud/ayedo/k8s/polycrate-api:0.9.12
polycrate run polycrate-api install

Fixes

FTS GIN-Reindex-Storm: state und kind aus Signal-Trigger entfernt

Ursache: Nach dem FTS-Rollout (0.14.13) enthielt _SEARCH_BASE_FIELDS die Felder state und kind. Der Reconciliation Loop ruft set_state() bei jedem Durchlauf auf – damit wurde bei jeder Zustandsänderung ein vollständiger GIN-Index-Rebuild auf der SearchIndex-Tabelle ausgelöst.

Effekt: - ~750 Tuple-Updates / 5 Minuten auf SearchIndex - GIN-Index fastupdate-Pending-List wurde permanent geleert und neu befüllt → I/O-Bursts - MVCC-Bloat: Autovacuum kam nicht nach → DB-Wachstum ~3,5 GB/Tag (25 GB → 45 GB in 6 Tagen) - Block I/O verdreifacht (1000 → 3000 Blöcke/5min), WAL-Aktivität stark erhöht - Bis zu 45 blockierte Queries, eine Transaktion mit 33 Minuten Laufzeit

Fix: state und kind aus _SEARCH_BASE_FIELDS entfernt. Diese Felder sind Filter-only auf SearchIndex — ihr Wert beeinflusst nicht, was ein User in die Suchmaske tippt.

# signals.py
_SEARCH_BASE_FIELDS = frozenset([
    "name", "display_name", "description",
    "archived", "labels",
    "organization_id", "organization",
    "workspace_id", "workspace",
])

Signal-Guard: set_state() und add_condition() nutzen update_fields

Ursache: Der FTS-Signal post_save enthält einen Guard, der Re-Indexing überspringt wenn update_fields gesetzt ist und keine FTS-relevanten Felder betroffen sind. set_state() rief self.save() ohne update_fields auf — der Guard griff daher nie.

Zusätzlich: Im WARNING-Branch von set_state() existierte ein bare save() das den State nicht veränderte, aber dennoch den Signal-Guard triggerte.

Fix:

# models.py — set_state()
self.save(update_fields=["state", "last_state", "last_state_change"])

# models.py — add_condition()
self.save(update_fields=["conditions"])

Der no-op-save() im WARNING-Branch wurde entfernt.

Suche: search_type="plain" statt "websearch"

Ursache: SearchQuery verwendete search_type="websearch". PostgreSQL's WebSearch-Parser interpretiert - als NOT-Operator. Suchanfragen wie prod-k8s-01 wurden intern zu prod AND NOT k8s AND NOT 01 — valide Objekte blieben unsichtbar.

Fix:

# search.py
search_query = SearchQuery(query, config="simple", search_type="plain")

Nach dem Deployment

Nach dem Deployment empfiehlt sich ein manuelles VACUUM ANALYZE auf der SearchIndex-Tabelle um den aufgelaufenen MVCC-Bloat sofort zu bereinigen und die Autovacuum-Aggressivität anzupassen:

-- Autovacuum für SearchIndex aggressiver konfigurieren
ALTER TABLE polycrate_api_searchindex SET (
    autovacuum_vacuum_scale_factor = 0.02,
    autovacuum_analyze_scale_factor = 0.01,
    autovacuum_vacuum_cost_delay = 2
);

-- Aufgelaufenen Bloat sofort bereinigen
VACUUM ANALYZE polycrate_api_searchindex;