# FORGE colima sizing — kad CI pada na OOM u docker buildu (MC #107134/#107139)

# FORGE colima sizing — kad CI pada na OOM u docker buildu

**Status:** AKTIVNO · **Nastalo:** 2026-08-14, MC #107134/#107139 · **Vrijedi za:** self-hosted azdo agent `bilko-forge-1` na FORGE (`makinja@100.94.54.37`)

## Simptom
Build na `main` pada u fazi **`Build (linux/amd64 → ACR)`**:
```
npm error signal SIGKILL
ResourceExhausted: cannot allocate memory
```
Sve prethodne faze prolaze — Kotlin compile, integrationTest, Kotest su zeleni. Pada isključivo korak koji gradi `bilko-web` docker image.

**Nije isto što i MC #104886** („Reached heap limit"). Tamo je JS heap dosegao svoj strop; ovdje **host/VM nema RAM-a**, pa dizanje `--max-old-space-size` **pogoršava** stvar.

## Uzrok
`colima` VM na FORGE-u je imao **6 GiB / 4 CPU** na mašini od 256 GB. Uz to se `bilko-web` gradi kao `docker buildx --platform linux/amd64`, dakle **QEMU emulacija na ARM-u**, što dodatno diže potrošnju.

## Popravka
```bash
ssh makinja@100.94.54.37
export PATH=/opt/homebrew/bin:$PATH
# backup postojeceg configa prije izmjene
cp ~/.colima/default/colima.yaml ~/.colima/default/colima.yaml.bak-$(date +%Y%m%d)
colima stop && colima start --cpu 8 --memory 24
```

**Provjera na oba mjesta — ne vjerovati jednom:**
```bash
colima list                        # -> aarch64, 8 CPU, 24GiB
docker info | grep -i "total memory"   # -> Total Memory: 23.42GiB
```

## Kako znati da je stvarno riješeno
Ne po tome što VM ima više RAM-a, nego po **kontrolisanom prije/poslije na istoj grani**:

| Build | sha | colima | Ishod |
|---|---|---|---|
| 1065, 1066, 1067, 1070 | `4f9224af` | 6 GiB | svi pali na fazi 2, ~30 min |
| **1072** | `e6f3a526` | **24 GiB** | **faza 2 prošla — nekeširano**, *"Compiled successfully in 4.2min"*, bez SIGKILL |
| 1073 | `e6f3a526` | 24 GiB | faza 2 „prošla" za 16 s — **korak je bio CACHED, nije izvršen** |

**Primarni dokaz je build 1072, ne 1073.** Ovo je ispravka nakon nezavisne peer-verifikacije (2026-08-15): 1073 je bio prvi citiran, ali njegov OOM-osjetljiv korak (`npm run build --workspace=@bilko/web`) je bio **registry-cache pogodak** (`#12 CACHED`) — cijeli job 16 sekundi. Sam po sebi ne dokazuje ništa o memoriji. Keširan je bio **zato što** je 1072 (isti sha, u redu 8 minuta ranije, ništa za ponovnu upotrebu) taj isti korak već odradio **nekeširano i uspješno** pod 24 GiB.

Pouka koja se ponavlja: *„faza prošla" nije isto što i „faza se izvršila".* Provjeri je li korak stvarno radio ili je uzet iz keša prije nego ga navedeš kao dokaz.

Dokaz: `~/system/evidence/107134/colima-fix-proven-build1073-2026-08-14.md` + peer-verifikacija `~/system/evidence/107139/peer-verify-transcript-2026-08-15.md`

## Kontrola confounda — RIJEŠENA EKSPERIMENTOM (2026-08-15)

1072 je na sha `e6f3a526`, a padovi su na `4f9224af` — različit sha je bio **stvaran** confound.

Prvo sam ga pokušao odbraniti argumentom: `git diff 4f9224af..e6f3a526` dira 113 fajlova, nijedan CI/build config, samo aplikativni kod, 8103 dodane linije — dakle posao je **teži**, ne lakši. Cross-vendor recenzent (gpt-5.6-sol) je to odbio, s pravom: to je rezonovanje, nije kontrola.

**Kontrola je onda izvedena — isti sha, mijenja se samo memorija:**

```
commit 4f9224af   (tačno onaj koji je 4× pao pod 6 GiB)
colima: 8 CPU / 24GiB   (docker info: Total Memory 23.42GiB)
docker buildx build --platform linux/amd64 -f apps/web/Dockerfile ...
#27 DONE 372.1s          exit=0          real 6m43.219s
grep -ci "sigkill|cannot allocate memory|ResourceExhausted|killed"  →  0
```

Izvedeno **van CI-ja**, direktno na FORGE-u u scratch worktreeu. Namjerno se nije puštalo kroz main pipeline: to bi deployalo stari aplikativni kod u živo okruženje, a PR/branch putanja uopšte ne gradi ovaj image pa ne može poslužiti kao test.

Dokaz: `~/system/evidence/107139/same-sha-control-build-2026-08-15.log` + `same-sha-control-preflight-2026-08-15.txt`

### Ni to nije bila prava kontrola — druga runda recenzije

Cross-vendor recenzent je i ovo odbio, opet s pravom:

> *„The same-SHA run is not a memory-only control because CPU doubled and the restart removed orphan containers."*

Tačno. Promjena je bila **6 GiB / 4 CPU → 24 GiB / 8 CPU**, a restart je uz to pomeo 3 orphan Testcontainers postgres kontejnera i `lumiscare-redis`. **Tri varijable, ne jedna.** Prvi „kontrolni" run je dokazao da novo okruženje radi — ne i *šta* je od toga bilo presudno.

### Izolacija — treći run, i ovaj je konačan

Isti sha, **CPU ostavljen na novoj vrijednosti (8)**, orphan kontejneri već pometeni, spuštena **samo memorija** na 6 GiB:

```
colima: aarch64, 8 CPU, 6GiB
#27 307.3  npm error signal SIGKILL
ERROR: ... "npm run build --workspace=@bilko/web" did not complete successfully: cannot allocate memory
ResourceExhausted
real 5m38.358s   exit=102
```

Dokaz: `~/system/evidence/107139/mem6gib-cpu8-isolation-build-2026-08-15.log`

### Konačna tabela — dvije tačke koje se razlikuju SAMO u memoriji

| sha | CPU | RAM | orphani | ishod |
|---|---|---|---|---|
| `4f9224af` | 4 | 6 GiB | prisutni | pao ×4 (CI 1065/1066/1067/1070), ~30 min |
| `4f9224af` | **8** | **6 GiB** | **pometeni** | **pao — SIGKILL, cannot allocate memory, 5m38s** |
| `4f9224af` | 8 | 24 GiB | pometeni | **prošao — 6m43s, bez SIGKILL** |

Srednji red isključuje obje konkurentske hipoteze odjednom: CPU je na novoj, višoj vrijednosti i **ne spašava**; orphani su počišćeni i **ne spašavaju**. Ostaje memorija.

**Zaključak: memorija je bila vezujuće ograničenje. Dokazano izolacijom, ne argumentom.**

FORGE je poslije eksperimenta vraćen na 8 CPU / 24 GiB i to je provjereno (`colima list` + `docker info` → `Total Memory: 23.42GiB`).

### Pouka o metodi — tri kruga, tri različite greške
1. Prvo sam kao dokaz naveo run čiji je korak bio **keširan** (`#12 CACHED`, 16 s) — „faza prošla" nije „faza se izvršila".
2. Zatim sam confound branio **argumentom** („113 fajlova čini posao težim") umjesto kontrolom.
3. Zatim sam kontrolu nazvao „samo memorija" iako su se promijenile **tri stvari**.

Moj vlastiti peer-verifikator je dao 12/12 PASS i propustio sve troje. Model drugog vendora je oborio dva kruga zaredom i oba puta bio u pravu. Kad se više varijabli promijeni zajedno, jedini izlaz je pustiti isti ulaz kroz postavke koje se razlikuju u **jednoj** stvari.

## Dvije zamke koje su nas koštale
1. **PR build ne dokazuje ništa o OOM-u.** Build 1071 (PR 338) je bio zelen poslije popravke i djelovao kao dokaz — ali PR putanja **uopšte ne gradi web image**. Prethodni pad istog PR-a (1069) bio je na `integrationTest baseline check`, ne na memoriji. Dokaz je isključivo **main** build.
2. **Jedan paralelni slot org-wide.** Ako ručno pokreneš build dok drugi već radi na istoj grani, drugi čeka — build 1073 je izgubio **21 minutu** čekajući 1072. Prije ručnog pokretanja provjeri šta već teče. `azdo-pr-requeue.sh` **kancelira** build koji je `running`.

## Trajni pravac (nije urađeno)
Web image se gradi emulirano. Organizacija **već ima plaćen Microsoft-hosted paralelni slot** (`isHosted:true, PurchasedCount:1, totalMinutes:1800`) koji **stoji prazan** — svih 11 jobova je zakucano na `bilko-selfhosted`. Premještanje samo image-build joba na hosted agenta daje nativni amd64 (bez QEMU) i skida kontenciju, bez ijednog dolara.

**Ograničenje:** `bilko-demo-pg` je IP-ograničen na FORGE, pa jobovi koji diraju bazu (Flyway, E2E) ne mogu na hosted agenta bez izmjene firewalla.

Detalji i cijene: `~/system/evidence/107134/ci-capacity-options-2026-08-14.md`

## Metodološka napomena
Prije ove popravke napravljene su **tri pogrešne atribucije** uzastopno (Sentry source mape, dupli typecheck pod QEMU, pa „nešto u aplikaciji"). Sve tri izmjene su bile korisne same po sebi, ali nijedna nije bila uzrok. Uzrok je nađen tek kad je neko pogledao **koliko RAM-a VM uopšte ima** — a to je stajalo neprovjereno jer je ranije prijavljeno „nemam SSH pristup FORGE-u", što nije bilo tačno: `makinja@` radi.