urMQTT po VPS: SQLite v uživatelském režimu, srovnání s Mosquittem, konfigurace jako data a builder v prohlížeči
Co se stalo po druhém textu o urMQTT: SQLite izolovaný mimo jádro, měření proti Mosquittu na stejném VPS (včetně toho, co nevyšlo v náš prospěch), nalezené příčiny pomalého jediného toku, konfigurace jako data s webovým builderem ISO a emulátorem v prohlížeči, nepovinný prostý MQTT a co ještě není dokázáno.

Druhý text, urMQTT na skutečném VPS, skončil seznamem „Co následuje": H6 (SQLite mimo jádro), H7 (bezpečnost času), X11 (srovnání s Mosquittem na stejném VPS) a první vydání. Tento text ten seznam sleduje ve stejném pořadí a pak přidává dvě věci, které na seznamu nebyly, protože vznikly cestou: konfiguraci jako data s webovým builderem ISO a nepovinný prostý MQTT bez TLS. Jako dříve: co jsme plánovali, co jsme postavili, co nás překvapilo a co zůstává otevřené. V tomto textu je i několik výsledků, které pro urMQTT nevycházejí dobře; nechali jsme je tu, protože jsou součástí pravdy.
Poznámka předem zůstává stejná: projekt je pre-alfa, první vydání je jen pro VPS s KVM a čísla měří kód, testy a konkrétní pokusy, ne zralost v produkci.
Jak to vypadalo v čase
Od minulého textu (31. 10. ve 23:00) do dneška (1. 11. ve 20:41) přibylo v repozitáři 59 nových commitů, celkem 178. Všechny vznikly během jednoho kalendářního dne, takže platí stejné upozornění jako dřív: projekt vzniká s Claude Code (commity nesou Co-Authored-By: Claude), plán, kód, testy a ADR se píšou v krátkých uzavřených smyčkách a čísla níže vypovídají o množství kódu a ověření, ne o tom, kolik lidské práce v nich je.
Čísla jsou spočítána stejnou metodou jako v minulém textu: všechny soubory .rs mimo vendor/ a fuzz/, funkce #[test], nadpisy ## ADR- v docs/DECISIONS.md.
| Okamžik | Commit | Čas | Řádků Rustu | Testů | ADR |
|---|---|---|---|---|---|
| minulý text | 794f607 | 31.10. 23:00 | 89 790 | 739 | 104 |
| H6 hotovo | a0423cc | 1.11. 03:59 | 95 078 | 754 | 107 |
| X11 | a29f49b | 1.11. 07:56 | 95 579 | 754 | 108 |
| webový builder a zrcadlo vydání | 582783f | 1.11. 16:49 | 105 645 | 848 | 122 |
| prostý MQTT, dokumentace | 8f959f1 | 1.11. 19:05 | 112 408 | 926 | 125 |
| dnes | d21e3ec | 1.11. 20:41 | 112 408 | 926 | 126 |
Pracovalo se paralelně na více větvích, takže pořadí commitů není přesně pořadím práce. ADR jsou číslována do 232, ale číslování má mezery (část byla při slučování větví přejmenována); počítáme nadpisy, ne nejvyšší číslo. Vydání: v0.2.0 (1. 11., 18:45 UTC) a v0.2.1 (1. 11., 20:22 UTC), obě označená jako pre-release.
H6: SQLite v uživatelském režimu
Co jsme plánovali. SQLite je 254 268 řádků C, které parsují databázi, WAL a žurnál z disku, a disk může být vyroben nebo upraven. Dosud běžel v jádře, s právy jádra. Co jsme postavili (ADR-210 až ADR-212): SQLite, jeho části libc a halda byly přesunuty do samostatného programu, dbworker, který běží na EL0 (aarch64), respektive v ring 3 (x86_64), ve vlastním adresním prostoru. Jádro už SQLite nelinkuje. Worker s jádrem komunikuje přes sdílené okno paměti a dvanáct systémových volání a každá vrácená délka a offset se před použitím kontroluje. Chyba ve workeru (nepovolený přístup, nedefinovaná instrukce, překročený časový rozpočet 30 s) ukončí volání, worker se znovu spustí a databáze se otevře z WAL jako při startu; po 5 restartech za minutu se úložiště prohlásí za vadné a nespouští se donekonečna.
Cena, změřená na aarch64 pod HVF, stejná VM, jádro se vestavěným SQLite oproti jádru s workerem, šest střídavých spuštění od každého:
| Měření | Před (v jádře) | Po (worker) | Rozdíl |
|---|---|---|---|
| vstup do uživatelského režimu a návrat | - | 1,1 us na kolo | |
db-bench, 5000 vložení v 50 transakcích, nejlepší / medián |
61,9k / 58,4k op/s | 54,7k / 53,3k op/s | -11,6% / -8,7% |
| jedno vložení na transakci, nejlepší / medián | 2319 / 2148 op/s | 2307 / 2126 op/s | -0,5% / -1% |
| celá záloha jedním voláním (23 MiB) | 514 ms | 635 ms | +23% |
Zápisy na disk (1572) a flushe (319) jsou identické, jak má být. Izolováno není tohle: jádro stále věří tomu, co worker vrátí, po kontrole tvaru. Kompromitovaný worker může lhát (například vrátit prázdný seznam odvolaných certifikátů a tím je „odvolat z odvolání"), ale nemůže číst paměť jádra, klíče TLS ani klíče ticketů. KPTI není, takže úniky typu Spectre/Meltdown nejsou zabráněny a nic o nich netvrdíme; klíč PAC sdílejí EL0 a EL1. Na x86_64 jsme cenu přepínání na KVM neměřili (pod TCG by číslo bylo nesmyslné) a samotné fuzzování workeru a protokolu není hotové. Sada testů úložiště (79 scénářů, každý točí SQLite ve workeru) prochází na aarch64 HVF a x86_64 TCG, na aarch64 TCG 76 (jeden se přeskakuje, protože TCG nemá MSI-X).
H7 je uzavřen, ale ne tak, jak jsme oznámili. Dolní mez času (ADR-050) a SNTP s konsenzem (T1) pokrývají rozsah H7; NTS (RFC 8915) zůstává mimo rozsah a je zapsán jako zbývající riziko, ne jako něco, co funguje. Nepovažujeme bezpečnost času za „vyřešenou", ale za hotovou v té části, kterou lze dokázat.
Nová síťová volba. Contabo nedává DHCP vlastnímu ISO, takže VPS dosud potřeboval jiný obraz než laboratorní VM. Nyní network.mode = "auto" (ADR-214) nejprve zeptá DHCP, a pokud pronájem nepřijde v zadané lhůtě (dhcp_timeout_s, výchozí 15 s) nebo ho ztratí, použije zadanou statickou adresu. Vyzkoušeno ručně na QEMU (s DHCP použije pronájem, bez sítě se po 5 s vrátí na statickou adresu); smoke scénář pro to ještě není.
X11: srovnání s Mosquittem na stejném VPS
Stejný druh stroje jako v minulém textu (Contabo, 4 vCPU AMD EPYC, 8 GiB), zátěž z linuxového hostitele vzdáleného 15 ms, oboustranný TLS 1.3 se stejnými certifikáty na obou stranách, Mosquitto 2.0.21 na Debianu 13 bez trvalého ukládání. Metoda a rozhodnutí jsou v ADR-213, surová data v docs/perf-results/. Medián ze tří spuštění.
| urMQTT | Mosquitto 2.0.21 | |
|---|---|---|
| handshaky/s, 32 / 64 / 128 klientů (profil bench) | 169 / 278 / 413 | 82 / 140 / 184 |
| totéž, s limity, jaké se dodávají | 185 / 262 / 333 (při 128: 202 až 286 chyb, resety handshaku) | - |
| paměť na nečinné spojení (20 000 spojení) | 0,50 KB haldy jádra (0,66 KB při 5000) | 26,8 KB růstu procesu |
| latence p50, QoS 0 / QoS 1, 800 zpráv/s, 64 B | 14,0 / 14,8 ms | 21,4 / 39,5 ms |
| QoS 1, 4800 zpráv/s, doručeno/s (p50) | 4209 (77 ms) | 4235 (197 ms) |
| 100 odběratelů, 50 zpráv/s: p50 | 16,6 ms | 99 ms |
| jeden tok, QoS 0, 64 B, publikováno/s | 2962 (0 až 7733) | 22 749 |
Co to znamená a co ne. Handshaky jsou dolní meze pro oba servery: generátor zátěže je sdílená VM s 60 až 70% ukradeného procesorového času a při 128 vláknech byl blízko vlastního limitu, zatímco pracovní jádra urMQTT byla vytížena jen z 12 až 13%. Poměr při 128 klientech (asi 2,2násobek) je nejvýznamnější; při 32 a 64 většinou měří čekání klientů. Čísla paměti nepočítají totéž (halda jádra oproti RSS procesu), takže „asi 50krát méně" je řádová hodnota, ne přesný poměr. Latence 14 ms je jedno kolo sítě (asi 15 ms), tedy „žádný měřitelný čas brokeru"; ocasy (p99 asi 100 až 200 ms) jsou u obou brokerů ovládány sdílenými hostiteli. Rozdíly pod asi 20% netvrdíme.
Co nevyšlo v náš prospěch a zůstalo v tabulkách: jediný tok TCP přes urMQTT nesl 2 až 8krát méně než přes Mosquitto, s výkyvy od nuly do 7,7 tisíce zpráv za sekundu mezi spuštěními, a 20ms watchdog handleru přerušoval živá spojení, když hypervizor zastavil vCPU (v produkčním nastavení action = "drop", 6 až 8 spojení na spuštění při zahlcení QoS 0). Obojí je popsáno v další části, protože to mělo příčiny v našem kódu.
Proč byl jediný tok pomalý: příčiny, ne výmluvy
Nejsnazší by bylo říct „sdílený VPS". Neřekli jsme to, ale s tímtéž VPS a stejnou metodou jsme hledali příčiny. Nalezlo se jich pět, čtyři v datové cestě a jedna ve watchdogu (ADR-221 až ADR-224):
| Příznak v X11 | Příčina v urMQTT | Změna |
|---|---|---|
| jeden tok 1,9k zpráv/s, často 0 doručeno | Ztracené probuzení: smyčka výstupu vracela seznam připravených spojení přes právě vypsaný; odběratel, jehož výstupní buffer se jednou zaplnil, už nikdy nebyl obsloužen, dokud ho nevzbudil PINGRESP | opraveno (ADR-223) |
| několik procent ztrát mění 48k na 14k zpráv/s | každý segment po ztraceném byl zahozen (příjem jen v pořadí), takže obnova stála kolo sítě na segment | fronta segmentů mimo pořadí, SACK (ADR-222) |
| strop 4,4 MB/s při 15 ms | 16bitové okno, odesílací fronta 64 KiB a pomalý start, který počítal ACK, ne bajty | škálování okna, časová razítka, počítání bajtů (ADR-222) |
| každé kolo toku, který se vejde, čeká 0 až 10 ms | dispečer spal do dalšího taktu po 10 ms (virtio-net nemá přerušení pro příjem) | dotazování NIC každých 200 us, dokud existují spojení (ADR-224) |
| 1 až 8 přerušených spojení na spuštění | watchdog započítával handleru uplynulý čas, a VPS zastavuje vCPU často a dlouho | rozpočet je čas, kdy jádro skutečně běželo (ADR-221) |
Zastavení jsou skutečná: na tomto VPS měla jádra 0 až 2 medián 36 až 80 zpožděných taktů za 5 sekund a nejhorší zastavení v okně mělo medián asi 70 ms a až 0,9 s (jádro 3, databáze a žurnál: až 1,55 s), zatímco hostitel neukazuje ukradený čas (host steal 0 us). Proto watchdog potřeboval jiné hodiny, ne větší rozpočet.
| kód X11 (7f9517e) | nyní (86a71ff) | Mosquitto | |
|---|---|---|---|
| jeden tok, QoS 0, 64 B, publikováno / doručeno za s | 1,9k / 0 | 23,7k / 12,6k | 29,3k / 29,2k |
| jeden tok, QoS 0, 1 KiB | 0 / 0 | 3,9k / 2,3k | 5,4k / 5,4k |
| 16 párů, zahlcení, QoS 0, 64 B | 24,7k / 20,0k | 43,2k / 37,4k | 31,4k / 30,7k |
| 16 párů, zahlcení, QoS 0, 1 KiB | 3,7k / 2,9k | 5,0k / 4,5k | 5,9k / 5,7k |
| ztracená spojení na spuštění (16 párů, 64 B) | 13 (7 až 16) | 0 (0 až 0) | 0 |
Watchdog: 148 přerušených spojení ve 30 zatížených spuštěních před, 0 ve 24 spuštěních po; na lokálním zařízení, které zpožďuje a ztrácí rámce, má při ztrátě 0,1% jeden tok 64 B s 14,3k zpráv/s (jádro X11) nyní 41,0k a při 0,5% z 5,0k na 17,7k.
Co tím neříkáme. urMQTT stále není rychlejší než Mosquitto na jediném toku: při 64 B publikuje asi 80% jeho rychlosti a doručuje asi 40%, protože zahazuje QoS 0, když je tok odběratele pomalejší než vydavatelův (Mosquitto bez limitu řadí do fronty). Při 1 KiB v 16párovém zahlcení je Mosquitto vpředu asi o 15%, což je v rámci šumu. Generátor byl znovu sdílená VM s 58 až 77% ukradeného času. Druhé kolo (zamykání haldy mimo datovou cestu, SACK na straně odesílání a další) je na neintegrované větvi perf-single-flow-wip; jeho čísla zatím nejsou tvrzení a neuvádíme je zde.
Slib, který jsme nedali, ale splnili: konfigurace je data
Dosud byla každá konfigurace novým sestavením: klíče a limity byly zabudované v obrazu. C1 to obrací (ADR-215 až ADR-219): jádro se staví jednou a konfigurace jsou data.
- Blob
urmqtt-cfg(C1a): verzovaný, kanonický zápis (pole podle rostoucích značek, otisk SHA-256, nepovinný oddělený podpis Ed25519), 70 polí s výchozími hodnotami a mezemi, dekodér, který odmítá vše neplatné. - Jádro ho čte jako modul Limine (C1b), před čímkoli jiným, na aarch64 i x86_64. Existuje jedno vydané jádro pro každé
ENVkromědev(ELF aarch64 prorelease,prodabenchmají stejné SHA-256; ověřeno jen na aarch64). Vydané jádro odmítne vývojovou konfiguraci a bez konfigurace napíše „refusing to serve" a nic neobsluhuje. - Nainstalovaný disk nese konfiguraci a aktualizace ji zachovává (C1c).
urmqtt-configa šablona ISO (C1d):pack,show,verify, apack --isonahradí obě kopie místa pro konfiguraci a 24 součtů modulů v hotovém ISO. Výsledek je bajt po bajtu stejný jako ISO, které sestavímkiso.sh --config. Šablona vydání v0.2.0 má 63 111 168 bajtů.- Prohlášení o důvěře (ADR-219): konfigurovaný obraz při startu vypíše otisk konfigurace (
config: fingerprint ...), podpis je nepovinný a ve výchozím stavu vypnutý. Upravené ISO nemá oficiální součet; otisk je způsob, jak vidět, co v něm je.
Builder na aldo.tech
Totéž, co dělá urmqtt-config pack --iso, dělá i webová stránka, týmž kódem: knihovna nástroje přeložená do WebAssembly (1,06 MB, bez importů a bez bloku unsafe). Stránka je na aldo.tech/cs/produkty/urmqtt-builder/ a existuje ve všech 15 jazycích webu (například hr).
- Nic neopouští prohlížeč. Certifikáty, soukromé klíče a hotové ISO zůstávají v počítači; stránka stahuje jen soubory vydání z tohoto webu, žádné sledování ani skripty třetích stran. V živém testu žádný požadavek nenesl klíčový materiál.
- Zrcadlo vydání (ADR-227): hodinu po hodině (časovač systemd, nízká priorita) kopíruje vydání z GitHubu, každé před zveřejněním ověří a stránka v prohlížeči ověří podpis
SHA256SUMSklíčem zabudovaným ve stránce, a teprve pak zapíše konfiguraci do šablony. Neúspěšné ověření stahování zablokuje. - Jednoduše ve výchozím stavu, pokročile podle potřeby (ADR-232): první obrazovka má tři kroky (kde poběží, vaše certifikáty, název obrazu). Jedno pole pro přetažení souborů roztřídí certifikát, klíč a CA podle obsahu. Pokročilé zobrazení otevře každé z 70 polí, vyhledávání, import a export TOML; obě zobrazení upravují stejný stav. Šablona (63 MB) se stahuje, zatímco uživatel vyplňuje formulář. Při živé zkoušce, s zkušebním zrcadlem a zkušebním klíčem, trvalo sestavení ISO v prohlížeči 5 až 18 s.


Emulátor, nepovinně. Vedle „Stáhnout ISO" stojí „Vyzkoušet v emulátoru": QEMU přeložené do WebAssembly spustí právě sestavený obraz v kartě prohlížeče. Nic z něj se před kliknutím nestahuje (asi 3 MB poprvé). Emulátor je jen BIOS, bez sítě (aby provoz uživatele neprošel přes nějakého prostředníka a nenarušil „nic neopouští prohlížeč"), potřebuje asi 1,5 GB paměti a počítačový prohlížeč (testováno v Chrome; Firefox a Safari ne). V něm urMQTT dosáhne URMQTT READY za asi 8 s (s přeskočeným odpočtem) a vypíše stejný otisk konfigurace jako stránka, což je důkaz, že se spustila právě ta konfigurace. Start je asi 2krát pomalejší než původní QEMU na stejném Macu (0,82 s oproti 1,9 s) a kryptografie asi 58krát pomalejší, protože hardwarové instrukce jsou emulované. Proto z emulátoru nevyvozujeme žádná tvrzení o rychlosti a stránka to říká před spuštěním. Při sestavení bylo třeba záplatovat čtyři bajty v .wasm kvůli chybě v překladu instrukce POPCNT v původním emulátoru (zatím jsme ji nenahlásili); samotný emulátor je pod GPL, s nabídkou zdrojového kódu.

Prostý MQTT, jen pro sítě, kterým věříte
Prostý MQTT (TCP 1883, bez TLS) je v IoT velmi běžný: je výchozí v Mosquittu, ve většině firmwarů pro Arduino a ESP, v Tasmotě, Zigbee2MQTT a Home Assistantu a používá se i uvnitř segmentovaných průmyslových sítí, za VPN a na soukromých LAN, i na zařízeních bez hodin (certifikáty vyžadují datum), bez dost flashe nebo procesoru pro TLS. Přes nedůvěryhodnou síť není přijatelný: kdo je na cestě, čte a mění provoz. Proto je P1 (ADR-229 až ADR-231) výslovná, omezená volba, ve výchozím stavu vypnutá, a port TLS zůstává; TLS se tím nevypíná, prostý port přichází vedle něj.
- Seznam povolených sítí je povinný (
allow_from, CIDR) a kontroluje se při SYN: mimo seznam není SYN-ACK ani stav. Celý internet (0.0.0.0/0) vyžaduje samostatnou výslovnou volbu. - Tři způsoby přihlášení: heslo (výchozí, když je zapnuto; PBKDF2-HMAC-SHA-256, sůl, 10 000 iterací, porovnání v konstantním čase, zamykání podle adresy), anonymně (jedna nakonfigurovaná identita s vlastním ACL) a podle adresy (
ip). Builder heslo hashuje v prohlížeči a v paměti drží jen součet. - Méně práv než mTLS: prostý klient nikdy nemá servisní, operátorská ani řídicí práva, nemá
$SYSani konzoli, má vlastní relace ($plain/<identita>/<id klienta>, jen čisté), vlastní limity a registr povolených ho dál zavazuje. - Co se ztrácí, je napsáno v
SECURITY.md: důvěrnost, integrita, autentičnost brokera a opakování zachyceného přihlášení. Konfigurací se to opravit nedá; tak to je, když není TLS.
Změřeno na notebooku (Apple silicon, HVF, uživatelská síť QEMU), 16 párů, 64 B, tři spuštění po 10 s: při 8000 zprávách za sekundu doručují cesta TLS i prostá všechny zprávy se stejnou latencí (p50 0,17 ms oproti 0,13 až 0,18 ms), nová spojení za sekundu: TLS asi 2600 až 2750, anonymní prostá asi 4900 až 7800, a přihlášení heslem je drahá část: jeden PBKDF2 stojí asi 2,5 ms, takže počet nových přihlášení je omezen na asi 200 na datové jádro za sekundu (změřeno 308 až 342 na dvou jádrech). Zahlcení na tomto stroji není stabilní nástroj (20k až 440k doručených za sekundu mezi spuštěními) a nevyvozujeme z něj závěr. Srovnání prosté cesty s Mosquittem na VPS ještě nebylo provedeno. Testy: skupina smoke plaintext 7 ze 7 na aarch64 HVF i na x86_64 TCG, nový fuzz cíl plain_input (2,3 milionu spuštění bez nálezů).
Chyby, které našel provoz naživo
Dvě věci neodhalily testy, ale první zapnutí na skutečném webu:
- Stránka hledala soubory vydání o jeden adresář výš, takže každé ověření podpisu odpovědělo 404 (oprava 13af301). Test používal zkušební zrcadlo na jiné cestě.
- Klávesnice emulátoru nikdy nedorazila k hostu: příčinou nebyl špatný překlad kláves, ale to, že dialog klávesy vůbec neposlouchal (oprava v ADR-232, s položkou pro start bez kláves).
Poučení
- Hledejte příčinu, neviňte prostředí. Většina slabin jediného toku a všechna přerušená spojení byly naše; hypervizor je skutečný, ale bylo třeba ho měřit, ne předpokládat.
- Srovnání s jiným produktem musí zahrnovat i ztráty. Tabulka, v níž jsou jen naše výhry, by nebyla srovnáním.
- Měřicí přístroj má meze. Sdílený generátor s 60 až 70% ukradeného času mění, co čísla znamenají; proto píšeme „dolní mez" a netvrdíme rozdíly pod 20%.
- Izolace není důkaz. SQLite v EL0 zmenšuje, co může způsobit chyba v něm, ne to, co může lhát.
- Skutečná instalace najde to, co testy nehledají: cestu, která je špatně složena, a klávesy, které nikdo neposlouchá.
Co ještě není dokázáno
- Podpisový klíč vydání je zatím zkušební klíč; v0.2.0 a v0.2.1 jsou pre-release a obrazy k nim jsou vývojové se zkušebními certifikáty (kromě šablony, která je bez konfigurace).
- 13 překladů stránky builderu (a tohoto textu) napsala AI a ověřily je testy a vzorky, ne rodilí mluvčí.
- Srovnání s Mosquittem je jedna relace, jedna cesta a jeden sdílený generátor, na sdíleném VPS; rozdíly pod 20% nejsou tvrzení.
- Prostá cesta nebyla měřena na VPS ani proti Mosquittu.
- Lighthouse na novém jednoduchém zobrazení není potvrzen (staré zobrazení: přístupnost 95 nebo více).
- Bare metal, cena workeru na x86_64 na KVM, Firefox a Safari pro emulátor: neměřeno.
Co následuje
- D1: veřejný demo broker. Emulátor z prohlížeče nemůžeme udělat veřejným: žije v kartě jednoho návštěvníka, je 20 až 100krát pomalejší než KVM, nemá příchozí síť a zmizí se zavřením karty; přesměrování internetu do cizího prohlížeče by bylo i zneužitím. Možný je skutečný demo broker: urMQTT na VPS za jménem na aldo.tech, s publikovanou zkušební CA, přísným ACL a malými limity. Zatím je to jen nápad, ne stavba, a vyžaduje výslovný souhlas s veřejným vystavením.
- Druhé kolo pro jediný tok (větev
perf-single-flow-wip): teprve až bude změřeno na VPS a sloučeno. - Prostá cesta proti Mosquittu na VPS, přihlášení a odvolání účtu pro prostý MQTT přes podepsaný řídicí kanál.
- Po vydání: bare metal (X12 až X16), IOMMU a Secure Boot, šifrování disku.
Kód je na github.com/zeljan-alduk/urMQTT a docs/PROGRESS.md zůstává jediným zdrojem pravdy o tom, co skutečně funguje, docs/PERFORMANCE.md o tom, co bylo změřeno. Pre-alfa, jako dříve; hlášení chyb a bezpečnostní hlášení podle pokynů v SECURITY.md jsou vítána.
Komentáře
Pravidla komentování