Katalog srovnany s tim, co opravdu bezi na services.csbot.cz/apps: trinact sluzeb dostalo pristupove udaje a levne cteci overeni, opravena appId, ktera nikam nevedla (ppl, microsoft365, transcription), a GA4, Search Console, Google Ads i Sklik ted stoji na aplikaci analytics, kazda s vlastnimi udaji. Nove sluzby SAP Business One, Google Workspace a Meta Ads. K tomu 23 skriptu, ktere s nimi opravdu neco delaji. OpenAI jako prvni sluzba, ktera nebezi u nas: Service.baseUrl s absolutni adresou, prepis pres <SLUZBA>_BASE_URL nebo adresu u konektoru, predpona hlavicky u pole udaju (uzivatel vlepi holy klic, Bearer dopise runtime). Dotaz na model, nahrani souboru, otazka nad souborem, prepis zvuku. Skript umi odeslat soubor pres ctx.http.postForm (multipart, obsah Base64). Sluzba E-mail pres SMTP. Neni to skript, ale vnitrni krok - SMTP neni HTTP. Konektor nese schranku firmy, krok ma HTML telo, ve kterem se dosazene hodnoty escapuji (znacky autora sablony jsou zamer, ostre zavorky od zakaznika ne). Overeni konektoru se prihlasi na server a nic neodesle. Helpdesk: Ticket.helpdeskSourceId drzi firmu, ktera pozadavek poslala, vlastnikem zustava ta, ktera ho resi - jinak by ho resitel nemel ve sve fronte. Komu pozadavek pripadne, urcuje Tenant.helpdeskProviderId. Zadavatel vidi jen svoje pozadavky a smi k nim pripsat komentar. Opravy v portalu: - hlasky o ulozisti a odchozi IP vidi jen spravce platformy - typ ticketu se v automatizaci vybira ze seznamu firmy, nebo dosadi z dat - stav ticketu je otevreny naseptavac, ne ciselnik - ticket jde zalozit rucne, zakaznik u nej neni povinny - kanal se prejmenoval a parametry u webhooku jsou oznacene jako nepovinne - srovnane markdown tabulky v cele dokumentaci Co z teto davky jeste neni: prepinac firmy je porad jen stav uvnitr stranky Prehled, takze se prepnuti neprojevi v Lidech ani jinde. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.4 KiB
Kapacita: 200 firem, 10 automatizací, 15 lidí na firmu
Otázka zněla: zvládne to současný stav s Postgresem, a co to bude chtít za server. Odpověď je změřená, ne odhadnutá.
Krátce: v současném stavu ne. Runtime běží, ale datová vrstva je psaná na stovky až tisíce ticketů, ne na miliony. Co konkrétně to zastaví a v jakém pořadí to opravit, je níž.
Co se měřilo
Jeden proces, režim souboru, tickety se posílaly přes příjem událostí. Měřeno na vývojovém stroji, tedy horní hranice latence, ne serveru.
| Ticketů | Výpis seznamu | Statistiky řešitelů | Soubor |
|---|---|---|---|
| 103 | 1,9 ms | 1,6 ms | 153 kB |
| 503 | 6,1 ms | 12,1 ms | 729 kB |
| 1 003 | 14,2 ms | 2,7 ms | 1,4 MB |
| 2 003 | 18,8 ms | 21,1 ms | 2,9 MB |
| 5 003 | 52,9 ms | 1,7 ms | 7,2 MB |
Příjem událostí: 130 až 190 událostí za sekundu včetně celého kola HTTP, uložení a zápisu do logu.
Z toho plyne:
- jeden ticket = 1,4 kB v uložišti,
- výpis roste lineárně, zhruba 10 mikrosekund na ticket, protože se filtruje pole v paměti,
- v režimu souboru se při každé změně přepisuje celý soubor. Při pěti tisících ticketech je to 7 MB zápisu kvůli jednomu komentáři. S Postgresem to odpadá, tam je to jeden řádek.
Kolik toho bude
200 firem, 10 automatizací každá, 15 lidí. Odhad provozu:
| Veličina | Výpočet | Za den | Za měsíc |
|---|---|---|---|
| Běhy automatizací | 2 000 automatizací, 100 běhů denně | 200 000 | 6 mil. |
| Kroky | 3 kroky na běh | 600 000 | 18 mil. |
| Tickety | 200 firem, 200 denně | 40 000 | 1,2 mil. |
| Řádky logu | 5 na ticket plus kroky | ~800 000 | 24 mil. |
Špička není průměr. 7 kroků za sekundu v průměru znamená ve špičce klidně 100 za sekundu, protože e-shopy neposílají objednávky rovnoměrně.
Co to v současném stavu zastaví
1. Všechna data jsou v paměti procesu
withMirror drží tickety, automatizace a incidenty v poli a při startu je
všechny načte. Při 1,2 milionu ticketů měsíčně je to 1,7 GB jen na tickety,
a to bez logu. Aplikace se nenastartuje.
Opravit se musí tak, že se tickety čtou dotazem s indexem a stránkováním,
ne z pole. withMirror má zůstat na tom, co je malé a čte se pořád (rozložení
dashboardu), ne na provozních datech.
2. Výpis prochází všechno
listTickets filtruje pole přes všechny firmy. Změřeno 10 mikrosekund na
ticket, takže při milionu ticketů je jeden výpis 10 sekund. Musí to být
WHERE tenant_id = ... AND status = ... ORDER BY updated_at LIMIT 50 nad
indexem, tedy jednotky milisekund bez ohledu na objem.
3. Log je uvnitř ticketu
Ticket si nese trace i events jako součást jednoho záznamu. Každý řádek
logu tím přepisuje celý ticket i s celým dosavadním logem. Po padesáti krocích
je to padesát zápisů, každý delší než ten předchozí.
Log patří do vlastní tabulky s cizím klíčem na ticket a s retencí. Při 24 milionech řádků měsíčně a plné odpovědi služby u každého je to řádově stovky GB za měsíc, takže retence není detail, ale podmínka provozu.
4. Runtime běží v požadavku
runFlow vykoná strom rovnou v HTTP požadavku. U ruční akce je to správně,
u webhooku ne:
- odesílatel čeká, než doběhnou všechny kroky,
- při pádu procesu se rozdělaný běh ztratí, protože není kde by byl zapsaný,
- není retry, není omezení souběhu na jednu službu, nic to nebrzdí.
5. Statistiky se počítají celé
getAgentStats projde všechny tickety firmy při každém zobrazení widgetu.
Při desítkách tisíc na firmu to jde, při milionu ne. Patří to do agregace
počítané dávkově, nebo aspoň do dotazu s GROUP BY nad indexem.
6. Jedna instance
Živý stream (SSE), mezipaměť widgetů nad konektory i kopie konfigurace
v paměti jsou vázané na proces. Při dvou instancích za load balancerem se
rozejdou. Řeší to LISTEN/NOTIFY na obnovu kopií a sdílený kanál na stream.
Konektory nejsou v naší moci
Za rychlost a výsledek cizí služby neručíme, a proto se s tím musí počítat v návrhu, ne v provozu:
| Riziko | Co s tím |
|---|---|
| Služba odpovídá pomalu | Timeout na krok, ne na celý běh. Běh se uspí a pokračuje. |
| Služba je chvíli mimo | Opakování s rostoucí prodlevou, ne hned a ne donekonečna. |
| Služba je mimo dlouho | Vypnout ji po sérii chyb a nezkoušet každý běh znovu, ať netrpí ostatní. |
| Služba má limit volání | Strop souběžných volání na dvojici firma a služba, ne globálně. |
| Služba odpoví dvakrát jinak | Klíč proti dvojímu provedení u kroku, aby se nevystavila druhá faktura. |
| Služba je pomalá jen pro jednu firmu | Fronta po firmách, aby jedna firma nezablokovala ostatní. |
Timeout a rozlišení "zkusit znovu" a "marné" už v scripts/http.ts je,
klíč proti dvojímu provedení taky. Chybí to, co je nad tím: fronta, opakování
a vypínání služby po sérii chyb.
Co je potřeba udělat, v pořadí
- Tickety, běhy a log do Postgresu jako tabulky, ne jako JSON v paměti.
Indexy na
(tenant_id, updated_at),(tenant_id, external_id)unikátní,(tenant_id, assignee_id, status). Stránkování na všech výpisech. - Log a události zvlášť od ticketu, s retencí. Bez toho to zaroste.
- Fronta běhů. Tabulka
run_queue, výběr přesSELECT ... FOR UPDATE SKIP LOCKED, worker jako druhý proces téhož obrazu. Webhook jen zapíše událost a odpoví, běh se stane na pozadí. - Opakování a vypínání služby kolem kroku.
- Agregace statistik dávkově, ne při každém zobrazení.
LISTEN/NOTIFYna obnovu kopií v paměti a sdílený kanál na živý stream.
Body 1 až 3 jsou podmínka, aby to vůbec šlo pustit. Zbytek je o tom, aby to bylo použitelné.
Kolik serveru to bude chtít
Po těch úpravách, pro zadaných 200 firem:
| Část | Kolik | Proč |
|---|---|---|
| Web a API | 2 instance, 1 vCPU a 1 GB každá | Požadavky jsou krátké, jde hlavně o dostupnost při restartu. |
| Worker běhů | 2 instance, 1 vCPU a 512 MB každá | Kroky čekají na cizí službu, procesor se skoro nepoužije. Jeden proces zvládne stovky souběžných kroků. |
| Postgres | 4 vCPU, 8 GB RAM, 200 GB disku | 10 až 20 zápisů za sekundu v průměru je málo, disk sežere log. |
| Celkem | ~8 vCPU, ~11 GB RAM |
Kritické číslo není procesor, ale disk pod databází a retence logu. S plnou odpovědí služby u každého kroku je to zhruba 20 GB měsíčně na každých 20 milionů řádků, takže 200 GB je půl roku provozu. Buď se odpovědi po měsíci zahazují a nechá se jen shrnutí, nebo se počítá s tím, že disk poroste.
Škálování je vodorovné: víc událostí znamená víc workerů, ne větší stroj. Jediné, co se škáluje svisle, je databáze, a ta má u tohoto objemu ještě velkou rezervu.
Co funguje už teď
Aby to nevypadalo hůř, než to je: runtime běží. Webhook vykoná strom, akce na ticketu vykoná strom, kroky si předávají výstupy, podmínka větví a celý průběh se zapíše do logu ticketu i s tím, co která služba vrátila. Na jednu firmu s desítkami ticketů denně je současný stav použitelný. Na 200 firem ne.