Skip to content
ZERONE
Nazaj na vpoglede
Porazdeljeni sistemi2026-04-18 · 4 min branjaIz Case 01

Batch finalizacija na ravni kontejnerja: zakaj monitor 83 minut ni videl ničesar

Pipeline z N paralelnimi sub-jobi finalizira status na ravni batcha — vsi workerji tečejo, monitor pa javlja zastoj, dokler zadnji kontejner ne konča. Rešitev: finalizirati po kontejnerju, ne po batchu.

Bug ni bil v tem, da bi bilo kaj pokvarjeno. Bug je bil, da je monitor trdil, da nič ne teče — medtem ko je vse teklo.

Setup: discovery daemon vsakih 30 minut razdeli 3 000 poizvedb na 8 vzporednih Docker kontejnerjev. Vsak kontejner obdela ~375 poizvedb po 9/min = 41 minut na kontejner. Ker so dolžine vrst različne, čas zaključka varira med 8 in 17 minutami na kontejner — zadnji pa pride do 83 minut.

Monitor je pollal vsakih 5 minut: "koliko jobov je batch zaključil v zadnji uri?". Dokler batch ni bil v celoti končan, je javljal 0 done/h. Operatorske nadzorne plošče so kazale "Discovery pipeline inactive". V resnici je osem kontejnerjev teklo s polno paro.

Arhitekturni greh

Originalna koda je imela en sam finalize() klic na koncu batch wrapperja:

def run_batch(queries):
    assign_to_containers(queries)
    wait_for_all_containers()
    finalize(batch_id)  # ← šele tukaj se status propagira

Pomeni: dokler zadnji kontejner ne zaključi zadnje poizvedbe, signal uspeha v nobeni tabeli, ki jo monitor bere, ne obstaja.

Popravek

Vsak kontejner javi svoj zaključek:

def run_container(container_id, queries):
    for q in queries:
        process(q)
        write_result(q, container_id)
    # Vsak finish event se takoj propagira
    finalize_container(container_id, batch_id, count=len(queries))

Poleg tega: batch wrapper na koncu naredi le še zaključni finalize_batch(batch_id) klic za batch-level statistiko (skupno trajanje itd.), ne več za row-level progress.

Monitor zdaj vidi nove številke ob vsakem zaključku kontejnerja. "0 done/h" postane "37, 284, 531, …" v prvih 20 minutah.

Pravilo kalibracije

Iz incidenta smo izpeljali numerično pravilo, ki ga odtlej uporabljamo na vsakem batch pipelineu:

Velikost batcha = Workerji × Throughput/min × Ciljne minute

Za ciljni čas zaključka 15 minut pri 9 poizvedbah/min s 4 workerji: 4 × 9 × 15 = 540 poizvedb/batch (zaokroženo 600). Z 8 workerji: 8 × 9 × 15 = 1 080 (zaokroženo 1 200).

Prejšnji 3 000 batch je bil pravilo palca brez ozira na granularnost monitorja. S 600 batchi vsaka iteracija teče pod 20 minutami — monitor vidi nove finish evente vsakih 8 minut.

Prenosljivi vzorec

Anti-vzorec ni omejen na web crawlerje. Našli smo ga v treh drugih setupih:

  • ETL pipelinei, ki polnijo staging tabele po batchu in šele na koncu potisnejo v produkcijo z INSERT ... SELECT.
  • ML treniranje, ki checkpoint piše šele na koncu vsake epohe — monitoring kaže "stale" 40+ minut pri velikih epohah.
  • Backup jobi, ki status postavijo na ✅ šele, ko so vsi chunki gotovi — 6 h status-slepote, medtem ko backup teče.

Operativni protistrup je vedno enak: finalizirati kar najbolj granularno. Per-container, per-shard, per-epoch, per-chunk. Vse, kar naredi granularnost monitoringa bistveno krajšo od skupnega runtimea, je prava odločitev.

Podoben izziv tudi pri vas?

Verjetno smo že videli kaj podobnega. Pogovorimo se.

Začnimo pogovor