Logo

dev-resources.site

for different kinds of informations.

Como eu reduzi em até 99% o tempo de resposta da minha API

Published at
11/22/2024
Categories
java
backend
jpa
hibernate
Author
samluiz
Categories
4 categories in total
java
open
backend
open
jpa
open
hibernate
open
Author
7 person written this
samluiz
open
Como eu reduzi em até 99% o tempo de resposta da minha API

ORM's são uma ferramenta poderosa, mas ao mesmo tempo que facilita nossa vida, também torna um pouco obscura a resolução de problemas se você não tiver experiência com os recursos que são oferecidos. Vou resumir como a funcionalidade de Lazy Loading do JPA + Hibernate e implementar HATEOAS reduziu em até 99% o tempo de resposta de um sistema em produção.

Inexperiência

Esse projeto foi feito no início da minha jornada com Java, onde pude aplicar o pouco conhecimento que eu havia adquirido em um projeto real, com pressão real e expectativas reais. Como era minha primeira experiência, fui aprendendo ao longo do projeto e em alguns momentos isso levava a algumas práticas questionáveis, incluindo duas mais impactantes.

Banco de dados

A API foi construída utilizando Java, PostgreSQL e JPA como ORM para facilitar a comunicação com o banco de dados.

Por padrão, o JPA utiliza Lazy Loading para buscar as informações relacionadas a uma entidade. Por exemplo: Se um Cliente possui várias compras associadas a ele, ao consultar um cliente na aplicação, o JPA entende que se eu não utilizei as compras de um cliente dentro de um contexto, ele não precisa consultar também todas as compras desnecessariamente. Esse era o padrão em minha aplicação inteira, exceto no contexto mais importante.

Quando me deparei com um erro na camada de autenticação, procurando soluções encontrei uma alternativa que funcionou: utilizar Eager Loading nos relacionamentos que eu estava manipulando naquela camada. Adicionei essa propriedade nas duas entidades da seguinte forma:

@ManyToMany(mappedBy = "profissionais", fetch = FetchType.EAGER)
private List<Cliente> clientes;
Enter fullscreen mode Exit fullscreen mode

Nos primeiros meses após a implantação não aconteceu nenhum problema, mas assim que o sistema escalou para algumas centenas de usuário as reclamações começaram a surgir. E não foi a toa, as requisições estavam levando de 10 segundos a quase 1 minuto pra completar:

Image description

Nesse cenário assustador, com um pouco mais de experiência devido aos meses que se passaram e algumas pesquisas, cheguei à conclusão de que essa lentidão foi causada principalmente pelo alto volume de processamento de JSON em uma só requisição. Isso se deu pelo fato de haver muitos recursos aninhados, algo que vou explicar a seguir.

Design da API

Devido ao projeto ter um prazo de entrega curto e um MVP muito grande devido a falta de experiência na coleta de requisitos, fui obrigado a tomar algumas decisões de arquitetura que não eram muito escaláveis. A pior decisão foi utilizar o retorno de todos os objetos aninhados partindo do objeto pai. Isso significa que um recurso consultado possuía vários objetos aninhados, e esse objetos também possuíam vários objetos aninhados, até chegar no último objeto possível associado ao recurso. Isso causou um gargalo no processamento de respostas JSON em consultas e outras interações com retorno e, dentre várias outras otimizações, optei por solucionar esse problema aplicando (parcialmente) o princípio HATEOAS, substituindo os recursos aninhados por links relevantes para consultar esses recursos associados. Esse pequeno ajuste garantiu uma escalabilidade maior, já que agora os retornos da API não cresciam exponencialmente.

Solução definitiva

Após várias otimizações em consultas custosas ao banco de dados, redução de roundtrips da aplicação ao banco de dados utilizando operações em batch e criação de índices no banco de dados, chegou o dia da implantação dessa refatoração. Estava ansioso pra testar essas funcionalidades mas precisava de um teste confiável. Pra garantir isso repliquei o banco de dados de produção para o ambiente de stage, abri a aplicação, testei e...

Não evoluiu nada.

Após meses de aprendizado constante e muitas modificações no código, parecia que meu esforço havia sido em vão. As requisições continuavam levando a mesma quantidade de tempo, mas dessa vez a performance estava ainda pior pelo motivo do frontend estar fazendo mais requisições do que antes (já que agora não existiam mais recursos aninhados). Naquele momento senti um frio na barriga e pensei que teria que virar a madrugada pra solucionar um problema grave de performance que parecia não ter mais soluções.

Até que eu lembrei do trecho abaixo de código:

@ManyToMany(mappedBy = "profissionais", fetch = FetchType.EAGER)
private List<Cliente> clientes;
Enter fullscreen mode Exit fullscreen mode

Havia uma esperança que esse trecho simples do código fosse o responsável por pelo menos uma parte do gargalo do sistema. A única alteração que fiz (em 3 trechos parecidos do código) foi a seguinte:

@ManyToMany(mappedBy = "profissionais", fetch = FetchType.LAZY)
private List<Cliente> clientes;
Enter fullscreen mode Exit fullscreen mode

Agora, sabendo o que essas estratégias de data fetching significam e seus prós e contras, estava confiante o suficiente para subir essa fix e testar novamente. Foi então que obtive os resultados abaixo:

Image description

Conclusão

ORM's são uma poderosa ferramenta, mas você precisa saber muito bem o que está fazendo. Mesmo após dezenas de otimizações, o gargalo em sua maioria estava em uma única propriedade do JPA. Eu obteria o mesmo resultado caso tivesse alterado isso e não realizasse nenhuma outra melhoria? Nunca saberemos.

hibernate Article's
30 articles in total
Favicon
JOOQ Is Not a Replacement for Hibernate. They Solve Different Problems
Favicon
Persistence Context в Hibernate Zoo: путешествие объекта по жизненным состояниям
Favicon
Unidirectional associations for one-to-many
Favicon
Como eu reduzi em até 99% o tempo de resposta da minha API
Favicon
Hibernate Zoo: Жадный Гиппопотам и Ленивый Лемур (Lazy vs Eager)
Favicon
🐾 Hibernate Zoo: Путеводитель по языкам запросов в мире данных 🐾
Favicon
How To Fetch Data By Using DTO Projection In Spring Data JPA
Favicon
Ubuntu 22.04 Hibernate Using Swap File
Favicon
Зоопарк Hibernate: N+1 запросов или как накормить жадного бегемота
Favicon
Spring Data JPA Stream Query Methods
Favicon
Uma breve introdução ao Hibernate
Favicon
Ubuntu hibernate
Favicon
Eager vs Lazy Initialization of Spring Beans
Favicon
Understanding JPA Mappings in Spring Boot: One-to-One, One-to-Many, Many-to-One, and Many-to-Many Relationships
Favicon
Java Hibernate vs JPA: Rapid review for you
Favicon
Hibernate Connection Library with GUI Generation
Favicon
what is JPA? explain few configurations
Favicon
Demystifying Hibernate: A Beginner's Journey
Favicon
How to deal with N+1 problems with Hibernate
Favicon
Java Hibernate vs JPA: Quick Review
Favicon
Uppercase table names in Spring Boot
Favicon
Hiring Alert - Java Developer- Blockchain
Favicon
H2 database Setup Error Unable to load name org.hibernate.dialect.Oracle10gDialect
Favicon
Capitalisation of table name generated by Hibernate when using MySQL server
Favicon
Display SQL statement generated by Hibernate JPA in Spring Boot environment
Favicon
Advanced and dynamic searching with Spring Data JPA
Favicon
Defining JPA/Hibernate Entities in Kotlin
Favicon
Criteria Queries and JPA Metamodel with Spring Boot and Kotlin
Favicon
How to save Hibernate Entity changes to Database
Favicon
Hibernate Cheat Sheet

Featured ones: