NexusFabricCereți o prezentare

Operare

Pagina asta e scrisă pentru cine ține platforma în funcțiune: ce e o instalație, ce cere o repornire, ce rămâne scris pe disc după ce un mesaj a trecut, și de unde încolo nu mergem.

O instalație se scalează pornind binarul din nou

Un motor e binarul plus directorul de stare pe care i-l dai. Al doilea motor e același binar, pornit din nou peste același director. Nu se instalează niciun orchestrator de noduri, nu se ține niciun registru de noduri, nu rulează niciun protocol de consens și niciun heartbeat: motoarele nu știu unele de altele, se coordonează prin lacăte POSIX peste directorul pe care-l împart. Unul ia lacătul de planificare, fiecare coadă capătă exact un lucrător, iar adăugările în același segment se serializează pe lacătul lui.

Forma asta cere ceva de la sistemul de fișiere, și cerința nu e o formalitate: lacăte de înregistrare fcntl adevărate — niciodată flock(), ale cărui semantici variază de la o montură de rețea la alta. Registrul și lanțul de audit se deschid deliberat cu jurnal de rollback în loc de WAL și cu sincronizare la fiecare scriere, tocmai ca să poată fi împărțite. Jurnalul L1 și baza interfeței de administrare au rămas în WAL, care are nevoie de indexul de memorie partajată al SQLite; cum există un singur director de stare și un singur indicator de cale, el trebuie să le suporte pe amândouă.

Trei lucruri se planifică în forma asta, și se spun aici, nu la trei dimineața. Preluarea între motoare nu e automată: planificatorul de fluxuri programate se alege o singură dată, la pornire, deci dacă se oprește motorul care îl ține, programările reîncep la următoarea repornire. Limitarea de debit se numără per motor, deci un plafon se dimensionează pentru motor, nu pentru instalație. Iar lacătul de lucrător e deliberat permisiv la o eroare de intrare-ieșire: preferă o livrare dublată unei cozi oprite, ceea ce rămâne în contractul de livrare cel puțin o dată. Manualul le scrie pe toate, la capitolul de operare despre rularea mai multor motoare.

Un singur binar duce tot

  • Punctele de intrare ale fluxurilor, WSDL-ul, verificarea de sănătate, lucrătorii de coadă, planificatorul de orare și interfața de administrare rulează în același proces. Interfața nu e un al doilea binar și nu are portul ei.
  • Ușa gRPC ascultă pe un port separat, în același proces, și numai dacă i se cere la pornire.
  • Un singur proprietar de runtime în proces, iar o platformă la capacitate refuză zgomotos, cu urmă în jurnal.
  • O instalație pornește știind numai manualul de operare: primul flux rulează cu poarta închisă și cu configurarea din mediu, în imaginea livrată.

Terminarea TLS nu e în proces. Se pune în față, la proxy-ul invers, iar ușa gRPC vorbește în clar în spatele lui.

Starea e un director, iar copia de siguranță e a directorului

Ce stă în directorul de stare
FișierCe ține
registry.dbRegistrul de artefacte, tenanții, cheile de API, configurarea de sistem
nexus-audit.dbLanțul de audit
nexus-log.dbJurnalul, ieșirea în bază de date
nexus-ui.dbUtilizatorii și sesiunile interfeței de administrare
logs/Jurnalul, ieșirea în fișier, câte unul pe zi
queues/Cozile, câte un set de fișiere pe pereche tenant–flux

Se salvează ca un întreg. O bază luată fără cozile ei descrie o instalație care n-a existat niciodată.

Lanțul de audit din nexus-audit.db se poate verifica oricând — nexus audit --tenant <t> --verify, sau butonul din pagina de audit. Un verdict de ruptură numește numărul de ordine al intrării atinse. Un lanț pe care nimeni nu l-a cerut nu se verifică de la sine, iar o listare nu spune nimic despre integritate. Comanda cere un tenant, deci o instalație cu mai mulți se verifică pe rând.

Nimic din directorul ăsta nu e cifrat la repaus: nici vreuna din cele patru baze, nici fișierele de coadă. Cifrarea la repaus e blocată pe o gestiune de chei pe care platforma n-o are, iar copia de siguranță e locul unde asta se simte cel mai tare — o arhivă a directorului duce cu ea tot ce e scris în clar înăuntru.

Pornirea refuză, în loc să tacă

Un flux declară ce configurare de mediu îi trebuie, iar o cheie declarată pe care instalația n-o oferă oprește pornirea, cu cheia numită. La pornire, nu la primul mesaj.

Aceeași întrebare se poate pune fără să pornească nimic: nexus config check dă același verdict ca pornirea și se încheie cu cod de eroare când lipsește o cheie. Un verdict, două momente — nu o a doua implementare care s-ar învechi tocmai în varianta pe care operatorul o rulează.

Și o scanare de pornire nu poate tăcea: un registru care nu se poate lista oprește pornirea, în loc să o lase să pară verificată.

Ce cere o repornire

  • Configurarea de mediu se ia o dată, la pornire. O schimbare de configurare cere o repornire — dacă valorile s-ar reciti pe parcurs, poarta de la pornire ar fi verificat alte valori decât cele pe care rulează platforma.
  • Adăugarea, schimbarea sau scoaterea unui orar cere o repornire. Schimbarea pașilor unui flux deja programat nu cere: ticul următor ia versiunea nouă.
  • Tabela de rutare gRPC se construiește la pornire. Un flux publicat după aceea nu e rutat până la o repornire, iar unul retras răspunde ca unul inexistent.
  • Plafoanele de debit trăiesc în memoria procesului. O repornire le golește, iar două motoare țin două contoare: o cheie configurată la *r* cereri pe secundă poate cheltui până la *N × r* peste N motoare. Plafonul se dimensionează pentru motor, nu pentru instalație.

Coada e la cel puțin o dată

Cel puțin o dată. Aia e toată garanția, și se scrie aici fiindcă restul paginii depinde de ea: cine construiește peste coadă construiește idempotența la destinație, nu o declară pe platformă. Ce oferă platforma e deduplicare declarativă, care e altceva.

  • Coada are jurnal propriu: un mesaj confirmat supraviețuiește unei căderi, iar reîncercarea programată supraviețuiește unei reporniri.
  • Ordinea se păstrează numai la prima livrare. O reîncercare intră în ordinea în care i-a venit rândul, nu în cea în care a sosit mesajul.
  • Plafonul de lucrători nu e un debit: e numărul de cozi distincte care pot fi drenate pe serverul ăsta. Se numără cozile, nu traficul.
  • După o repornire, mesajele neprelucrate ale unei cozi stau pe disc până când ceva se trimite din nou fluxului acela. Lucrătorul pornește la prima trimitere, nu la pornirea procesului.
  • O coadă drenată își recuperează octeții, iar lucrătorul continuă să consume; un mesaj aruncat din coada moartă chiar dispare de pe disc.

Ce rămâne pe disc dintr-un mesaj de coadă

Corpul unui mesaj stă în fișierul cozii ca JSON în clar, într-un cadru cu sumă de control. Suma de control e integritate, nu cifru: spune dacă octeții s-au stricat, nu îi ascunde de nimeni.

Mascarea nu ajunge acolo. Se aplică la ieșirea de jurnal, pe chei de obiect JSON, iar corpul scris pe coadă n-o vede.

Confirmarea nu șterge nimic. Un mesaj confirmat mută un indicator de octet mai departe; ce e înaintea lui rămâne pe disc, în clar. Octeții se duc într-una din două clipe: coada ajunge complet drenată și lucrătorul îi trunchiază fișierul, sau coada e abandonată și întreținerea îi șterge fișierele. Nu există limită de timp, nu există limită de dimensiune, nu există expirare. Pe o coadă cu trafic continuu, clipa aia poate să nu vină niciodată.

Și nu există ștergerea unui mesaj anume la cerere. Singura ștergere per mesaj e aruncarea unei scrisori moarte: e pe copia din coada moartă, nu pe originalul din coadă, și devine efectivă la drenajul următor.

Orare

Un flux poate purta un orar cu șase câmpuri, iar un tic lasă în jurnal aceeași pereche — primire plus verdict — ca o cerere venită pe ușa din față, pe canalul cron. Un orar care a eșuat nu e invizibil.

Și în aceeași suflare, fiindcă un operator trebuie să afle asta înainte, nu la trei dimineața: lacătul de planificare face ca un singur motor să poarte orarele, deci nimic nu se declanșează de două ori — dar alegerea se face o singură dată, la pornire. Dacă motorul care ține lacătul se oprește, orarele tac până la următoarea repornire a unui motor. Repornirea se planifică; preluare automată nu există.

O singură fereastră de retenție, și numai peste jurnal

Jurnalul are două ieșiri — rânduri într-o bază de date și fișiere pe disc. O singură fereastră de retenție le guvernează pe amândouă, într-o singură trecere, cablată în procesul care servește.

Fereastra aia nu trece dincolo de jurnal: nu atinge cozile, nu atinge registrul, nu atinge lanțul de audit și nu atinge starea de flux. Cine planifică spațiul pe disc al unei instalații planifică patru lucruri, nu unul.

Ce nu face platforma

Nu există punct /metrics și niciun export de jurnal către un colector extern — syslog, OTLP, statsd, webhook, fluentd, Kafka: sunt exact două destinații, ambele locale.