Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions participantes/yan-miranda/README.md
Original file line number Diff line number Diff line change
@@ -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
```
60 changes: 60 additions & 0 deletions participantes/yan-miranda/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions participantes/yan-miranda/nginx.conf
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
69 changes: 69 additions & 0 deletions participantes/yan-miranda/script.sql
Original file line number Diff line number Diff line change
@@ -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;