Optimizare cozi, best practice

Salut,
Am un app in NodeJs care pentru a scala am folosit cozi cu bullMq.
Appul nu e nimic deosebit. Face niste requesturi REST, scrie in db, calculeaza ceva pe baza datelor venite prin REST si mai face alte requesturi.
Pentru cei care nu stiu fiecare coada are un nume unic in care se pun joburi care la randul lor pot avea nume.
Fiecare coada poate fi ascultata de unu sau mai multi workeri. Fiecare worker poate executa in paralel mai multe joburi pe coadă.
Problemele intampinate sunt:

  • loadul pe cozi este asimetric, adică in unele incep sa se stranga mai multe joburi care stau sa fie procesate si numarul creste constant. Trebuie sa pun cresc manual numarul de threaduri ca sa scada spere 0 numarul de joburi , apoi se acumuleaza in alte cozi si maresc pe alta coada si micsorez pe o alta. Practic distribui workerii manual.
  • aplicația este deservește mai multi clienți si requesturile au limita de x per minute. Am creat cate o coada cu nume separat per client am un worker cu un thread. Este ok abordarea asta dar daca workerul este offline practic aplicația freezuieste. E o solitie mai buna?
  • nodeJs are limitari de ram ai cpu per instanță. Acum este un porces care are toti workerii. Care e cea mai buna strategie sa fie appul scalabil?
    Mulțumesc

Nu am lucrat cu bullMQ, dar cu siguranta nu ar trebui sa faci aceasta distribuire manuala.

Cred ca in cazul tau, nu ar trebui sa ai mai mult de 2-3 queues.
Sa zicem ca ne rezumam la 2.
Pentru fiecare din astea 2 queues iti setezi un numar de workers in dependenta de cum stii/crezi tu ca e load-ul. Spre ex: pentru firstQueue setezi un numar de 2 workers, pentru secondQueue setezi un numar de 4 workers. Iar workerii ar trebui sa preia job-urile dupa round-robin, adica sa le revina la fiecare egal repartizat, pe fiecare queue in parte. Asta ar fi varianta on-premise, cu minim de efort.

Daca vrei sa mergi si mai departe, atunci poti merge pe Cloud, unde sa ai un un sistem de auto-scaling (gen Lambda) care faci increase/decrease la workers in functie de load-ul pe care-l ai.


Nu cred ca e ok sa creezi cate un queue pentru fiecare client in parte. Poti identifica/targeta un client anume, trimitand un user_id in payload. Atat. Nu trebuie sa ai queue-uri diferite.

Queue-urile ar trebuie create in dependenta de natura/job-ul pe care-l rezolva, iar aici poti sa aduci o oarecare dinamicitate folosindu-te de payload-ul job-ului.


Worker-ul ar trebui sa fie un app separat. Sau cel putin sa ruleze ca un proces separat. Cel mai bine ar fi: server separat, machine (server fizic) separat, sau chiar zona geografica separata.

Zic asta pentru asta e ideea la queues/workers: sa faci dispersie la load/failures. Daca ceva iti crapa sau consuma prea multe resurse, nu vrei sa afectezi tot sistemul, ci doar o bucatica cat mai mica

1 Like

Pentru a scala ce mai exact? Ce fel de cereri?

Problema ta e ca nu folosesti kubernetes sau un environment serverless.

Ai putea seta environment-ul de kubernetes sa isi mai dea un pod/worker automat daca queue-ul creste mai mult de N si sa scada daca scade backlog-ul. Dar trebuie sa ai grija si la costuri. Plus, nu folosi thread-uri cu node, normal ca e asimetric, e putin anti-pattern sa rulezi mai multe lucruri pe un singur nod. Un runner de node - un singur task si dupa trebuie sa fie oprit, node nu e nici destul de stabil sa ruleze mult timp. (fara sa fii foarte atent la cod) Stiu ca acum poti folosi workers in node, dar nu cred ca e stabil.

Daca folosesti serverless, setezi un event din queue, cand intra ceva, automat rulezi pe un worker nou la infinit, deci queue-ul mereu va fi gol.

Dar ai alta problema, trebuie sa tii undeva ce a fost procesat si ce nu ca sa stii sa nu rulezi din nou acelasi task din queue altfel poti sa faci acelasi lucru cu 10 runneri diferiti care pica exact dupa ce au procesat ce trebuia dar nu au actualizat queue-ul. E.g. trimiti acelasi email de 10000 de ori fiindca nu stie sistemul ca s-a procesat task-ul.

Solutii elegante ar putea fi AWS Fargate, fargate practic scaleaza nelimitat, poti sa ai instant 1 milion de instante de node cu resurse nelimitate timp de 20 de minute. (E serverless, dar ai limita de timp la cat poate rula o functie si cu cata memorie)
Alternativa la fargate poate fi https://fly.io, Heroku, Google Cloud Run. Cu platformele de genul platesti doar daca aplicatia ta e folosita.

Self hosted vezi Dokku - The smallest PaaS implementation you’ve ever seen, Home | OpenFaaS - Serverless Functions Made Simple, Nuclio: Serverless Platform for Automated Data Science, Home - Knative

EDIT: Nu m-am concentrat pe bullmq, problema ta e pe setari undeva :slight_smile:

@isti37 Multumesc pt raspuns. Problema mea cea mai mare acum nu este ssalarea efectiv cat concurenta.

  • Nu pot avea mai mult de 1 producer si daca ala pica practic nu pot sa am HA.
  • Nu pot avea mai mult de 1 consumer pe anumite cozi care proceseaza api-uri pt ca am limitari.

Nu chiar instant ca dureaza ceva pana face pull la docker image si pana face build si run la container. Mie imi dureaza 30 secunde, in medie, pe un proiect real (python) .

Lambda, e mult mai instant decat Fargate, doar ca are limita de 15 min ca si execution timeout.


Heroku / fly.io - ambele din ce stiu eu, sunt proiectate pentru web servers, nu pentru workers. Iar cand zic web servers, ma refer practic la ceva care e pornit si sta in starea asta.

Un worker, pe de alta parte, se porneste/opreste de nenumarate ori. Ori de cate ori un vine un event si este procesat.


De la bullmq care, probabil e rulat pe un singur server, la Kubernetes e cale launga. E mult prea overkill pentru cazul de fata.

Solutia in cazul tau, intr-un singur cuvant ar fi - Cloud. Daca vrei HA si esti un developer, care nu este la nivelul avansat de DevOps/SysAdmin in care sa-si configureze singur o infrastructura cu HA, clar trebuie sa mergi pe Cloud. Acolo ai deja tool-urile pregatite.

Da, ai dreptate nu e instant, ia cam 30 de secunde si la mine, dar cred ca am citit undeva ca AWS are ceva solutie la asta.

Problema nu este ce soluție de cloud sa folosesc si ceva îmbunătățiri la arhitectura de app.

Mie mi se pre ca va complicati foarte mult. Din ce inteleg eu tu ai un numar variabil de cozi alimentate cu un numar variabil de elemente. Deci oricum ai lua-o, tu ai o problema oarecum simpla de concurenta in procesarea acelor cozi, dar, din cate vad eu, tratata gresit din punct de vedere al abordarii.

In primul rand eu nu as folosi fire de executie (threads are expensive) si in al doilea rand nu as folosi Node pentru asa ceva. As aborda povestea asta in Go folosind concurenta nativa pentru ca goroutinele sunt ieftine si pentru partea de REST as folosi un router cu middleware ca sa fac limitari si nu numai (Gorilla Mux?). Nu am toate datele problemei tale dar din puntul meu de vedere, in mare, cam asa ar putea sta treaba mult mai sanatoasa si mai controlabila.

Pe de alta parte nu cred ca e o buna practica ca orice problema marunta sa o duci intr-un serviciu specializat din cloud sau sa o implementezi intr-o infrastructura de cloud specializata pentru ca asta pe termen lung te va duce intro zona de vendor lock destul de dezavantajoasa pentru tine cat si pentru produsul pe care il creezi. Investitia in cloud sau tehnologii dependente de fel si fel de vendori este bine sa o mentii la nivel de infrastructura si nu de functionalitate locala.

Nu pot avea mai mult de 1 producer si daca ala pica practic nu pot sa am HA.

Legat de HA, cum s-a mai spus e overkill. Foarte multa lume merge in Kubernetes fara sa isi puna prea mult problema daca serviciul creat este el in sine HA, adica este suficient de bine scris sa nu crape? De multe ori serviciul scris bine si pus in systemd cu autorestart on fail este mai mult decat suficient. Daca nu e scris cu idea de HA in minte de la inceput el o sa crape si in Kubernetes si in orice infrastructura de cloud vrei tu.

Nu pot avea mai mult de 1 consumer pe anumite cozi care proceseaza api-uri pt ca am limitari.

O coada prin definitie nu poate fi procesata in paralel dar poate fi procesata serial/concurent. De exemplu in Go poti sa extragi elementele foarte repede si le arunci in goroutine care apoi sa faca procesarea lenta a informatiei.

1 Like

Legat de bullmq problemele pe care le-ai mentionat se rezolva cu rate limiting. Are ceva rate limiter destul de inteligent.

Rate limiting - BullMQ

Pentru go ceva similar cu bullmq ar fi NATS.io