Angular face retry la server daca un request dureaza prea mult

Facand referire la aceasta postare de pe stackoverflow, pentru ca nu vreau sa lungesc acest topic si sa nu se mai inteleaga nimic din el:

Am un API in Scala si voiam sa implementez un GenericRepository. Pentru asta, citesc datele din baza de date ca o lista de obiecte (List[Object]), fiecare obiect insemnand o linie din tabel (o alta lista de obiecte)) (primul for). Asta se intampla in repoul generic, dupa care lista este trimita in repo-ul entitatii, unde ii cunosc tipul si mapez fiecare obiect in tipul respectiv (al 2lea for). Deci, pe langa citirea din resultSet al executarii queryului, pentru a folosi un generic repo o sa mai adaug 2 for’uri pentru fiecare entitate. Asta m-am gandit ca va creste timpul de executie in cazul entitatilor unde returnez un numar foarte mare de linii (sute de mii) si m-am apucat de testat:
Initial am testat fara folosirea acelui generic repo, pur si implu am vrut sa vad in cat timp incarc o lista de o suta de mii de iteratii, obiecte cu doar 2 parametrii (id si nume)

  def test(): List[(Int, String)] = {
    var roleList: List[(Int, String)] = List.empty
    for (i <- 0 to 100000) {
      if (i % 10000 == 0) println(i)
      val row: (Int, String) = (i, getName(i))
      roleList = roleList :+ row
    }
    roleList
  }

Pentru 10k iteratii dureaza in jur de 10 secunde (enorm). In timpul asta, Angularul asteapta raspunsul. Dupa aproape 60 secunde de asteptare, Angularul retrimite requestul catre server (al 2lea) si il ignora total pe primul. Astfel, in server cand primul request este undeva la 70% executie, incepe un al 2lea. Primul request se executa, api-ul trimite raspunsul la client, dar acesta este ignorat pentru ca Angularul asteapta raspunsul ultimului request trimit (cel de-al 2lea). Cand al 2lea request ajunge la 60 70%, angular trimite un al 3lea request. Asta se intampla de 4 ori, am timeout setat la 120 secunde, si intre timp Angularul primeste eroare connection reset (atentie, nu timeout)

Trebuia sa fie doar un test banal, care s-a complicat surprinzator :)) Ce se intampla in cazul in care chiar vreau sa returnez cateva sute de mii de linii sau doar sa le procesez in backend si sa trimit rezultatul in frontend? Se poate sa fie si din cauza pc-ului, itereaza 10k linii in 10 secunde, CPU este 100% iar Memory usage undeva pe la 80%.

Initial incercam sa testez cu cat ar creste timpul acele transformari de obiecte in tip si for-uri ale generic repo-ului fata de un repository normal. Ce ma uimeste totusi e ca … aveam impresia ca e o problema de backend, cand se pare ca e de front.

*N-am mai apucat sa vad cum se comporta si cu transformarea dintr-o lista de obiecte in lista de (Int, String) pentru ca e clar ca va merge mult mai prost decat in primul caz :crazy_face:

Gresesc eu ceva, pe undeva sigur :))) daca setez retry(0) in serviciul de angular, conexiunea se inchide dupa aproape un minut, cand primul request este in executie. Doar ca nu vreau sa pun in fiecare serviciu retry(0) acolo unde stiu ca backendul are nevoie de mai mult timp pentru intoarcerea rezultatului. Si asta, oricum n-ar rezolva problema, ca arunca eroare.

Mărește timeout-ul atît la serverul web cît și la clientul http din Angular…

Also, nu văd rostul la a aduce atîtea înregistrări la client. Vezi poate poți să ții datele într-un cache sau faci o pre-procesare a datelor.

In primul rand nu e normal sa ai un request care sa dureze mai mult de 30-60 secunde cat e setat un timeout, by default. Nu e ok nici din punct de vedere al UX-ului ca utilizatorul sa nu aiba un feedback, pe o perioada asa indelungata, despre ce se intampla cu request-ul lui.

Datele ar trebui sa fie paginate. In felul asta ii poti da un feedback constant utilizatorului referitor la progres si la status. Facand paginare mai poti si paraleliza procesul, facundu-l si mai performant. Trecand peste asta, daca vrei ca backend-ul sa fie cel care instiinteaza frontend-ul cand a terminat de executat un anumit proces, atunci trebuie sa folosesti WebSockets, unde este deschisa in permanenta o conexiune intre server si client, iar fiecare dintre parti poate trimite un mesaj catre cealalta, la orice moment dat, cea din urma procesand request-ul instantaneu.

De obicei, procesele care dureaza mult, sunt rulate in background, pe server, printr-un cronjob, cel mai adesea. In momentul in care vine request-ul de pe frontend, ii raspunzi cu datele de la ultima procesare a job-ului, fara a-l pune sa astepte efectiv procesarea.

3 Likes

Ar trebui sa generezi un stream response nu sa procesezi totul pe server si pe urma sa returnezi datele. Cum generezi ceva date faci push cu ele la client, pe masura ce se umple buffer-ul 1-4k se pushuie mai departe si asa vei avea cate un pachet de date la cateva secunde care o sa-ti mentina conexiunea deschisa.
Vezi json stream response sau foloseste csv daca vrei doar sa descarci datele.

Folosesc websockets doar pentru o anumita parte a aplicatiei, acolo unde datele trebuiesc sa fie realtime si sunt sharuite intre mai multi utilizatori, ce le pot modifica.

Ma refeream strict la acel test pentru ca nu mi s-a parut normal ce se intampla. Totusi, am inteles ca e un test exagerat, care intr-o aplicatie in productie nu se va gasi vreodata. Astfel de procesari se fac fie cu joburi, fie cum ai spus voi cu websockets si nu cu http requesturi, cum am incercat eu acum, pentru ca bubuie conexiunea. Da, si pentru user ar fi enervant ca singurul element de pe pagina sa fie un loading timp de 2 minute :sneezing_face:

Din ce am observat si websocketurile au o limita maxima a datelor (buffer limit). Se poate modifica din configul aplicatiei, la fel ca timeoutul, dar tot am fost obligat sa folosesc paginatie chiar si acolo

Lasand problema conexiunii deoparte, mai exista una, e anormal de mult sa itereze 10k numere in 10 secunde. @Cosmin_Popescu mi-a dat un hint, si anume ca folosesc Tuple si ca acestea ar consuma destule resurse. Am descoperit de curand Tuple (Int, String) si le folosesc doar acolo unde cred ca nu este necesara o clasa custom. Doar ca nu m-am gandit la performanta pana acum. Am fost surprins ca exista si in C#, nu doar in Java/Scala. Doh, cate mai am de invatat :))) e prea scurta ziua :crazy_face: :unamused: