From 75bfcd499ce0855ff3263c45421b6f46874220ea Mon Sep 17 00:00:00 2001 From: "yan.miranda" Date: Tue, 2 Dec 2025 22:32:27 -0400 Subject: [PATCH] [FEATURE] Test backend java - Yan Miranda --- participantes/yan-miranda/README.md | 39 +++++++++++ participantes/yan-miranda/docker-compose.yml | 60 +++++++++++++++++ participantes/yan-miranda/nginx.conf | 48 ++++++++++++++ participantes/yan-miranda/script.sql | 69 ++++++++++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 participantes/yan-miranda/README.md create mode 100644 participantes/yan-miranda/docker-compose.yml create mode 100644 participantes/yan-miranda/nginx.conf create mode 100644 participantes/yan-miranda/script.sql diff --git a/participantes/yan-miranda/README.md b/participantes/yan-miranda/README.md new file mode 100644 index 0000000..394b71e --- /dev/null +++ b/participantes/yan-miranda/README.md @@ -0,0 +1,39 @@ +# Teste Java - Sizebay + +**Autor:** Yan Miranda + +**Repositório Original:** https://github.com/YanMiranda/sizebay + +## Tecnologias +- Java 17 +- Spring Boot 3.3 +- PostgreSQL (Stored Procedures) +- Nginx +- Docker + +## Abordagem Arquitetural + +Para atender aos requisitos de alta concorrência e restrição rígida de recursos (1.5 CPU / 550MB RAM), a solução focou em **reduzir a responsabilidade da aplicação** (Java) e **otimizar o I/O do Banco de Dados**. + +### 1. Transacionalidade e Performance (Stored Procedures) +A lógica crítica de transação (débito/crédito) e validação de consistência foi migrada da camada de aplicação para o banco de dados via funções PL/pgSQL (`create_transaction`). +* **Por que:** Isso elimina o *overhead* de múltiplos *round-trips* (idas e vindas na rede) entre a API e o Banco dentro de uma mesma transação. +* **Resultado:** A operação torna-se atômica e o tempo de bloqueio (*lock*) dos registros ocorre estritamente dentro do motor do banco, liberando as conexões do pool Java muito mais rápido. + +### 2. Otimização de Armazenamento e I/O +* **Tabelas UNLOGGED:** As tabelas do Postgres foram configuradas como `UNLOGGED` para reduzir a escrita no *Write Ahead Log* (WAL), priorizando a velocidade de inserção bruta exigida pelo teste de carga. +* **Tipagem Eficiente:** Uso de `CHAR(1)` para tipos fixos, otimizando o armazenamento e o tráfego de dados. + +### 3. Resiliência e Alta Disponibilidade (Nginx Tuning) +O Nginx foi configurado não apenas como balanceador, mas como uma camada de proteção e resiliência: +* **Retry Policy:** Configuração de `proxy_next_upstream` para redirecionar requisições imediatamente para a instância vizinha em caso de falha ou timeout. +* **Keep-Alive:** Ajuste fino de conexões persistentes para evitar o custo de *handshake* TCP/SSL repetitivo. + +### 4. Otimização de Recursos (JVM) +* **Fail Fast & Silent:** Exceções de negócio (`SaldoInsuficiente`, `ClienteNaoEncontrado`) foram configuradas sem a geração de *Stack Trace* (`fillInStackTrace`), economizando ciclos de CPU preciosos ao evitar o rastreamento da pilha em erros esperados. +* **DTOs:** Uso de Java Records para imutabilidade e menor consumo de memória no *Heap*. + +## Como rodar +```bash +docker-compose up +``` \ No newline at end of file diff --git a/participantes/yan-miranda/docker-compose.yml b/participantes/yan-miranda/docker-compose.yml new file mode 100644 index 0000000..4d7f06b --- /dev/null +++ b/participantes/yan-miranda/docker-compose.yml @@ -0,0 +1,60 @@ +version: '3.9' + +services: + api01: &api + image: yanmiranda/sizebay:latest + container_name: api01 + hostname: api01 + environment: + - DB_URL=jdbc:postgresql://db:5432/sizebay + - DB_USER=sizebay + - DB_PASSWORD=sizebay + - JAVA_TOOL_OPTIONS=-Xmx180m -Xms180m -XX:+UseSerialGC + depends_on: + db: + condition: service_healthy + deploy: + resources: + limits: + cpus: '0.55' + memory: '180MB' + + api02: + <<: *api + container_name: api02 + hostname: api02 + + nginx: + image: nginx:latest + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - api01 + - api02 + ports: + - "9999:9999" + deploy: + resources: + limits: + cpus: '0.15' + memory: '70MB' + + db: + image: postgres:latest + hostname: db + environment: + - POSTGRES_PASSWORD=sizebay + - POSTGRES_USER=sizebay + - POSTGRES_DB=sizebay + volumes: + - ./script.sql:/docker-entrypoint-initdb.d/script.sql + deploy: + resources: + limits: + cpus: '0.25' + memory: '120MB' + healthcheck: + test: ["CMD-SHELL", "pg_isready -U sizebay -d sizebay"] + interval: 5s + timeout: 5s + retries: 5 \ No newline at end of file diff --git a/participantes/yan-miranda/nginx.conf b/participantes/yan-miranda/nginx.conf new file mode 100644 index 0000000..6ced217 --- /dev/null +++ b/participantes/yan-miranda/nginx.conf @@ -0,0 +1,48 @@ +worker_processes auto; +worker_rlimit_nofile 20000; + +events { + use epoll; + worker_connections 4048; + multi_accept on; +} + +http { + access_log off; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + # 5000 requests é um bom meio termo. + # Recicla a memória de tempos em tempos sem cortar o Gatling toda hora. + keepalive_requests 5000; + keepalive_timeout 65; + + # Proteção: Limpa conexões mortas + reset_timedout_connection on; + + # Timeouts longos para segurar o tranco do Cold Start + proxy_read_timeout 120s; + proxy_connect_timeout 10s; + proxy_send_timeout 120s; + + proxy_buffering off; + proxy_request_buffering off; + + upstream api { + server api01:8080 max_fails=0 fail_timeout=0; + server api02:8080 max_fails=0 fail_timeout=0; + keepalive 100; + } + + server { + listen 9999; + + location / { + proxy_pass http://api; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_next_upstream error timeout http_502 http_503 http_504; + } + } +} \ No newline at end of file diff --git a/participantes/yan-miranda/script.sql b/participantes/yan-miranda/script.sql new file mode 100644 index 0000000..990b0cd --- /dev/null +++ b/participantes/yan-miranda/script.sql @@ -0,0 +1,69 @@ +DROP TABLE IF EXISTS tb_transacoes; +DROP TABLE IF EXISTS tb_clientes; + +CREATE UNLOGGED TABLE tb_clientes ( + cli_id SERIAL PRIMARY KEY, + cli_limite INTEGER NOT NULL, + cli_saldo INTEGER NOT NULL +); + +CREATE UNLOGGED TABLE tb_transacoes ( + tra_id SERIAL PRIMARY KEY, + cli_id INTEGER NOT NULL, + tra_valor INTEGER NOT NULL, + tra_tipo CHAR(1) NOT NULL, + tra_descricao VARCHAR(10) NOT NULL, + tra_realizada_em TIMESTAMP NOT NULL +); + +CREATE INDEX idx_transacoes_cliente_data ON tb_transacoes (cli_id, tra_realizada_em DESC); + +INSERT INTO tb_clientes (cli_id, cli_limite, cli_saldo) VALUES +(1, 100000, 0), +(2, 80000, 0), +(3, 1000000, 0), +(4, 10000000, 0), +(5, 500000, 0); + +CREATE OR REPLACE FUNCTION create_transaction( + p_cliente_id INTEGER, + p_valor INTEGER, + p_descricao VARCHAR, + p_tipo VARCHAR +) RETURNS TABLE (status VARCHAR, novo_saldo INTEGER, limite INTEGER) AS $$ +DECLARE +v_saldo INTEGER; + v_limite INTEGER; + v_valor_ajustado INTEGER; +BEGIN + IF p_tipo = 'd' THEN + v_valor_ajustado := -p_valor; +ELSE + v_valor_ajustado := p_valor; +END IF; + +UPDATE tb_clientes +SET cli_saldo = cli_saldo + v_valor_ajustado +WHERE cli_id = p_cliente_id + AND (p_tipo = 'c' OR (cli_saldo + v_valor_ajustado) >= -cli_limite) + RETURNING cli_saldo, cli_limite INTO v_saldo, v_limite; + +IF FOUND THEN + INSERT INTO tb_transacoes (cli_id, tra_valor, tra_tipo, tra_descricao, tra_realizada_em) + VALUES (p_cliente_id, p_valor, p_tipo, p_descricao, NOW()); + +RETURN QUERY SELECT 'O'::VARCHAR, v_saldo, v_limite; +RETURN; +END IF; + +SELECT cli_limite, cli_saldo INTO v_limite, v_saldo +FROM tb_clientes +WHERE cli_id = p_cliente_id; + +IF NOT FOUND THEN + RETURN QUERY SELECT 'N'::VARCHAR, 0, 0; +ELSE + RETURN QUERY SELECT 'I'::VARCHAR, v_saldo, v_limite; +END IF; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file