06 noiembrie 2025

Training Java performance - Ziua 3 (microservicii)

NFR = non functional requirements

reteaua Google - sa nu depinda de alte cabluri; 6 x 9 availability pt Google Login

Sistem neperformant:
Throttling: ex. rate limiting
Queuing
Auto-scaling

Startup time: rau pe Java > mai rau Spring > si mai rau EJB

Conexiune persistenta Browser-BE: websocket/grpc/http2; pt multe request-uri/zi de la putini useri

Disintegrator - spargere cod mare in bucati mici
avantaje: decuplare, refolosire, izolare bug, versiune java la zi (CVE, Spring), testabil mai usor (pe bucati), scaleaza independent, deploy independent (faster cycle time)

Spring AI, Spring MCP

Integrator: motive sa nu spargi codul
latenta, cuplare stransa, microserviciile sunt scumpe; consistenta datelor (ACID in loc de eventual consistency)

https://www.infoq.com/news/2023/03/stop-cloud-zombies-qcon/

enum in Dtos pt REST compatibility: cand modifici/scoti din enum strici; daca faci rename pe un camp din Dto; camp din required in optional;

test care verifica ca nu ai facut breaking change pe contract;

subjson in json "metadata" pt alte adaugiri

Clientii v1 identificati prin header-ele Authorization, X-Api-Key, X-Client-Id, Traceparent (incl timp)

OpenTelemetry Java Agent- pus in JVM pt tracing (userv)

swagger-ts-generator

isaqb.org - certificare arhitect

Get id repetat - eroare 429 Too Many Requests -> use batch API

Request multiplexing - astepti sa vina mai multe req, le pui in asteptare (sincron) ~ batching

Netflix: grpc + graphQL

CQRS - segregare responsabilitati command / query

POST/PUT return data?

Caz RM creare programare in 2 pasi -> ret. patientId -> GET alte patientId -> afli date despre alti pacienti

202 Accepted (dupa validare request)
200 OK [Retry-after] - prea lung, nu e gata, dar totul e ok, ruleaza async; clientul face polling
200 OK {status: DONE}
302 Redirect - pt rezultate mari

SAU: serverul creeaza un topic la care clientul se aboneaza

SAU: clientul primeste un HTTP callback (webhook) pe care sa faca polling

SAU: websocket

Agregator - face batching

Proxy - ruteaza orbeste
API proxy - la intrare in system
Service proxy - la iesire, spre API externe

Gateway: caching, compresie date, rutare requests, load balancing, service registry, http access log, metrici, tracing,
agrega date din mai multe API-uri == NOK ca implica business logic in Gw
autentifica request-uri (cu ajutorul unui serviciu), fara autorizare
rate limiting

Connect timeout < Response timeout -- trebuie setate, altfel infinite

Lock.tryLock(timeout)

Eroare 500 + header Retry-After - daca e din cauza unui serviciu extern (nu 500 propriu-zis)

resilience4j

health probe: aliveness & readiness (capabil de trafic)
unalive: conexiuni agatate permanent, disc corupt, OOME, deadlock

LB poate evita o instanta daca e slow/are erori.

Inbox table = store then process
Outbox table pattern = store and fwd
- pune mesajul in SQL cu un status, apoi luat si trimis extern, actualizat status

Operatii idempotente = retryable: GET, DELETE, PUT (overwrite)

POST: idempotent daca la retry trimite acelasi idempotency key (poate fi timestamp la ms); sau serverul sa tina un hash(comanda), salvat intr-un map cu comenzile din ultimele 30 minute -> daca primeste aceeasi comanda va face reject

PUT cu IK (UUID)

server.tomcat.max-connections
server.tomcat.accept-count
server.tomcat.threads.max

Bulkhead = apeluri simultane per JVM
Rate limiter = apeluri intr-un interval de timp
Quartz - orchestrare timers prin DB

Compartimentalizare -> izolare serviciu picat

Cache - for latency or for data replication

App de baza are un SLA (99.99) -> e ok sa chemi alt API cu SLA mai mare (99.999)

Istio: construieste un mic scut in jurul unui serviciu (throttling etc - il face ca un gateway)

Strategii de deployment: canary (ex. FB release in Argentina), blue/green (gradually transfer traffic), deploy in staging pt testeri si clienti timp de _;

release notes, feature flags

shadow deploy=rutezi input din prod si verifici ca da la fel, in paralel cu versiunea veche

Ex. connection pool starvation:

@Transactional
method() {
  synchronized(b) {
  // call a slow api
  }
}

Cache miss 99%: e prea mic, a expirat, nu este gasita cheia (lipseste hashCode, equals in clasa cheii)
Teste pe cache!! --> AgencyCacheTest
Cache hit prea mare: key collision? (chei trunchiate, sunt mereu gasite)

@TimeAspect
@Timed
MeterRegistry - raporteaza metrici

@Observed

Monolit (risc: cod complex) -> microservicii (risc: integrarile)
=>
Mockito -> WireMock (care nu reflecta realitatea din prod)
Monolit: multe unit teste; uS: multe teste de integrare
uS: mai multe bug-uri in yaml config decat in cod

@FeignClient - pt apel rest dintr-un uS la altul

Jeff Bezos manifesto for externalization (2002)

ZoomIt- timer (gong)

Surse:
video: https://www.youtube.com/watch?v=arUOVFUKt6s
drive: https://drive.google.com/open?id=1A2WIOb6tZvkzkHwDEAQSfFCKQAIG4imt&usp=drive_fs
https://victorrentea.ro/slides/

04 noiembrie 2025

Training Java performance - Ziua 2 (JPA)

 SOLR/ES/Lucene - full text search

Vector search - next thing


Brian: paginare N rezultate, apoi N/2 , N/4 ... dar intr-o singura tranzactie


Query-uri dinamice:

1. concat string-uri: WHERE 1+1" (ca apoi sa poti concatena alte conditii, if criteria)

2. Query DieSL: JpaQuery

- Diesel genereaza entitatile (ex. QMaterial)

3. CriteriaAPI


Planul de executie uraste "OR"

- face cache la planul construit anterior

- query diferit: alt plan


Index: ideal e sa ai distributie buna pe date (nu prea multe valori repetitive)


AskTom - (Oracle) - intrebari tuning etc


@Id

@GeneratedValue(strategy = _ )

- SEQUENCE: trebuie creata; la rollback tranzactie nu se poate recupera (da impresia ca e un rand sters in table)

INCREMENT BY 50: tragi din baza 50 id-uri odata

- IDENTITY = auto-increment; ceri de fiecare data din baza

- PK violation daca inserezi id-uri 'cunoscute'

- UUID - greu de ghicit, poate fi generat de client; dar urat

- TABLE


@Embedded pe un obiect din entity -> muti anumite attribute din clasa (coloane) pt a-i scurta marimea


@Entity to Domain Model - vezi notele


@Transactional pe metoda - nu poti face face try/catch pe query

- interzise tranzactiile manuale

Write behind: JPA amana scrierile pana inainte de commit (flush)- pt a insera/update in batch-uri

em.flush() - grabeste flush, dar nu comite; crapa de la erori

+ un select ar putea grabi flush


Carte: "Release it!"


Entitate gasita cu find pe o metoda @Transactional:

.set...() face update (auto-save)

alt .set nu face / nu se mai potriveste equals


em.detach(obj); // scoate din urmarire


em.clear(); // elimina din context toate entitatile (detach la tot), goleste 1st level cache al lui Hibernate


flush vs clear:

- flush scrie tot ce avea de scris; util: inainte sa chemi un PL/SQL, Trigger sau native query.

- goleste fara sa scrie nimic


@DynamicUpdate - modifica coloana din entitate doar daca chiar se modifica


1st level cache = transaction-scoped; find se uita in cache

2nd level cache = intre request-uri diferite (optional @Cacheble pe @Entity sau query hint)

- util: primul select ia din baza, al doilea select identic intr-un TTL=5 min ia din cache


Persistence context


@Cache pe entity class


em.merge(entity); // update fara auto-save, fara @Transactional, cand construiesti cu new entitatea Venita de la client (nu mai faci find)

Merge = suprascriere entitate veche

select parinte, copii si apoi update


cascade

- DETACH ~ detaseaza copiii odata cu parintele

- REFRESH ~ git reset

- MERGE - copiii se ins/upd/del


@Transactional cand doar citesti din BD: lazy init (entitatile-copii ca lista nu sunt dispo pt un framework gen Jackson)


@OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval = true)

entitate parent.getChildren().remove(1) => UPDATE copil set parent null => cu orphanRemoval omoara copiii cu FK = null

( valabil la em.merge )


Cand un camp nu trebuie sa fie modificat pe un flux (ajuta cu merge side effects):

1) @Column(updatable = false) String createdBy; // eroare daca incerci sa modifici din cod

2) setCreatedBy - ignora/arunca eroare daca e deja setat campul

3) suprascrii cu valoarea care era initial in baza


Concurenta la modificare entitate:

Optimistic lock: te lasa sa scrii, cand salvezi iti da eroare daca cineva a salvat inaintea ta

@Version

private Long version; // sau LocalDateTime

// la em.merge(entity) se adauga o conditie suplimentara: AND version = 1 (cat era pe entity)


Pessimistic lock: pus lock in baza = intarzie a doua tranzactie pana comite prima

cu row lock: SELECT ... where version=7 FOR UPDATE [NO WAIT - la nivel de rand]

- face lock pe randurile returnate de SELECT

tx2 va astepta pana tx1 comite; = default in postgres/H2

adaugare coloane IN_EDIT_BY, IN_EDIT_SINCE cand materialul incepe sa fie editat de un user (lock pe linia din tabel)


Nu folosim synchronized in EJB


Monitorizare "connection acquisition time" = metrici - Grafana, Kibana, New Relic


@Transactional:

persist, flush nu duc la commitment daca apare exceptie in metoda tranzactata

- orice metoda chemata in thread-ul curent e parte din aceeasi tranzactie

- thread async: NU, si acopera exceptia; pt ca se pierde conexiunea JDBC care avea tranzactia pornita; nu merge nici daca pasezi EntityManager-ul de pe thread-ul gazda


Metoda @Transactional care cheama alta metoda @Transactional = e aceeasi tranzactie (parinte)

Propagation.REQUIRES_NEW ca sa fie distincta

TxType.REQUIRED = este default


throw checked exception intr-o metoda @Transactional nu impiedica commit-ul, doar unchecked (runtime) impiedica (!!!)

=> metoda @Transactional nu se recomanda cu throws! - faci try/catch/throw RuntimeException


@Transactional cu Propagation.REQUIRES_NEW pe o metoda-copil din acelasi serviciu = ca si cum nu are @Transactional


! nu se da API-call din @Transactional


Deadlock:

synchronized(x) din java + SELECT FOR UPDATE id=7 in parallel cu 

SELECT FOR UPDATE id=7 + synchronized(x) din java


View materializat pe disc si actualizat periodic;


Rainbow brackets- plugin IntelliJ


DbUnit 


Tuning tips:

- EXISTS (SELECT...) in loc de SELECT COUNT() sau id=(SELECT id...)

- Evita SELECT DISTINCT pe mai multe coloane = multe comparatii

- WHERE in loc de HAVING

- INNER JOIN ON <cond> in loc de WHERE<cond>

- LIMIT

- UNION ALL nu UNION (=DISTINCT)

- A UNION B in loc de WHERE... OR..

- GROUP BY in loc de OVER PARTITION BY

- tabele auxiliare

- IN si = in loc de != si <> si NOT IN