urMQTT после VPS-а: SQLite у корисничком режиму, поређење са Mosquittom, конфигурација као податак и градитељ у прегледачу
Шта се догодило после другог текста о urMQTT-у: SQLite изолован изван језгра, мерења наспрам Mosquitta на истом VPS-у (и оно што није ишло у нашу корист), пронађени узроци спорог једног тока, конфигурација као податак уз веб градитељ ISO-а и емулатор у прегледачу, необавезни обичан MQTT и шта још није доказано.

Други текст, urMQTT на правом VPS-у, завршио се списком „Шта следи”: H6 (SQLite изван језгра), H7 (безбедност времена), X11 (поређење са Mosquittom на истом VPS-у) и прво издање. Овај текст прати тај списак истим редом, а затим додаје две ствари којих на списку није било, јер су се појавиле успут: конфигурацију као податак, уз веб градитељ ISO-а, и необавезни обичан MQTT без TLS-а. Као и раније: шта смо планирали, шта смо изградили, шта нас је изненадило и шта је отворено. У овом тексту има и неколико резултата који не иду у корист urMQTT-а; оставили смо их јер су део истине.
Напомена унапред остаје иста: пројекат је пред-алфа, прво издање је само за VPS-ове са KVM-ом, а бројке мере код, тестове и одређене огледе, не зрелост у продукцији.
Како је то изгледало у времену
Од прошлог текста (31.10. у 23:00) до данас (1.11. у 20:41) у репозиторијуму је 59 нових commitова, укупно 178. Сви су настали у једном календарском дану, па важи иста опомена као раније: пројекат се гради уз Claude Code (commitови носе Co-Authored-By: Claude), план, код, тестови и ADR-ови настају у кратким затвореним петљама, а бројке испод говоре о количини кода и провера, не о томе колико је људског рада у њима.
Бројке су израчунате истом методом као у прошлом тексту: све .rs датотеке изван vendor/ и fuzz/, #[test] функције, наслови ## ADR- у docs/DECISIONS.md.
| Тренутак | Commit | Време | Rust редова | Тестова | ADR-ова |
|---|---|---|---|---|---|
| прошли текст | 794f607 | 31.10. 23:00 | 89 790 | 739 | 104 |
| H6 готов | a0423cc | 1.11. 03:59 | 95 078 | 754 | 107 |
| X11 | a29f49b | 1.11. 07:56 | 95 579 | 754 | 108 |
| веб градитељ и огледало издања | 582783f | 1.11. 16:49 | 105 645 | 848 | 122 |
| обичан MQTT, документација | 8f959f1 | 1.11. 19:05 | 112 408 | 926 | 125 |
| данас | d21e3ec | 1.11. 20:41 | 112 408 | 926 | 126 |
Радило се паралелно на више грана, па редослед commitова није стриктно редослед рада. ADR-ови су нумерисани до 232, али нумерација има празнина (део је преименован при спајању грана); бројимо наслове, не највећи број. Издања: v0.2.0 (1.11., 18:45 UTC) и v0.2.1 (1.11., 20:22 UTC), оба означена као pre-release.
H6: SQLite у корисничком режиму рада
Шта смо планирали. SQLite је 254 268 редова C-а који парсира базу, WAL и дневник са диска, а диск може бити направљен или измењен. Дотад је радио у језгру, са правима језгра. Шта смо изградили (ADR-210 до ADR-212): SQLite, његови libc делови и heap премештени су у засебан програм, dbworker, који ради на EL0 (aarch64) односно ring 3 (x86_64), у сопственом адресном простору. Језгро више не линкује SQLite. Радник са језгром разговара кроз заједнички прозор меморије и дванаест системских позива, а свака дужина и померај који се врате проверавају се пре употребе. Грешка у раднику (недопуштен приступ, недефинисана инструкција, прекорачен временски буџет од 30 s) завршава позив, радник се поново покреће и база се отвара из WAL-а као при подизању; након 5 поновних покретања у минуту похрана се проглашава неисправном, а не покреће се у недоглед.
Цена, измерена на aarch64 под HVF-ом, иста VM, језгро са уграђеним SQLite-ом наспрам језгра са радником, шест наизменичних покретања сваке:
| Мерење | Пре (у језгру) | После (радник) | Разлика |
|---|---|---|---|
| улазак и излазак из корисничког режима | - | 1,1 us по кругу | |
db-bench, 5 000 уметања у 50 трансакција, најбољи / медијан |
61,9k / 58,4k op/s | 54,7k / 53,3k op/s | -11,6% / -8,7% |
| једно уметање по трансакцији, најбољи / медијан | 2 319 / 2 148 op/s | 2 307 / 2 126 op/s | -0,5% / -1% |
| цела сигурносна копија једним позивом (23 MiB) | 514 ms | 635 ms | +23% |
Записи на диск (1 572) и flushеви (319) су идентични, како и мора бити. Није изоловано ово: језгро и даље верује ономе што радник врати, након провере облика. Компромитовани радник може да лаже (на пример да врати празну листу опозваних сертификата, а тиме их „одопозове”), али не може да чита меморију језгра, TLS кључеве ни кључеве тикета. Нема KPTI-ја, па цурења типа Spectre/Meltdown нису спречена и не тврдимо ништа о њима; PAC кључ деле EL0 и EL1. На x86_64 цену пребацивања нисмо измерили на KVM-у (под TCG-ом би број био бесмислен), а само фазирање радника и протокола није готово. Скуп тестова за похрану (79 сценарија, сваки врти SQLite у раднику) пролази на aarch64 HVF-у и x86_64 TCG-у, на aarch64 TCG-у 76 (један се прескаче јер TCG нема MSI-X).
H7 је затворен, али не онако како смо најавили. Доња граница времена (ADR-050) и SNTP са консензусом (T1) покривају обим H7; NTS (RFC 8915) остаје изван обима и записан је као преостали ризик, а не као нешто што ради. Не сматрамо да је безбедност времена „решена”, него да је њен доказиви део готов.
Нова мрежна опција. Contabo не даје DHCP прилагођеном ISO-у, па је VPS дотад тражио другу слику од лабораторијске VM. Сада network.mode = "auto" (ADR-214) прво пита DHCP, а ако закупа нема у задатом року (dhcp_timeout_s, подразумевано 15 s) или га изгуби, примењује подразумевану статичку адресу. Испробано ручно на QEMU-у (са DHCP-ом користи закуп, без мреже се након 5 s враћа на статичку адресу); smoke сценарија за то још нема.
X11: поређење са Mosquittom на истом VPS-у
Иста врста машине као у прошлом тексту (Contabo, 4 vCPU AMD EPYC, 8 GiB), оптерећење са Linux домаћина 15 ms даље, узајамни TLS 1.3 са истим сертификатима на обе стране, Mosquitto 2.0.21 на Debianu 13 без трајности. Метода и одлуке су у ADR-213, сирови подаци у docs/perf-results/. Медијан од три покретања.
| urMQTT | Mosquitto 2.0.21 | |
|---|---|---|
| руковања/s, 32 / 64 / 128 клијената (bench профил) | 169 / 278 / 413 | 82 / 140 / 184 |
| исто, са ограничењима каква се испоручују | 185 / 262 / 333 (при 128: 202 до 286 грешака, ресети руковања) | - |
| меморија по неактивној вези (20 000 веза) | 0,50 KB хипа језгра (0,66 KB при 5 000) | 26,8 KB раста процеса |
| латенција p50, QoS 0 / QoS 1, 800 порука/s, 64 B | 14,0 / 14,8 ms | 21,4 / 39,5 ms |
| QoS 1, 4 800 порука/s, испоручено/s (p50) | 4 209 (77 ms) | 4 235 (197 ms) |
| 100 претплатника, 50 порука/s: p50 | 16,6 ms | 99 ms |
| један ток, QoS 0, 64 B, објављено/s | 2 962 (0 до 7 733) | 22 749 |
Шта то значи, а шта не. Руковања су доње границе за оба сервера: генератор оптерећења је дељена VM са 60 до 70% украденог процесорског времена и при 128 нити био је близу сопственог ограничења, а urMQTT-ова радна језгра била су заузета само 12 до 13%. Однос при 128 клијената (око 2,2 пута) најсмисленији је; при 32 и 64 углавном мери чекање клијената. Бројке меморије не броје исто (хип језгра наспрам RSS-а процеса), па је „око 50 пута мање” ред величине, не прецизан однос. Латенција од 14 ms је један круг мреже (око 15 ms), дакле „нема мерљивог времена брокера”; репови (p99 око 100 до 200 ms) су код оба брокера доминирани дељеним домаћинима. Разлике испод око 20% не тврдимо.
Шта није ишло у нашу корист и остало је у табелама: један TCP ток кроз urMQTT носио је 2 до 8 пута мање него кроз Mosquitto, са променама од нуле до 7,7 хиљада порука у секунди међу покретањима, а 20 ms watchdog руковаоца прекидао је живе везе кад је хипервизор зауставио vCPU (у продукцијској поставци action = "drop", 6 до 8 веза по покретању при QoS 0 преплављивању). Обе ствари су описане у следећем одељку јер су имале узроке у нашем коду.
Зашто је један ток био спор: узроци, а не изговори
Најлакше би било рећи „дељени VPS”. Нисмо, него смо уз исти VPS и исту методу тражили узроке. Пронађено је пет узрока, четири у путу података и један у watchdogу (ADR-221 до ADR-224):
| Симптом у X11 | Узрок у urMQTT-у | Промена |
|---|---|---|
| један ток 1,9k порука/s, често 0 испоручено | Изгубљено буђење: петља излаза враћала је списак спремних веза преко управо исписаног; претплатник чији се излазни међуспремник једном напунио више никад није био опслуживан док га PINGRESP није пробудио | поправљено (ADR-223) |
| неколико процената губитка претвара 48k у 14k порука/s | сваки сегмент након изгубљеног био је одбачен (пријем само по реду), па је опоравак коштао круг мреже по сегменту | ред сегмената изван редоследа, SACK (ADR-222) |
| строп 4,4 MB/s при 15 ms | 16-битни прозор, ред слања од 64 KiB и спори старт који је бројао ACK-ове, а не бајтове | скалирање прозора, временске ознаке, бројање бајтова (ADR-222) |
| сваки круг тока који стане чека 0 до 10 ms | диспечер је спавао до следећег откуцаја од 10 ms (virtio-net нема прекид за пријем) | испитивање NIC-а сваких 200 us док постоје везе (ADR-224) |
| 1 до 8 прекинутих веза по покретању | watchdog је руковаоцу наплаћивао протекло време, а VPS зауставља vCPU-ове често и дуго | буџет је време које је језгро стварно радило (ADR-221) |
Заустављања су стварна: на овом VPS-у језгра 0 до 2 имала су медијан 36 до 80 закаснелих откуцаја по 5 секунди, а најгоре заустављање у прозору било је медијан око 70 ms и до 0,9 s (језгро 3, база и дневник: до 1,55 s), док домаћин не јавља украдено време (host steal 0 us). Зато је watchdogу требао други сат, не већи буџет.
| X11 код (7f9517e) | сада (86a71ff) | Mosquitto | |
|---|---|---|---|
| један ток, QoS 0, 64 B, објављено / испоручено у s | 1,9k / 0 | 23,7k / 12,6k | 29,3k / 29,2k |
| један ток, QoS 0, 1 KiB | 0 / 0 | 3,9k / 2,3k | 5,4k / 5,4k |
| 16 парова, преплављивање, QoS 0, 64 B | 24,7k / 20,0k | 43,2k / 37,4k | 31,4k / 30,7k |
| 16 парова, преплављивање, QoS 0, 1 KiB | 3,7k / 2,9k | 5,0k / 4,5k | 5,9k / 5,7k |
| изгубљене везе по покретању (16 парова, 64 B) | 13 (7 до 16) | 0 (0 до 0) | 0 |
Watchdog: 148 прекинутих веза у 30 оптерећених покретања пре, 0 у 24 покретања после; на локалној направи која одгађа и губи оквире, при губитку од 0,1% један ток од 64 B са 14,3k порука/s (језгро X11) сада има 41,0k, а при 0,5% са 5,0k на 17,7k.
Шта ово не каже. urMQTT и даље није бржи од Mosquitta на једном току: при 64 B објављује око 80% његове брзине, а испоручује око 40%, јер одбацује QoS 0 кад је ток претплатника спорији од издавачевог (Mosquitto без границе ставља у ред). При 1 KiB у 16-парном преплављивању Mosquitto је испред за око 15%, што је унутар шума. Генератор је поново био дељена VM са 58 до 77% украденог времена. Друга рунда (закључавање хипа изван пута података, SACK на страни слања и друго) је на неинтегрисаној грани perf-single-flow-wip; њене бројке још нису тврдње и овде их не наводимо.
Обећање које нисмо дали, али смо га испунили: конфигурација је податак
До сада је свака конфигурација била нова градња: кључеви и ограничења били су уграђени у слику. C1 то окреће (ADR-215 до ADR-219): језгро се гради једном, а конфигурација је податак.
- Блоб
urmqtt-cfg(C1a): верзионисан, канонски запис (поља по растућим ознакама, SHA-256 отисак, необавезни одвојени Ed25519 потпис), 70 поља са подразумеваним вредностима и границама, декодер који одбија све неисправно. - Језгро га чита као Limine модул (C1b), пре било чега другог, на aarch64 и x86_64. Постоји једно издато језгро за сваки
ENVосимdev(aarch64 ELF-ови заrelease,prodиbenchимају исти SHA-256; проверено само на aarch64). Издато језгро одбија развојну конфигурацију, а без конфигурације пише „refusing to serve” и не опслужује ништа. - Инсталирани диск носи конфигурацију и ажурирање је задржава (C1c).
urmqtt-configи шаблон ISO-а (C1d):pack,show,verify, аpack --isoзамењује оба примерка места за конфигурацију и 24 сажетка модула у готовом ISO-у. Резултат је бајт за бајт исти као ISO којиmkiso.sh --configгради. Шаблон издања v0.2.0 има 63 111 168 бајтова.- Изјава о поверењу (ADR-219): конфигурисана слика при подизању исписује отисак конфигурације (
config: fingerprint ...), а потпис је необавезан и искључен по подразумеваном. Измењени ISO нема службени сажетак; отисак је начин да се види шта је у њему.
Градитељ на aldo.tech
Исто што ради urmqtt-config pack --iso, ради и веб страница, истим кодом: библиотека алата преведена у WebAssembly (1,06 MB, без увоза и без unsafe блока). Страница је на aldo.tech/sr/proizvodi/urmqtt-builder/ и постоји на свих 15 језика сајта (на пример hr).
- Ништа не напушта прегледач. Сертификати, приватни кључеви и готов ISO остају на рачунару; страница скида само датотеке издања са овог сајта, нема праћења ни скрипти трећих страна. У тесту уживо ниједан захтев није носио кључни материјал.
- Огледало издања (ADR-227): сат по сат (systemd timer, ниског приоритета) копира издања са GitHuba, свако проверава пре објаве, а страница у прегледачу проверава потпис
SHA256SUMSкључем уграђеним у страницу, па тек онда уписује конфигурацију у шаблон. Неуспела провера блокира преузимање. - Једноставно по подразумеваном, напредно по потреби (ADR-232): први екран су три корака (где ради, ваши сертификати, име слике). Једно поље за превлачење датотека разврстава сертификат, кључ и CA по садржају. Напредни приказ отвара свако од 70 поља, претраживање, увоз и извоз TOML-а; оба приказа уређују исто стање. Шаблон (63 MB) скида се док корисник попуњава форму. У проби уживо, са пробним огледалом и пробним кључем, изградња ISO-а у прегледачу трајала је 5 до 18 s.


Емулатор, необавезно. Поред „Преузми ISO” стоји „Испробај у емулатору”: QEMU преведен у WebAssembly покреће управо изграђену слику у картици прегледача. Ништа од њега се не скида пре клика (око 3 MB први пут). Емулатор је BIOS-only, без мреже (да саобраћај корисника не би прошао кроз неког посредника и нарушио „ништа не напушта прегледач”), тражи око 1,5 GB меморије и рачунарски прегледач (тестиран Chrome; Firefox и Safari нису). У њему urMQTT стиже до URMQTT READY за око 8 s (са прескоченим одбројавањем) и исписује исти отисак конфигурације као страница, што је доказ да је подигнута управо та конфигурација. Подизање је око 2 пута спорије од изворног QEMU-а на истом Macu (0,82 s наспрам 1,9 s), а криптографија око 58 пута спорија јер су хардверске инструкције емулиране. Зато из емулатора не извлачимо никакве тврдње о брзини, а страница то каже пре покретања. При изградњи је требало закрпити четири бајта у .wasm-у због грешке у преводу инструкције POPCNT у изворном емулатору (још је нисмо пријавили); сам емулатор је под GPL-ом, уз понуду изворног кода.

Обичан MQTT, само за мреже којима верујете
Обичан MQTT (TCP 1883, без TLS-а) врло је чест у IoT-у: подразумеван је у Mosquittu, у већини firmwarea за Arduino и ESP, у Tasmoti, Zigbee2MQTT-у и Home Assistantu, а користи се и унутар сегментираних индустријских мрежа, иза VPN-а и на приватним LAN-овима, па и на уређајима без сата (сертификати траже датум), без довољно flasha или процесора за TLS. Преко непоуздане мреже није прихватљив: ко је на путу, чита и мења саобраћај. Зато је P1 (ADR-229 до ADR-231) изричит, ограничен избор, искључен по подразумеваном, а TLS порт остаје; TLS се овим не искључује, обичан порт долази уз њега.
- Списак дозвољених мрежа је обавезан (
allow_from, CIDR-ови) и проверава се при SYN-у: изван списка нема SYN-ACK-а ни стања. Цео интернет (0.0.0.0/0) тражи засебну изричиту опцију. - Три начина пријаве: лозинка (подразумевано кад је укључено; PBKDF2-HMAC-SHA-256, со, 10 000 итерација, поређење у сталном времену, закључавање по адреси), анонимно (један конфигурисан идентитет са сопственим ACL-ом) и по адреси (
ip). Градитељ лозинку распршује у прегледачу и у меморији држи само сажетак. - Мање права него mTLS: обичан клијент никад нема сервисна, оператерска ни управљачка права, нема
$SYSни конзоле, има сопствене сесије ($plain/<идентитет>/<id клијента>, само чисте), сопствена ограничења и регистар дозвољених га и даље обавезује. - Шта се губи, пише у
SECURITY.md: поверљивост, целовитост, аутентичност брокера и понављање снимљене пријаве. То се конфигурацијом не може поправити; тако је кад нема TLS-а.
Измерено на лаптопу (Apple silicon, HVF, QEMU корисничка мрежа), 16 парова, 64 B, три покретања од 10 s: при 8 000 порука у секунди TLS и обичан пут испоручују све поруке са истом латенцијом (p50 0,17 ms наспрам 0,13 до 0,18 ms), нове везе у секунди: TLS око 2 600 до 2 750, анонимни обичан око 4 900 до 7 800, а пријава лозинком је скупи део: један PBKDF2 стоји око 2,5 ms, па се број нових пријава ограничава на око 200 по језгру података у секунди (измерено 308 до 342 на два језгра). Преплављивање на тој машини није стабилан инструмент (20k до 440k испоручених у секунди међу покретањима) и из њега не извлачимо закључак. Поређење обичног пута са Mosquittom на VPS-у још није направљено. Тестови: smoke група plaintext 7 од 7 на aarch64 HVF-у и на x86_64 TCG-у, нови fuzz циљ plain_input (2,3 милиона покретања без налаза).
Грешке које је пронашао рад уживо
Две ствари нису ухватили тестови, него прво укључење на правом сајту:
- Страница је датотеке издања тражила један директоријум превисоко, па је свака провера потписа одговорила 404 (исправак 13af301). Тест је користио пробно огледало на другој путањи.
- Тастатура емулатора никад није стигла до госта: узрок није био лош превод тастера, него то што дијалог уопште није слушао тастере (исправак у ADR-232, уз унос за подизање без тастера).
Лекције
- Тражите узрок, не окривљујте окружење. Већина слабости једног тока и све прекинуте везе биле су наше; хипервизор је стваран, али га је требало мерити, не претпостављати.
- Поређење са другим производом мора укључити и губитке. Табела у којој су само наше победе не би била поређење.
- Мерни инструмент има границе. Дељени генератор са 60 до 70% украденог времена мења шта бројке значе; зато пишемо „доња граница” и не тврдимо разлике испод 20%.
- Изолација није доказ. SQLite у EL0 смањује шта грешка у њему може, не оно што може да лаже.
- Стварна инсталација налази оно што тестови не траже: путању која је погрешно сложена и тастере које нико не слуша.
Шта још није доказано
- Потписни кључ издања за сада је пробни кључ; v0.2.0 и v0.2.1 су pre-release, а слике уз њих развојне уз пробне сертификате (осим шаблона, који је без конфигурације).
- 13 превода странице градитеља (и овог текста) написао је AI и проверили су их тестови и узорци, не изворни говорници.
- Поређење са Mosquittom је једна сесија, један пут и један дељени генератор, на дељеном VPS-у; разлике испод 20% нису тврдње.
- Обичан пут није мерен на VPS-у ни против Mosquitta.
- Lighthouse на новом једноставном приказу није потврђен (стари приказ: приступачност 95 или више).
- Bare metal, x86_64 цена радника на KVM-у, Firefox и Safari за емулатор: није мерено.
Шта следи
- D1: јавни демо брокер. Емулатор из прегледача не можемо учинити јавним: живи у картици једног посетиоца, 20 до 100 пута је спорији од KVM-а, нема долазне мреже и нестаје са затварањем картице; преусмеравање интернета у туђи прегледач било би и злоупотреба. Могућ је прави демо брокер: urMQTT на VPS-у иза имена на aldo.tech, са објављеним пробним CA-ом, строгим ACL-ом и малим ограничењима. То је засад само идеја, не градња, и треба изричит пристанак за јавно излагање.
- Друга рунда за један ток (грана
perf-single-flow-wip): тек кад је измерена на VPS-у и спојена. - Обичан пут против Mosquitta на VPS-у, пријава и опозив налога за обичан MQTT преко потписаног управљачког канала.
- После издања: bare metal (X12 до X16), IOMMU и Secure Boot, шифровање диска.
Код је на github.com/zeljan-alduk/urMQTT, а docs/PROGRESS.md и даље је једини извор истине о томе шта стварно ради, docs/PERFORMANCE.md о томе шта је измерено. Пред-алфа, као и раније; пријаве грешака и безбедносне пријаве по упутама у SECURITY.md добродошле су.
Коментари
Правила коментарисања