urMQTT na pravom VPS-u: ISO, rukovanje od 0,24 ms, vlastiti disk i ojačana jezgra
Što se dogodilo s tri obećanja iz prvog teksta o urMQTT-u (jedan ISO, sirovi disk bez datotečnog sustava, dokazivo sigurno) nakon prva četiri dana razvoja, s mjerenjima, tablicama i popisom onoga što još nije gotovo.

U prvom tekstu o urMQTT-u opisali smo unikernel koji jest MQTT broker, do prekretnice M17, i završili s tri obećanja pod naslovom "Što slijedi": jedan ISO koji se podiže na više platformi, sirovi disk bez datotečnog sustava i sigurnost koja se može dokazati, a ne samo tvrditi. Ništa od toga tada nije bilo izgrađeno. Ovaj tekst prati što se dogodilo sa svakim obećanjem, istim redom kao i prije: što smo planirali, što smo izgradili, što nas je iznenadilo i što je još otvoreno. Usput ćemo pokazati i koliko je to trajalo, jer je to dio priče.
Jedna napomena unaprijed: projekt je i dalje pred-alfa. Brojke u tekstu mjere kod i testove, ne zrelost u produkciji, a prva objava je namjerno ograničena na VPS-ove s KVM-om. Bare metal ostaje za kasnije.
Kako je to izgledalo u vremenu
Prvi commit nastao je 28. listopada 2026. u 23:48 (M0). Do 31. listopada u 23:00 u repozitoriju je 119 commitova, raspoređenih kao 3, 39, 38 i 39 po kalendarskim danima. Odmah recimo i kako je to moguće: projekt se gradi uz Claude Code, a commitovi nose Co-Authored-By: Claude trailere. Četiri kalendarska dana povijesti ne znače da je jedna osoba za četiri dana napisala 90 000 redaka; znače da se plan, implementacija, testovi i ADR-ovi pišu u kratkim, zatvorenim petljama, a svaki korak završava istim ritualom provjere kao i prije. Brojke ispod govore o količini koda i testova, ne o tome koliko je sve to provedeno u stvarnom radu.
Graf pokazuje tri krivulje koje rastu skoro zajedno, što i je namjera: testovi i zapisane odluke prate kod, a ne zaostaju za njim. Nagib je najstrmiji 29. listopada, kad nastaje cijeli put od jezgre do brokera. Dalje se redovi dodaju sporije, ali testovi rastu brže od koda (27 testova na 1,6 tisuća redaka na početku, 739 na 89,8 tisuća danas).
| Prekretnica | Commit | Vrijeme | Rust redaka | Testova | ADR-ova |
|---|---|---|---|---|---|
| M0 | 9412a9c | 28.10. 23:48 | 1 592 | 27 | 13 |
| M5 | a61083b | 29.10. 01:32 | 9 314 | 88 | 24 |
| M9 | 4be8cc9 | 29.10. 02:21 | 15 729 | 149 | 28 |
| M11 | 4800904 | 29.10. 03:43 | 22 737 | 180 | 30 |
| M13 | a558d5e | 29.10. 05:22 | 26 556 | 193 | 32 |
| M17 | 7df4f98 | 29.10. 08:48 | 33 504 | 232 | 36 |
| S1 | ecf72ec | 30.10. 00:46 | 41 910 | 299 | 45 |
| X8 | fb1504a | 30.10. 19:08 | 56 341 | 456 | 59 |
| PERF | 76f7c36 | 31.10. 14:19 | 72 511 | 618 | 76 |
| danas | HEAD | 31.10. 23:00 | 89 790 | 739 | 104 |
Redovi su svi .rs fajlovi izvan vendor/ i fuzz/, testovi su #[test] funkcije. Što je koji dio dodao, po datumu iz git povijesti:
| Datum | Prekretnice | Što je dodano |
|---|---|---|
| 29.10. | M0 do M17 | jezgra, mreža, TLS, broker, SQLite, više jezgri, vlastiti TCP, ojačanje, reproducibilno izdanje |
| 29.10. navečer i 30.10. | X0 do X10, S1, H2, H3 (X0 počinje 29.10. u 20:54) | port na x86_64, univerzalni ISO, jedan disk i samoinstalirajući ISO, TLS provjere, napadački klijenti |
| 31.10. | O1 do O4, T1, PERF, H4, H5, H9, S2 do S6 | uvid i konzola preko MQTT-a, živi način rada i dijagnostika, SNTP, ubrzanja, ojačanje, verifikacija, lanac nabave, pohrana |
Obećanje 1: jedan ISO za više platformi
Što smo planirali. Jedna datoteka koja se podiže na aarch64 UEFI-ju, x86_64 UEFI-ju i x86_64 BIOS-u. Što smo izgradili. Port na x86_64 (X0 do X10) i univerzalni ISO s Limine bootloaderom, jedan hibridni ISO s kojeg se podižu sve te kombinacije; smoke testovi ga podižu na četiri virtualna stroja. Izbornik ima tri unosa: urMQTT (živi način rada, radi iz RAM-a, ništa ne piše na disk), instalacija na disk stroja na kojem je pokrenut (instalacija piše GPT i podatke, pa gasi stroj) i Dijagnostika (detekcija hardvera i mjerenja).

Prva stvarna meta bio je VPS, a ne fizičko računalo: odluka je da prvo izdanje podržava samo VPS (KVM), a bare metal dolazi nakon njega. Testirali smo na Contabo VPS-u s AMD EPYC procesorom (obitelj 23, Zen 1), 4 vCPU-a i 8 GiB RAM-a, s virtio-net mrežom i virtio-scsi diskom. Prvi pokušaj nas je iznenadio: ISO priključen kao virtualni CD podizao je zadani unos, produkcijska slika odbila je poslužiti s medija samo za čitanje i resetirala se, pa smo dobili beskonačnu petlju izbornik, jezgra, reset bez ičega čitljivog. Danas fatalna greška u produkcijskoj slici ostavlja poruku na konzoli 60 sekundi prije resetiranja, a s optičkog medija ispisuje da je podignuto s medija samo za čitanje i staje. Živi način rada u RAM-u (O3) kasnije je taj problem zaobišao za uobičajeni slučaj.
Dijagnostički unos nastao je iz tog iskustva: stroj koji ne vidimo moramo moći pitati što ima. Ispisuje CPU, RAM, mrežu, diskove i PCI uređaje, mjeri propusnost memorije, disk i kriptografiju, a ista mjerenja ispisuje kao JSON na serijski port. Zapisivanje na disk dopušteno je samo u neraspoređenom prostoru i tek nakon što se utipka WIPE; na snimci ispod to je preskočeno.

Što je otvoreno. Pravi hardver. Poslije prvog izdanja slijedi bare metal (USB podizanje, NVMe i AHCI, mrežna kartica stvarnog računala), zajedno s IOMMU-om i Secure Bootom, jer bez IOMMU-a zlonamjerni ili neispravni uređaj može pisati po cijeloj memoriji. U ovoj verziji o tome ne tvrdimo ništa.
Rukovanje: od 1,2 ms do 0,24 ms
Prvo mjerenje dijagnostike na VPS-u dalo je nezgodnu brojku: mTLS rukovanje, samo kriptografija na strani poslužitelja, trajalo je 1,2 ms. Na aarch64 smo već koristili hardverske AES i SHA instrukcije, ali x86_64 nije imao ništa slično za javni ključ: RustCrypto je prenosiv i constant-time, pa i spor. Tri promjene su to spustile.
- Za P-256 smo preuzeli
nistz256asembler iz OpenSSL-a i ring-a (ADR-130), uz tablice s fiksnom bazom za potpisivanje i provjeru potpisa CA (ADR-110, ADR-111). - X25519 je prepisan na četiri 64-bitna uda s MULX/ADX instrukcijama (ADR-131).
- Nakon toga smo profilirali i ostatak (ADR-132, ADR-200).
| Mjerenje na VPS-u (server, mikrosekunde) | Prije | Poslije |
|---|---|---|
| mTLS rukovanje, cijela kriptografija | 1200 | oko 240 (237 u mjerenju s grafa, 244 na snimci) |
| X25519 | 146 | 48 |
| ECDSA potpis | 673 | 34 |
| ECDSA provjera | 309 | 90 (107 na snimci) |
| rukovanje, stari memcpy, isti boot | 278,8 | 229,4 (-17,7%) |
Brojke se razlikuju po pokretanju oko 10%, pa smo zapisali raspon umjesto jedne vrijednosti. Ukupno je to otprilike pet puta manje CPU vremena za rukovanje.
Što smo naučili o memcpy-u. Kad je kriptografija pala, profil je pokazao nešto što nismo očekivali: funkcije mem* (memcpy, memmove, memset) trošile su 7% rukovanja, 13 do 39% puta TLS zapisa (39% za 64 B, 13% za 4 KiB) i 17 do 34% razdjeljivanja PUBLISH-a. Zamijenili smo ih SSE2 asemblerom za x86_64 (ADR-200), uz diferencijalne testove prema referentnoj implementaciji za sve veličine, poravnanja i preklapanja. Po pozivu je to 55 do 75 ciklusa prema 7 do 12 za 1 do 64 bajta.
| Put | Udio mem* prije | Brže poslije |
|---|---|---|
| rukovanje | 7% | 17,7% |
| TLS zapis 64 B | 39% | 26,4% |
| TLS zapis 256 B | 37% | 19,6% |
| TLS zapis 1 KiB | 24% | 16,1% |
| TLS zapis 4 KiB | 13% | 6,6% |
| razdjeljivanje poruka | 17 do 34% | 7 do 34% |
Čitanje grafa: što je zapis manji, to veći dio vremena odlazi na kopiranje, pa je i dobitak veći. Za male poruke, koje su većina MQTT prometa, to je ono što znači.
Što ovo ne znači. Na nativnom x86_64 smo samo kao referencu za asembler mjerili OpenSSL 3.5.5 (ADR-130), a ne kao usporedbu proizvoda. Usporedbu Linux plus Mosquitto na istom VPS-u (zadatak X11) još nismo napravili. Zato ne tvrdimo da je urMQTT brži od Linuxa: nije izmjereno. Brojke iznad uspoređuju urMQTT sa samim sobom.
Preopterećenje je bio drugi rezultat. Na VPS-u, s jednog Maca (RTT 35 ms), 128 klijenata dalo je 618 rukovanja u sekundi, 256 klijenata 393, a 512 klijenata 67, uz tisuće odbijenih veza. Nije to bio plato nego urušavanje. Uzroke smo pronašli u kodu (dva su potvrđena u QEMU-u/HVF-u, na VPS-u se nisu mogli ponoviti), više njih odjednom: odbijena veza vraćala je klijenta preko sljedećeg segmenta koji nosi valjani SYN kolačić, odbijanje je RST-om govorilo klijentu da odmah proba ponovno, a prijem je bio O(red). Ispravak (ADR-080 do ADR-082) ispušta SYN koji ne bi bio prihvaćen, pamti odbijene tokove, drži prijem u O(1) i broji rukovanja po jezgri. Moguće je (hipoteza, nije provjereno) da je dio uzroka bio i na klijentu: alat za opterećenje je zatvarao prvi, pa je jedan Mac s 16 384 efemernih portova i TIME_WAIT-om od 30 sekundi ograničen na oko 550 veza u sekundi. Zato pravi strop poslužitelja još nismo izmjerili.
Obećanje 2: sirovi disk, ali pažljiv
Što smo planirali. Zadržati vlastiti raspored bez datotečnog sustava i dodati sve ono što datotečni sustav inače daje: poravnanje, rast, discard, sigurnosne kopije i provjere. Zašto ne datotečni sustav. O tome smo raspravljali i odluka je potvrđena: urMQTT piše jednu bazu, jedan telemetrijski dnevnik i vlastite metapodatke, ne treba imenike, dozvole, ni imena datoteka, a svaki dodatni sloj bio bi još kod u povjerljivom dijelu i još jedan način da zapis tiho nestane. Disk je GPT s ESP particijom i jednom urMQTT particijom podataka, na kojoj je vlastiti raspored.
Što smo izgradili (S2 do S6).
- Raspored v2 (S2). Topologija diska, poravnanje na 1 MiB ili optimalnu veličinu I/O-a, cijeli fizički blokovi, A/B par superblokova s generacijom (ADR-140). Raspored v1 kernel odbija uz jasnu poruku, a migrira ga izvanmrežni alat
urmqtt-disk migrate(ADR-141): zadano je probni prolaz, kopira se pa tek onda prebacuje, uz povrat i ubrizgavanje greške na svakom zapisu. - Rast (S3). Pri podizanju disk može narasti (nikad se ne smanjuje), a na virtio-blk i za vrijeme rada, preko prekida promjene konfiguracije (ADR-142, ADR-143).
- Discard i write-zeroes (S4). Tanko dodijeljeni diskovi vraćaju prostor domaćinu. U testu je qcow2 datoteka na domaćinu pala sa 62 na 16 MiB nakon što su istekli telemetrijski segmenti, a oporavak nikad ne vjeruje sadržaju koji je odbačen.
- Sigurnosna kopija baze na disku (S5). A/B kopija pomoću SQLite online backup API-ja u koracima po 16 stranica: najduži korak 4,6 ms naspram 445 ms kad se kopira u jednom pozivu. Komitovi tijekom kopije imali su maksimum 2,4 ms prema 2,2 ms bez nje. Ako
quick_checkpri podizanju ne uspije, baza se vraća iz kopije. - Čišćenje i ograničeno podizanje (S6). Scrubber čita disk uz ograničen I/O proračun i prijavljuje oštećene zapise, a podizanje čita samo najnoviji segment svake particije, tako da vrijeme podizanja ne raste s veličinom telemetrijske regije. Time smo zatvorili problem koji smo izračunali unaprijed: na velikom disku bi potpuni oporavak trajao od minuta do desetaka minuta.
Cijela grupa smoke testova za pohranu prolazi na obje arhitekture: 79 uspješnih, 0 neuspjelih, na aarch64 pod HVF-om i na x86_64 pod TCG-om. Mjerili smo i da S4 nije usporio bazu: 53,3 tisuće umetanja u sekundi u paketima prije i poslije, bez izmjerive razlike.
Što je iznenadilo. Koliko od "jednostavnog" rasporeda otpada na rubne slučajeve: virtio-scsi i virtio-blk prijavljuju topologiju na različite načine, a nepotpuni zapis u sredini migracije mora uvijek ostaviti bar jednu valjanu verziju. Zato je svaki zapis na putu migracije i rasta prošao ubrizgavanje greške.
Što je otvoreno. SQLite stranice nemaju kontrolni zbroj, pa promijenjeni bit unutar vrijednosti u retku baza neće otkriti; telemetrijski zapisi i sadržaj kopija imaju CRC i otkrivaju se. Šifriranje diska (S7) nije gotovo.
Obećanje 3: dokazivo sigurno, koliko god to uspijemo
Ojačanje (H4). Alokator briše oslobođenu memoriju i provjerava je pri ponovnoj uporabi, otkriva dvostruko oslobađanje, veže pokazivače slobodne liste XOR-om i nulira ključeve pri oslobađanju (ADR-170). Zaštita stoga (-Zstack-protector=strong, 1315 funkcija, a SQLite s -fstack-protector-strong, ADR-171). Izvješća o greškama u produkciji ne sadrže registre ni adrese (ADR-172). KASLR na obje arhitekture: kernel je PIE i Limine ga pomiče za oko 19 bitova, a nasumično se postavljaju heap, stogovi i MMIO prozor (ADR-173). Na aarch64 MTE označava heap (ADR-174), ali samo pod TCG-om: HVF i x86 ga nemaju. kCFI smo procijenili i nismo ga prihvatili jer prevoditelj na prikvačenom nightlyju pada s unutarnjom greškom; IOMMU je odgođen s bare metalom (ADR-175, ADR-176).
Cijena je izmjerena, ne pretpostavljena. Na putu rukovanja (dp-bench, aarch64 HVF, puna TLS 1.3 + mTLS + CONNECT rukovanja):
| Korak | 1 jezgra | 4 jezgre | Promjena |
|---|---|---|---|
| bez ojačanja | 1941 | 7620 | |
| + alokator | 1898 | 7429 | -2,2% do -2,5% |
| + zaštita stoga | 1845 | 7243 | zajedno -4,9% do -5% |
| + PIE i oznake | oko 1800 | oko 6750 | na opterećenom računalu, šumovito |
Sveukupno je dakle cijena na putu rukovanja otprilike 5% za dijelove mjerene čisto, a s PIE-jem i oznakama izlazi oko 7% na jednoj i oko 11% na četiri jezgre, no to je mjerenje šumovito. Smatramo je prihvatljivom, ali nismo je ponovili na x86_64 VPS-u.
Vrijedi usporediti s brojkom iz prvog teksta (862 rukovanja u sekundi na jednoj jezgri). Isti alat danas, s pogonom hardverskog kriptiranja na aarch64 (X0b) i nizom ubrzanja koja su stigla otada, daje 1941 prije ojačanja. Nismo izolirali doprinos svake promjene, pa o uzroku ne tvrdimo ništa.
Kako je provjereno, i što nije. H5 je dodao Miri na podskup host crateova, Kani dokaze, sanitizere, model-fuzzer za ACL i izvješća o pokrivenosti. Kani je dokazao record_len u TLS-u, parse u TCP-u i dvije aritmetike kotača tajmera. Nije dokazano (isteklo vrijeme): oba MQTT dekodera, oba parsiranja konfiguracije i ponašanje kotača tajmera; CI posao za Kani zato još ne blokira. Miri ne može izvoditi asembler. Lanac nabave (H9): CycloneDX SBOM s 215 komponenti, cargo-vet prolazi s 39 potpuno revidiranih i 140 izuzetaka, a cargo-deny je konfiguriran da savjete o ranjivostima tretira kao pad CI-ja (ti CI poslovi do sada su provjereni samo lokalno, ne na GitHubu, a negativni test nije pokrenut), uz VULNERABILITY-HANDLING.md. Iskreno stanje: od 67 kernel-linkanih verzija trećih crateova, 53 su izuzeci, a ne revizije. Vet dokazuje da se skup ovisnosti ne može promijeniti bez vidljive razlike, ne da je TCB pregledan. Vlastitu reviziju ni jednog cratea nismo napisali.
SNTP (T1) je u tom smislu mala priča o sigurnosti: opcionalan je, samo prema privatnim IPv4 poslužiteljima, s maksimalnim skokom i uvjetom da se izvori slažu (medijan, većina unutar jedne sekunde). Sat koji bilo tko na putu može pomaknuti nije sat kojem smijemo vjerovati kod valjanosti certifikata.
Na Macu: aarch64 izvorno i x86_64 emulacijom
Razvijamo na Macu, a taj ima dva načina za pokretanje istog koda i oni su vrlo različiti.
| aarch64 pod HVF-om | x86_64 pod TCG-om | |
|---|---|---|
| Što je to | izvorno izvođenje, brzina blizu stvarnog procesora | QEMU emulira instrukcije |
| Rukovanje u gostu | oko 0,58 ms (prvi put mjereno oko M13); dp-bench danas 1941/s po jezgri |
oko 3,3 ms |
| TLS smoke scenariji | brzi | oko 15 minuta po scenariju |
| MTE | ne (nema ga na HVF-u) | x86 ga nema; označavanje heapa radi samo za aarch64 gosta pod TCG-om |
| Čemu služi | stvarna mjerenja | ispravnost: x86 put se provjerava, brojke nisu performanse |
Pravilo je jednostavno: brojke pod TCG-om su emulacija i ne citiramo ih kao performanse. x86 performanse mjerimo samo na pravom VPS-u. Zato kriptografske brojke iznad dolaze s Contabo VPS-a, a ne s Maca.
Lekcije
- Mjerite na stvarnom stroju prije optimiranja. Prvi pristup bio je optimizirati ono što smo mislili da je sporo; tek je dijagnostika na VPS-u pokazala 1,2 ms i gdje točno odlazi vrijeme.
- Preopterećenje je zasebna značajka. Sustav koji je brz pod opterećenjem može se urušiti iznad njega; odbijanje mora biti jeftinije od prihvaćanja.
- Profil iznenađuje. Najveći pojedinačni dobitak za male poruke nije bio u kriptografiji nego u kopiranju memorije.
- Disk bez datotečnog sustava traži više posla, ne manje. Ono što sustav datoteka inače daje (poravnanje, rast, discard, kopije, provjere) moramo napisati i provjeriti sami, svaki korak s ubrizgavanjem grešaka.
- Iskrenost u brojkama. Memorijska mjerenja na dijeljenom VPS-u variraju do 10 puta među pokretanjima i ne govore ništa o kodu, pa ih ne navodimo. Kripto brojke su stabilne do oko 10%.
- Brzina razvoja nije zrelost. Testovi i ADR-ovi rastu uz kod, ali četiri dana nisu pravi pogon.
Što slijedi
- H6: SQLite izoliran na razini EL0, tako da greška u bazi ne otvara jezgru.
- H7: ostatak sigurnosti vremena (NTS i obrada skokova); SNTP je tek prvi korak.
- X11: usporedba s Linuxom i Mosquittom na istom VPS-u, tek tada ima smisla govoriti o relativnoj brzini.
- Izdanje: prva javna objava za VPS (KVM), uz potpisane artefakte i upute.
- Poslije izdanja: bare metal (X12 do X16), IOMMU i Secure Boot, te šifriranje diska.
Kod je na github.com/zeljan-alduk/urMQTT, a docs/PROGRESS.md i dalje je jedini izvor istine o tome što stvarno radi. Pred-alfa, kao i prije; prijave grešaka i sigurnosne prijave po uputama u SECURITY.md dobrodošli su.
Komentari
Pravila komentiranja