Giter Site home page Giter Site logo

zanfranceschi / rinha-de-backend-2024-q1 Goto Github PK

View Code? Open in Web Editor NEW
1.7K 15.0 951.0 720.32 MB

Repositório da 2ª edição da Rinha de Backend

Home Page: https://twitter.com/rinhadebackend

License: MIT License

PowerShell 0.01% Shell 0.01% Scala 0.01% PLpgSQL 0.24% JavaScript 6.23% Makefile 0.01% Dockerfile 0.01% PHP 0.01% Python 0.01% HTML 86.09% CSS 7.42%

rinha-de-backend-2024-q1's Introduction

Rinha de Backend - 2024/Q1

A Rinha de Backend é um desafio que tem como principal objetivo compartilhar conhecimento em formato de desafio! Esta é a segunda edição. A data limite para enviar sua submissão é 10 de Março de 2024 às 23:59:59 e em 14 de Março de 2024 às 19:00 os resultados serão anunciados numa live no YouTube.

O principal assunto dessa Rinha trata de controle de concorrência com o tema créditos e débitos (crébitos) e foi inspirado pelos colegas @lucascs e @kmyokoyama, nesse e nesse comentário dessa tweet.

arte Se quiser entender mais sobre o espírito das Rinhas, confira o repositório da primeira edição.

O Que Precisa Ser Feito?

Para participar você precisa desenvolver uma API HTTP com os seguintes endpoints:

Transações

Requisição

POST /clientes/[id]/transacoes

{
    "valor": 1000,
    "tipo" : "c",
    "descricao" : "descricao"
}

Onde

  • [id] (na URL) deve ser um número inteiro representando a identificação do cliente.
  • valor deve ser um número inteiro positivo que representa centavos (não vamos trabalhar com frações de centavos). Por exemplo, R$ 10 são 1000 centavos.
  • tipo deve ser apenas c para crédito ou d para débito.
  • descricao deve ser uma string de 1 a 10 caracteres.

Todos os campos são obrigatórios.

Resposta

HTTP 200 OK

{
    "limite" : 100000,
    "saldo" : -9098
}

Onde

  • limite deve ser o limite cadastrado do cliente.
  • saldo deve ser o novo saldo após a conclusão da transação.

Obrigatoriamente, o http status code de requisições para transações bem sucedidas deve ser 200!

Regras Uma transação de débito nunca pode deixar o saldo do cliente menor que seu limite disponível. Por exemplo, um cliente com limite de 1000 (R$ 10) nunca deverá ter o saldo menor que -1000 (R$ -10). Nesse caso, um saldo de -1001 ou menor significa inconsistência na Rinha de Backend!

Se uma requisição para débito for deixar o saldo inconsistente, a API deve retornar HTTP Status Code 422 sem completar a transação! O corpo da resposta nesse caso não será testado e você pode escolher como o representar. HTTP 422 também deve ser retornado caso os campos do payload estejam fora das especificações como, por exemplo, uma string maior do que 10 caracteres para o campo descricao ou algo diferente de c ou d para o campo tipo. Se para o campo valor um número não inteiro for especificado, você poderá retornar HTTP 422 ou 400.

Se o atributo [id] da URL for de uma identificação não existente de cliente, a API deve retornar HTTP Status Code 404. O corpo da resposta nesse caso não será testado e você pode escolher como o representar. Se a API retornar algo como HTTP 200 informando que o cliente não foi encontrado no corpo da resposta ou HTTP 204 sem corpo, ficarei extremamente deprimido e a Rinha será cancelada para sempre.

Extrato

Requisição

GET /clientes/[id]/extrato

Onde

  • [id] (na URL) deve ser um número inteiro representando a identificação do cliente.

Resposta

HTTP 200 OK

{
  "saldo": {
    "total": -9098,
    "data_extrato": "2024-01-17T02:34:41.217753Z",
    "limite": 100000
  },
  "ultimas_transacoes": [
    {
      "valor": 10,
      "tipo": "c",
      "descricao": "descricao",
      "realizada_em": "2024-01-17T02:34:38.543030Z"
    },
    {
      "valor": 90000,
      "tipo": "d",
      "descricao": "descricao",
      "realizada_em": "2024-01-17T02:34:38.543030Z"
    }
  ]
}

Onde

  • saldo
    • total deve ser o saldo total atual do cliente (não apenas das últimas transações seguintes exibidas).
    • data_extrato deve ser a data/hora da consulta do extrato.
    • limite deve ser o limite cadastrado do cliente.
  • ultimas_transacoes é uma lista ordenada por data/hora das transações de forma decrescente contendo até as 10 últimas transações com o seguinte:
    • valor deve ser o valor da transação.
    • tipo deve ser c para crédito e d para débito.
    • descricao deve ser a descrição informada durante a transação.
    • realizada_em deve ser a data/hora da realização da transação.

Regras Se o atributo [id] da URL for de uma identificação não existente de cliente, a API deve retornar HTTP Status Code 404. O corpo da resposta nesse caso não será testado e você pode escolher como o representar. Já sabe o que acontece se sua API retornar algo na faixa 2XX, né? Agradecido.

Cadastro Inicial de Clientes

Para haver ênfase em concorrência durante o teste, poucos clientes devem ser cadastrados e testados. Por isso, apenas cinco clientes, com os seguintes IDs, limites e saldos iniciais, devem ser previamente cadastrados para o teste – isso é imprescindível!

id limite saldo inicial
1 100000 0
2 80000 0
3 1000000 0
4 10000000 0
5 500000 0

Obs.: Não cadastre um cliente com o ID 6 especificamente, pois parte do teste é verificar se o cliente com o ID 6 realmente não existe e a API retorna HTTP 404!

Como Fazer e Entregar?

Assim como na Rinha de Backend anterior, você precisará conteinerizar sua API e outros componentes usados no formato de docker-compose, obedecer às restrições de recursos de CPU e memória, configuração mínima arquitetural, e estrutura de artefatos e processo de entrega (o que, onde e quando suas coisas precisam ser entregues).

Você pode fazer a submissão de forma individual, dupla de 2, dupla de 3 ou até dupla de 50 pessoas. Não tem limite. E você e/ou seu grupo pode fazer mais de uma submissão desde que a API seja diferente.

Artefato, Processo e Data Limite de Entrega

Para participar, basta fazer um pull request neste repositório incluindo um subdiretório em participantes com os seguintes arquivos:

  • docker-compose.yml - arquivo interpretável por docker-compose contendo a declaração dos serviços que compõe sua API respeitando as restrições de CPU/memória e arquitetura mínima.
  • README.md - incluindo pelo menos seu nome, tecnologias que usou, o link para o repositório do código fonte da sua API, e alguma forma de entrar em contato caso vença. Fique à vontade para incluir informações adicionais como link para site, etc.
  • Inclua aqui também quaisquer outros diretórios/arquivos necessários para que seus contêineres subam corretamente como, por exemplo, nginx.conf, banco.sql, etc.

Aqui tem um exemplo de submissão para te ajudar, caso queira.

Importante! É fundamental que todos os serviços declarados no docker-compose.yml estejam publicamente disponíveis! Caso contrário, não será possível executar os testes. Para isso, você pode criar uma conta em hub.docker.com para disponibilizar suas imagens. Essa imagens geralmente terão o formato <user>/<imagem>:<tag> – por exemplo, zanfranceschi/rinha-api:latest.

Um erro comum na edição anterior da Rinha foi a declaração de imagens como se estivessem presentes localmente. Isso pode ser verdade para quem as construiu (realizou o build localmente), mas não será verdadeiro para o servidor que executará os testes!

Importante! É obrigatório deixar o repositório contendo o código fonte da sua API publicamente acessível e informado no arquivo README.md entregue na submissão. Afinal, a Rinha de Backend tem como principal objetivo compartilhar conhecimento!

Um exemplo de submissão/pull request da Ana, poderia ter os seguintes arquivos:

├─ participantes/
|  ├─ ana-01/
|  |  ├─ docker-compose.yml
|  |  ├─ nginx.config
|  |  ├─ sql/
|  |  |  ├─ ddl.sql
|  |  |  ├─ dml.sql
|  |  ├─ README.md

A data/hora limite para fazer pull requests para sua submissão é até 2024-03-10T23:59:59-03:00. Após esse dia/hora, qualquer pull request será automaticamente rejeitado.

Note que você poderá fazer quantos pull requests desejar até essa data/hora limite!

Por "API" aqui, me refiro a todos os serviços envolvidos para que o serviço que atenderá às requisições HTTP funcione, tais como o load balancer, banco de dados e servidor HTTP.

A sua API precisa ter, no mínimo, os seguintes serviços:

  • Um load balancer que faça a distribuição de tráfego usando o algoritmo round robin. Diferentemente da edição anterior, você não precisa usar o Nginx – pode escolher (ou até fazer) qualquer um como p.ex. o HAProxy. O load balancer será o serviço que receberá as requisições do teste e ele precisa aceitar requisições na porta 9999!
  • 2 instâncias de servidores web que atenderão às requisições HTTP (distribuídas pelo load balancer).
  • Um banco de dados relacional ou não relacional (exceto bancos de dados que têm como principal característica o armazenamento de dados em memória, tal como Redis, por exemplo).
flowchart TD
    G(Stress Test - Gatling) -.-> LB(Load Balancer / porta 9999)
    subgraph Sua Aplicação
        LB -.-> API1(API - instância 01)
        LB -.-> API2(API - instância 02)
        API1 -.-> Db[(Database)]
        API2 -.-> Db[(Database)]
    end

Nota: Você pode usar componentes adicionais se quiser. Mas lembre-se de que as restrições de CPU e memória devem obedecer a regra de que a soma dos limites (que devem ser declarados para todos os serviços) não poderá ultrapassar 1.5 unidades de CPU e 550MB de memória! Use o bom senso e boa fé, não adicione um banco relacional e um Redis, por exemplo, e use apenas o Redis como armazenamento – afinal, a Rinha é apenas uma brincadeira que fomenta o aprendizado e não a competição desleal.

Dentro do seu arquivo docker-compose.yml, você deverá limitar todos os serviços para que a soma deles não ultrapasse os seguintes limites:

  • deploy.resources.limits.cpu 1.5 – uma unidade e meia de CPU distribuída entre todos os seus serviços
  • deploy.resources.limits.memory 550MB – 550 mega bytes de memória distribuídos entre todos os seus serviços

Obs.: Por favor, use MB para unidade de medida de memória; isso facilita as verificações de restrições.

# exemplo de parte de configuração de um serviço dentro do um arquivo docker-compose.yml
...
  nginx:
    image: nginx:latest
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - api01
      - api02
    ports:
      - "9999:9999"
    deploy:
      resources:
        limits:
          cpus: "0.17"
          memory: "10MB"
...

O seguinte são apenas arquivos de exemplo para que você não saia do zero, caso tenha alguma dificuldade ou apenas queira acelerar a construção da sua API. Obviamente, modifique como quiser respeitando todos as restrições anteriormente explicadas aqui. Novamente, você não precisa usar especificamente um banco de dados relacional – o exemplo seguinte é apenas ilustrativo.

docker-compose.yml

version: "3.5"

services:
  api01: &api
    # Lembre-se de que seu serviço HTTP deve estar hospedado num repositório
    # publicamente acessível! Ex.: hub.docker.com
    image: ana/minha-api-matadora:latest
    hostname: api01
    environment:
      - DB_HOSTNAME=db
    
    # Não é necessário expor qualquer porta além da porta do load balancer,
    # mas é comum as pessoas o fazerem para testarem suas APIs e conectarem
    # ao banco de dados na fase de desenvolvimento.
    ports:
      - "8081:8080"
    depends_on:
      - db
    deploy:
      resources:
        limits:
          cpus: "0.6"
          memory: "200MB"

  api02:
    # Essa sintaxe reusa o que foi declarado em 'api01'.
    <<: *api 
    hostname: api02
    environment:
      - DB_HOSTNAME=db
    ports:
      - "8082:8080"
 
  nginx:
    image: nginx:latest
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - api01
      - api02
    ports:
        # Obrigatório expor/usar a porta 9999 no load balancer!
      - "9999:9999" 
    deploy:
      resources:
        limits:
          cpus: "0.17"
          memory: "10MB"

  db:
    image: postgres:latest
    hostname: db
    environment:
      - POSTGRES_PASSWORD=123
      - POSTGRES_USER=admin
      - POSTGRES_DB=rinha
    ports:
      - "5432:5432"
    volumes:
      - ./script.sql:/docker-entrypoint-initdb.d/script.sql
    deploy:
      resources:
        limits:
          # Note que a soma de todos os limites dos serviços
          # aqui declarados é de 1.5 unidades de CPU e 550MB
          # de memória. A distribuição feita aqui é apenas
          # um exemplo – distribua como quiser.
          cpus: "0.13"
          memory: "140MB"

# O uso do modo `bridge` deve ser adequado à carga que será usada no teste.
# A edição anterior se beneficiou do modo host pois o volume de requisições
# era relativamente alto e a virtualização da rede se tornou um gargalo, mas
# este modo é mais complexo de ser configurado. Fique à vontade para usar o
# modo que quiser desde que não conflite com portas trivialmente usadas em um
# SO.
networks:
  default:
    driver: bridge
    name: rinha-nginx-2024q1

script.sql

-- Coloque scripts iniciais aqui
CREATE TABLE...

DO $$
BEGIN
  INSERT INTO clientes (nome, limite)
  VALUES
    ('o barato sai caro', 1000 * 100),
    ('zan corp ltda', 800 * 100),
    ('les cruders', 10000 * 100),
    ('padaria joia de cocaia', 100000 * 100),
    ('kid mais', 5000 * 100);
END; $$

nginx.conf

events {
    worker_connections 1000;
}

http {
    access_log off;
    sendfile   on;
    
    upstream api {
        server api01:8080;
        server api02:8080;
    }

    server {
        listen 9999; # Lembra da porta 9999 obrigatória?
        
        location / {
            proxy_pass http://api;
        }
    }
}

Ferramenta de Teste

Como na edição anterior, a ferramenta Gatling será usada novamente para realizar o teste de performance. Pode fazer muita diferença você executar os testes durante a fase de desenvolvimento para detectar possíveis problemas e gargalos. O teste está disponível nesse repositório em load-test.

Ambiente de Testes

Para saber os detalhes sobre o ambiente (SO e versões de software) acesse Especificações do Ambiente de Testes.

Note que o ambiente em que os testes serão executados é Linux x64. Portanto, se seu ambiente de desenvolvimento possui outra arquitetura, você precisará fazer o build do docker da seguinte forma: $ docker buildx build --platform linux/amd64

Por exemplo: $ docker buildx build --platform linux/amd64 -t ana/minha-api-matadora:latest .

Para executar os testes

Aqui estão instruções rápidas para você poder executar os testes:

  1. Baixe o Gatling em https://gatling.io/open-source/
  2. Certifique-se de que tenha o JDK instalado (64bits OpenJDK LTS (Long Term Support) versions: 11, 17 e 21) https://gatling.io/docs/gatling/tutorials/installation/
  3. Certifique-se de configurar a variável de ambiente GATLING_HOME para o diretório da instalação do Gatling. Para se certificar de que a variável está correta, os seguinte caminhos precisam ser válidos: $GATLING_HOME/bin/gatling.sh no Linux e %GATLING_HOME%\bin\gatling.bat no Windows.
  4. Configure o script ./executar-teste-local.sh (ou ./executar-teste-local.ps1 se estiver no Windows)
  5. Suba sua API (ou load balancer) na porta 9999
  6. Execute ./executar-teste-local.sh (ou ./executar-teste-local.ps1 se estiver no Windows)
  7. Agora é só aguardar o teste terminar e abrir o relatório O caminho do relatório é exibido ao término da simulação. Os resultados/relatórios são salvos em ./load-test/user-files/results.

Fique à vontade para alterar a simulação para testar diferentes aspectos e cenários. Não inclua essas alterações no pull request de submissão!

De nada :)

Pré teste

Na edição anterior da Rinha, o teste começava poucos segundos após a subida dos contêineres e, devido as restrições de CPU e memória, nem todos os serviços estavam prontos para receber requisições em tão pouco tempo. Nessa edição, antes do teste iniciar, um script verificará se a API está respondendo corretamente (via GET /clientes/1/extrato) por até 40 segundos em intervalos de 2 segundos a cada tentativa. Por isso, certifique-se de que todos seus serviços não demorem mais do que 40 segundos para estarem aptos a receberem requisições!

Nota importante sobre o teste escrito!

A simulação contém um teste de lógica de saldo/limite que extrapola o que é comumente feito em testes de performance. O escrevi assim apenas por causa da natureza da Rinha de Backend. Evite fazer esse tipo de coisa em testes de performance, pois não é uma prática recomendada normalmente. Testes de lógica devem ficar junto ao código fonte em formato de testes de unidade ou integração!

Critérios para Vencer A Rinha de Backend

Surpresa! :)

Acompanhamento do Status das Execuções dos Testes

Link do status parcial da Rinha de Backend.

rinha-de-backend-2024-q1's People

Contributors

alcivanlucas avatar diegocoronel avatar droderuan avatar emanuel-xavier avatar engylemure avatar gabrielfmagalhaes avatar gasparbarancelli avatar gldmelo avatar hiroshimorowaka avatar jojodev02 avatar jonathanperis avatar kitsunesemcalda avatar lpicanco avatar lucianovilela avatar omurilo avatar renatolfc avatar rodrigocaldeira avatar santannaf avatar stefannovasky avatar thusspokebieu avatar vgdss avatar victorverdoodt avatar viniciusfcf avatar vkobinski avatar weversonl avatar whyakari avatar wladneto avatar yanpitangui avatar ygorcarmo avatar zanfranceschi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rinha-de-backend-2024-q1's Issues

Canal oficial para discussão?

Eu estava esperando encontrar uma lista enorme de perguntas aqui e discussões sobre soluções, vcs trocam ideia soh pelo X? Eu estou quebrando alguma regra que eu não vi postando aqui?

PR errado aceito?

Aparentemente foi aceito um PR que mudou os arquivos principais da rinha ao invés de adicionar um novo participante.

Dúvida sobre Validação de Transações Concorrentes

Olá, estou testando minha API e estou tendo o único problema de jmesPath(saldo.total).find.is(0), but actually found -25 dei uma olhada no script de testes e reparei isso aqui.

validacaoTransacoesConcorrentes("d").inject(
      atOnceUsers(validacaConcorrentesNumRequests)
    ).andThen(
      validacaoTransacoesConcorrentesSaldo(validacaConcorrentesNumRequests * -1).inject(
        atOnceUsers(1)
      )
    ).andThen(
      validacaoTransacoesConcorrentes("c").inject(
        atOnceUsers(validacaConcorrentesNumRequests)
      ).andThen(
        validacaoTransacoesConcorrentesSaldo(0).inject(
          atOnceUsers(1)
        )
      )
    )

Sendo que essas validações são sempre feitas no cliente id 1 de acordo com:

val validacaConcorrentesNumRequests = 25
  val validacaoTransacoesConcorrentes = (tipo: String) =>
    scenario(s"validação concorrência transações - ${tipo}")
    .exec(
      http("validações")
      .post(s"/clientes/1/transacoes")
          .header("content-type", "application/json")
          .body(StringBody(s"""{"valor": 1, "tipo": "${tipo}", "descricao": "validacao"}"""))
          .check(status.is(200))
    )
  
val validacaoTransacoesConcorrentesSaldo = (saldoEsperado: Int) =>
    scenario(s"validação concorrência saldo - ${saldoEsperado}")
    .exec(
      http("validações")
      .get(s"/clientes/1/extrato")
      .check(
        jmesPath("saldo.total").ofType[Int].is(saldoEsperado)
      )
    )

Logo, se eu entendi direito como é pra o crédito e débito funcionar na rinha, esse script faz com que o saldo do cliente 1 fique em -25 através das transações de débito e passando na primeira validação, porém quando é realizada a segunda validação utilizando crédito, apesar de o saldo não ser afetado pelas transações de crédito, o cliente 1 continua com -25 de saldo do teste anterior, falhando a verificação de estar com 0 de saldo no segundo teste.
A ordem não deveria ser ao contrário, crédito primeiro e débito depois? Ou eu entendi o crédito e débito errado?

Teste de validação retornando 200 ao invés de 422

O teste de validação abaixo deveria retornar um status 422.

.exec(
  http("validações")
    .post("/clientes/1/transacoes")
    .header("content-type", "application/json")
    .body(StringBody(s"""{"valor": 1, "tipo": "c", "descricao": "123456789 e mais um pouco"}"""))
    .check(status.in(422))
)

Porém, ao testar localmente, ele retorna sempre um status 200.Olhando o corpo da requisição não consigo ver nada de errado na requisição. O que pode ser?

Duvida sobre banco de dados em memória

Poderia esclarecer:

Exceto bancos de dados que têm como principal característica o armazenamento de dados em memória

Se um banco de dados armazena tudo em memoria e possui um thead pra descarregar a memória em disco de forma asincrona, seria desqualificado?

Redis tem features pra persistir dados em disco assim como outros DBs tem features pra cachear rows e outros elementos do banco de dados em memória.

Teste de performance pode iniciar antes do fim dos testes de critérios, causando falsos negativos.

Nas seguintes linhas:

).andThen(
criteriosClientes.inject(
atOnceUsers(saldosIniciaisClientes.length)
),
criterioClienteNaoEcontrado.inject(
atOnceUsers(1)
).andThen(
debitos.inject(
rampUsersPerSec(1).to(220).during(2.minutes),
constantUsersPerSec(220).during(2.minutes)
),

O teste criteriosClientes acontece em paralelo com criterioClienteNaoEcontrado.

Se o resultado de criterioClienteNaoEcontrado retornar antes da finalização de criteriosClientes, o teste de perfomance irá inicializar, causando falsos negativos no teste criteriosClientes, pois quando se faz o request do extrato, requisições do teste perfomance já podem ter sido iniciadas.

A solução seria adicionar um andThen para que o test de perfomance se inicie apenas após o teste, criteriosClientes.

).andThen(
  criteriosClientes.inject(
    atOnceUsers(saldosIniciaisClientes.length)
  ).andThen( // AQUI!
  criterioClienteNaoEcontrado.inject(
    atOnceUsers(1)
  ).andThen(
    debitos.inject(
      rampUsersPerSec(1).to(220).during(2.minutes),
      constantUsersPerSec(220).during(2.minutes)
    ),

Podem confirmar se eu entendi certo? Não tenho experiência com Gatling :(

Problemas com o Gatling

Fala pessoal!
Sabem me dizer se está faltando algo para eu configurar no Gatling / JDK para esses erros?

---- Errors --------------------------------------------------------------------

Request timeout to localhost/127.0.0.1:9999 after 60000 ms 8548 (61,47%)
j.i.IOException: Premature close 5345 (38,44%)
status.find.in(422), but actually found 400 5 ( 0,04%)

================================================================================

Estava achando estranho todos os testes do meu projeto estarem com muitos erros. Então rodei o projeto do Zan, zan-dotnet, e obtive esses resultados (bem diferente do que está aqui no repo)

---- Global Information --------------------------------------------------------

request count 61478 (OK=47573 KO=13905 )
min response time 1 (OK=1 KO=1 )
max response time 60089 (OK=59989 KO=60089 )
mean response time 15843 (OK=6946 KO=46280 )
std deviation 20476 (OK=9767 KO=18168 )
response time 50th percentile 5495 (OK=4393 KO=60000 )
response time 75th percentile 18146 (OK=6591 KO=60000 )
response time 95th percentile 60000 (OK=29664 KO=60001 )
response time 99th percentile 60001 (OK=56995 KO=60003 )
mean requests/sec 206.997 (OK=160.178 KO=46.818)

---- Response Time Distribution ------------------------------------------------

t < 800 ms 6376 ( 10%)
800 ms <= t < 1200 ms 586 ( 1%)
t >= 1200 ms 40611 ( 66%)
failed 13905 ( 23%)

Specs da minha máquina:

  • Ubuntu 22.04.4 LTS
  • Intel Core i5-6300U
  • 16 GB Ram
  • Docker versão 25.0.3
  • Docker compose versão 2.24.6

Participações de grupos pequenos?

eu e um colega estamos estudando rust, e nos deparamos com essa rinha, achamos a ideia bem legal, e seria nossa primeira vez fazendo algo do tipo na linguagem. Queria confirmar se poderiamos fazer em dupla, visto que em nenhum momento ficou claro sobre!

Não temos experiencia nenhum com a linguagem visto que ambos trabalhamos com node. Mas achamos que esse projeto seria uma ótima "desculpa" para focarmos mais nesse estudo que vem sendo negligenciado a tempo :)

Validação de strings

Nas validações de quantidade de caracteres, podemos assumir que todas as strings serão ascii (sem caracteres especiais)?

Address already in use / Connection refused / Premature close

Olá pessoal!
Sabem me dizer se está faltando algo para eu configurar no Gatling / JDK para esses erros?
Estou executando em windows.

---- Errors --------------------------------------------------------------------

j.n.BindException: Address already in use: getsockopt 9089 (42.96%)
j.n.ConnectException: Connection refused: getsockopt 8077 (38.18%)
j.i.IOException: Premature close 3990 (18.86%)
================================================================================

Obrigado

NGINX: worker_connections are not enough

Atualmente, estou trabalhando em outro projeto para a rinha, utilizando o Spring Webflux. No entanto, tenho enfrentado um problema recorrente ao executá-lo no meu sistema: após aproximadamente 3000 requisições, ele apresenta o erro mencionado no título desta issue. Esse mesmo problemas acontece em alguns dos outros projetos baseados em Webflux que encontrei por aqui e executei na minha maquina.

Já tentei resolver o problema aumentando o limite NOFILE do Linux, tanto utilizando o comando prlimit quanto adicionando algumas configurações ao arquivo /etc/sysctl.conf. E mesmo se eu alterar o arquivo nginx.conf colocando um worker_connections alto, em algum momento esse problema ataca novamente.

Alguém poderia sugerir possíveis soluções para esse problema?

Erro validação: jmesPath(saldo.total).find.is(-25), but actually found -3

Olá, não sei se mais alguém está com esse problema, mas essa validação é a única que está dando problema...

Meu projeto está aqui: https://github.com/jcrjuliano/rinha-q1

image

Já fiz e refiz a lógica várias vezes, inicialmente eu havia feito o código usando design patterns (strategy e factory) para ficar organizado, porém, removi tudo e joguei no controlador direto para diminuir a quantida de beans, mas mesmo assim esse erro não se resolve.

E o estranho que é meio intermitente, e as vezes há uma outra validação que também da o erro:
image

Obrigado.

Pode usar redis ou não?

No documento o primeiro paragrafo de arquitetura cita o texto:

Um banco de dados relacional ou não relacional (exceto bancos de dados que têm como principal caracterísitca o armazenamento de dados em memória, tal como Redis, por exemplo).

E outra parte do texto tem:

Use o bom senso e boa fé, não adicione um banco relacional e um Redis, por exemplo, e use apenas o Redis como armazenamento – afinal, a Rinha é apenas uma brincadeira que fomenta o aprendizado e não a competição desleal.

Pode ou não usar o redis como cache ou algo do tipo?

Teste de validação incorreto

Aqui a validação testa se a inserção mantém a ordem mas ali no teste ele valida inversamente, falhando essas requests de validação:

image

Está errado ou tem algo que eu não estou pegando?

SQLite pode?

@zanfranceschi et al., parabéns pela iniciativa. O README do repositório está muito bem redigido. Fiquei com apenas uma dúvida aqui: usar SQLite, gravando os dados em arquivo, vai valer?

[AJUDA] Nginx não encontra host da api no teste

Olá, ao submeter o projeto para teste a criação do container falha com o seguinte erro host not found in upstream "api01:3000", porém ao rodar o projeto localmente consigo subir o container normalmente.

  • Realizei o build da imagem como manda o README, com docker buildx build --platform linux/amd64
  • O nginx depende do health das apis para subir

docker-compose.logs docker-compose.yml nginx.conf

Conseguem me ajudar a entender o que acontece, por favor?

Dúvida script de testes

Oi @zanfranceschi,

Acha que faz sentido adicionar uma validação da arquitetura minima no script executar-teste-local ?
Tinha pensado em algo bem simples, tipo validar mínimo de containers:

num_containers=$(docker ps -q | wc -l)
if [ "$num_containers" -ge 4 ]; then
    .... ok é no mínimo 4
else
    ... para o teste
fi

E talvez fazer algo com docker stats --no-stream --format ... para pegar a memória total.

A ideia é tentar ajudar/alertar quem tá fazendo errado por engano.
Se fizer sentido, posso tentar abrir um PR.

Até mais!

Sugestão: instruções para o povo que usa ARM

Ótimo vc ter mencionado o $ lscpu, que mostra que a máquina onde os testes vão rodar em uma arch x86_64!

Algumas pessoas podem ter arquiteturas locais diferentes, tipo ARM (caso de quem usa Mac M1, ou quem, por algum motivo, roda num Raspberry PI).

Para estas pessoas (eu incluso), será preciso fazer o build da imagem docker cross-arquitetura. Um simples docker build vai funcionar, mas vai falhar no seu servidor na hora de rodar os testes, na hora de dar o docker run.

Pra compilar de tal forma que funcione em um x86_64, é necessário rodar o docker build da seguinte forma:

$ docker buildx build --platform linux/amd64

Por exemplo: $ docker buildx build --platform linux/amd64 -t ana/minha-api-matadora:latest .


Fica aqui a sugestão e deixo ao seu cargo decidir. Se preferir, posso abrir um PR adicionando isso ao readme.

Posso construir meu próprio load balancer?

Entendi de acordo com o readme que a escolha do load balancer é aberto, desde que tenha round robin para as 2 instâncias da API. Queria saber se posso(ou não) construir um próprio load balancer pras instâncias da api, como uma imagem própria.

Data limite diferentes

Achei essas duas datas limite no README.

"A data limite para enviar sua submissão é 2024-03-10T23:59:59-03:00."
e
"A data/hora limite para fazer pull requests para sua submissão é até 2024-03-29T23:59:59.000-03:00"

Qual devemos considerar?

Att,

Banco de dados

É permitido ter o banco de dados rodando dentro dos containers de api e gravando os dados em um volume único (diretório externo mapeado) compartilhado pelos dois containers?

Teste integridade do extrato em race condition

Existe ou vai existir algum teste para avaliar a integridade do extrato durante transações?
Ex: DURANTE o registro das 10 primeiras transações, ao consultar o extrato em paralelo, a soma das transações deve bater com o saldo total do próprio extrato.

Aproveito para sugerir que aceite o HTTP STATUS 400 quando a request estiver fora do padrão esperado (valor como float, length(descricao) > 10, etc)

Resultado rodando local diferente do que o da Submissão

Olá galera!

Rodei diversas vezes minha submissão localmente e o resultado foi consideravelmente melhor do que o resultado em STATUS-TESTES.

Queria uma ajuda para saber se eu estou executando o teste de maneira errado ou se tem relação com a arquitetura da máquina X a imagem gerada.

Segue o link para a submissão: https://github.com/zanfranceschi/rinha-de-backend-2024-q1/tree/main/participantes/vitor_weirich_java

Agradeço qualquer apoio :)

Resultados rodando localmente:
image
image

Dúvida sobre a estrutura do projeto

Vie que o projeto precisa ser composto de um Load Balance, Banco de Dados e dois serviços http

Tem problema se eu escrever uma única aplicação que faça tudo?

Um mini buffer pro banco de dados com armazenamento em fs
Um serviço http que recebe o trafego e o redireciona para duas threads usando semaforo?

Vai saber... se performance for realmente o fator determinante 🙈

Validação de float

Uma das validações, é enviado o valor de 1.0 esperando o resultado 422. No js, 1.0 e 1 são funcionalmente identicos, seria possivel alterar o valor do teste para algum float "real", como 1.2?

Load balancer

O load balancer precisa necessariamente usar uma estratégia round-robin ou alguma outra estratégia (exemplo aleatória, decidida pelo kernel) é permitida?

[Ajuda] Algum erro que não foi exibido no docker-compose.logs

Opa @zanfranceschi, tudo joia meu caro? Dei uma olhada nos status de execução da minha submissão, vi que está marcado como falha, mas não consegui identificar nenhum erro no docker-compose. Consegue me dar uma força se é algo nos logs que não estou vendo ou o problema está aqui entre a cadeira e o teclado? rs

Tive um pouquinho de dificuldade também pra executar na minha máquina. Pra eu conseguir bater com o nginx local aqui, tive que usar um host network diferente do docker-compose que dei submissão... não sei se é isso afetando, mas como não consegui ver nada nos logs de erro 500, não identifiquei 100%

Logs: https://github.com/zanfranceschi/rinha-de-backend-2024-q1/blob/main/participantes/gabrielfmagalhaes/docker-compose.logs

[AJUDA] - Minha api cai assim que ela inicia

Problema

Assim que a api inicia os testes, ela é derrubada, rodei localmente várias vezes e não consigo identificar qual é o problema, pois em todas as execuções os testes passam com sucesso.

Em cada teste local, eu tento limpar todo o lixo do Docker rodando o comando abaixo

docker stop $(docker ps -aq) && docker rm $(docker ps -aq) && docker rmi $(docker images -q) && docker volume rm $(docker volume ls -q) && docker compose up

O que já tentei

  • Retirar commits assíncronos e outras bruxarias do Postgres
  • Diminuir o pool de conexões do ngnix
  • Aumentar a memória das apis

O que observei

Vi que a aplicação está consumindo toda a memória disponível, mas não consigo encontrar onde existem vazamentos de memória ou bugs.

Screenshot from 2024-02-21 12-55-49

Se alguém passou por isso ou queira ajudar, agradeço demais!

Docker como load balancer

É permitido usar o load balancer incluso do Docker Compose? Visto que o algoritmo dele é Round Robin. Ou é necessário ter um componente à parte?

Comportamento estranho nas validações ("danada").

https://github.com/zanfranceschi/rinha-de-backend-2024-q1/blob/main/load-test/user-files/simulations/rinhabackend/RinhaBackendCrebitosSimulation.scala#L201-L242

Além do comentário 5 consultas simultâneas ao extrato para verificar consistência com apenas 4 chamadas, em uma execução dos testes, obtive o seguinte erro:

> jmesPath(ultimas_transacoes[0].descricao).find.is(danada), but      1 (50.00%)
 actually found 2uVvpSZG39

porém, nos logs (filtrados pelo client_id afetado) obtive:

api01-1  | extrato	2	=>	{"saldo":{"total":0,"limite":80000,"data_extrato":"2024-03-05T19:42:35.139Z"},"ultimas_transacoes":[]}
api01-1  | transacoes	2	{"valor": 1, "tipo": "c", "descricao": "toma"}	=>	{"saldo":1,"limite":80000}
api02-1  | transacoes	2	{"valor": 1, "tipo": "d", "descricao": "devolve"}	=>	{"saldo":0,"limite":80000}
api01-1  | extrato	2	=>	{"saldo":{"total":0,"limite":80000,"data_extrato":"2024-03-05T19:42:35.150Z"},"ultimas_transacoes":[{"valor":1,"tipo":"d","descricao":"devolve","realizada_em":"2024-03-05T19:42:35.148Z"},{"valor":1,"tipo":"c","descricao":"toma","realizada_em":"2024-03-05T19:42:35.143Z"}]}

api01-1  | transacoes	2	{"valor": 1, "tipo": "c", "descricao": "danada"}	=>	{"saldo":1,"limite":80000}
api01-1  | extrato	2	=>	{"saldo":{"total":1,"limite":80000,"data_extrato":"2024-03-05T19:42:35.166Z"},"ultimas_transacoes":[{"valor":1,"tipo":"c","descricao":"danada","realizada_em":"2024-03-05T19:42:35.155Z"},{"valor":1,"tipo":"d","descricao":"devolve","realizada_em":"2024-03-05T19:42:35.148Z"},{"valor":1,"tipo":"c","descricao":"toma","realizada_em":"2024-03-05T19:42:35.143Z"}]}
api02-1  | extrato	2	=>	{"saldo":{"total":1,"limite":80000,"data_extrato":"2024-03-05T19:42:35.168Z"},"ultimas_transacoes":[{"valor":1,"tipo":"c","descricao":"danada","realizada_em":"2024-03-05T19:42:35.155Z"},{"valor":1,"tipo":"d","descricao":"devolve","realizada_em":"2024-03-05T19:42:35.148Z"},{"valor":1,"tipo":"c","descricao":"toma","realizada_em":"2024-03-05T19:42:35.143Z"}]}
api02-1  | extrato	2	=>	{"saldo":{"total":1,"limite":80000,"data_extrato":"2024-03-05T19:42:35.170Z"},"ultimas_transacoes":[{"valor":1,"tipo":"c","descricao":"danada","realizada_em":"2024-03-05T19:42:35.155Z"},{"valor":1,"tipo":"d","descricao":"devolve","realizada_em":"2024-03-05T19:42:35.148Z"},{"valor":1,"tipo":"c","descricao":"toma","realizada_em":"2024-03-05T19:42:35.143Z"}]}
api01-1  | transacoes	2	{"valor": 5294, "tipo": "c", "descricao": "2uVvpSZG39"}	=>	{"saldo":5295,"limite":80000}
api01-1  | extrato	2	=>	{"saldo":{"total":5295,"limite":80000,"data_extrato":"2024-03-05T19:42:36.172Z"},"ultimas_transacoes":[{"valor":5294,"tipo":"c","descricao":"2uVvpSZG39","realizada_em":"2024-03-05T19:42:36.144Z"},{"valor":1,"tipo":"c","descricao":"danada","realizada_em":"2024-03-05T19:42:35.155Z"},{"valor":1,"tipo":"d","descricao":"devolve","realizada_em":"2024-03-05T19:42:35.148Z"},{"valor":1,"tipo":"c","descricao":"toma","realizada_em":"2024-03-05T19:42:35.143Z"}]}
...

Aparentemente, esse "danada", do criteriosClientes, está ocorrendo paralelamente aos creditos/debitos...

Duvida sobre o p75 geral

Nos resultados dos testes parciais da rinha, alguem pode me explicar o que é aquele dado p75 geral, como funciona aquele numero?

Descricão da transação e valor estão inconsistentes

Há casos em que o campo descrição está sendo enviado com mais de 10 caracteres ou vazio. Peguei estes dois logo no começo do teste.

.body(StringBody(s"""{"valor": 1, "tipo": "c", "descricao": "123456789 e mais um pouco"}"""))

e

.body(StringBody(s"""{"valor": 1, "tipo": "c", "descricao": ""}"""))

e

.body(StringBody(s"""{"valor": 1, "tipo": "c", "descricao": null}"""))

A especificação no README diz que o campo é obrigatório, e que deve ter entre 1 e dez caracteres:

- `descricao` deve ser uma string de 1 a 10 caractéres.

Seria bom esclarecer e corrigir, afinal o campo é mesmo obrigatório? Tem mesmo esse limite de dez caracteres?

Percebi que o mesmo está acontecendo para o valor. O README diz que deve ser um inteiro:

- `valor` deve um número inteiro positivo que representa centavos (não vamos trabalhar com frações de centavos). Por exemplo, R$ 10 são 1000 centavos.

Mas nos testes vemos um decimal:

.body(StringBody(s"""{"valor": 1.2, "tipo": "d", "descricao": "devolve"}"""))

Dúvida sobre a declaração dos artefatos

O caminho para o artefato declarado no diretório participantes/nome deve ser o mesmo declarado no projeto participante?

Não ficou claro para mim se ele serviria apenas como um verificador do que foi submetido e o que está no repo ou se o mesmo path seria o utilizado.

Teste de carga não está cobrindo o caso de multiplas requisições paralelas para o mesmo cliente

Fala, pessoal. Estou abrindo essa issue para reportar um problema que identifiquei. Por algum motivo uma API problemática (com Race Condition) está conseguindo passar em todos os casos do teste. A minha suspeita é que como estamos saindo com as requests com ID dos clientes randômicos, não estamos contemplando os casos de N requests paralelas de débito/crédito para o mesmo usuário.

Eu rodei no meu local um projeto de um participante que suspeitei da race condition, e fiz um script (link aqui) para testar a minha suspeita (caso queira o link do repositório participante que está com a race condition, só mandar dm).

O script vai sair com 100 requisições paralelas e cada requisição vai tentar debitar 1000, no final da execução esperamos que o saldo do cliente (id: 1) esteja -100000. Porém, vejam o resultado:

Clique para expandir os responses inconsistentes
{
  '200': 100,
  status_200: [
    { limite: 100000, saldo: -1000 },
    { limite: 100000, saldo: -3000 },
    { limite: 100000, saldo: -1000 },
    { limite: 100000, saldo: -1000 },
    { limite: 100000, saldo: -2000 },
    { limite: 100000, saldo: -4000 },
    { limite: 100000, saldo: -2000 },
    { limite: 100000, saldo: -2000 },
    { limite: 100000, saldo: -3000 },
    { limite: 100000, saldo: -5000 },
    { limite: 100000, saldo: -4000 },
    { limite: 100000, saldo: -3000 },
    { limite: 100000, saldo: -3000 },
    { limite: 100000, saldo: -3000 },
    { limite: 100000, saldo: -3000 },
    { limite: 100000, saldo: -4000 },
    { limite: 100000, saldo: -2000 },
    { limite: 100000, saldo: -3000 },
    { limite: 100000, saldo: -4000 },
    { limite: 100000, saldo: -8000 },
    { limite: 100000, saldo: -4000 },
    { limite: 100000, saldo: -6000 },
    { limite: 100000, saldo: -4000 },
    { limite: 100000, saldo: -5000 },
    { limite: 100000, saldo: -5000 },
    { limite: 100000, saldo: -4000 },
    { limite: 100000, saldo: -5000 },
    { limite: 100000, saldo: -5000 },
    { limite: 100000, saldo: -7000 },
    { limite: 100000, saldo: -6000 },
    { limite: 100000, saldo: -6000 },
    { limite: 100000, saldo: -6000 },
    { limite: 100000, saldo: -7000 },
    { limite: 100000, saldo: -6000 },
    { limite: 100000, saldo: -8000 },
    { limite: 100000, saldo: -7000 },
    { limite: 100000, saldo: -7000 },
    { limite: 100000, saldo: -8000 },
    { limite: 100000, saldo: -7000 },
    { limite: 100000, saldo: -7000 },
    { limite: 100000, saldo: -9000 },
    { limite: 100000, saldo: -8000 },
    { limite: 100000, saldo: -8000 },
    { limite: 100000, saldo: -8000 },
    { limite: 100000, saldo: -8000 },
    { limite: 100000, saldo: -10000 },
    { limite: 100000, saldo: -9000 },
    { limite: 100000, saldo: -9000 },
    { limite: 100000, saldo: -9000 },
    { limite: 100000, saldo: -9000 },
    { limite: 100000, saldo: -10000 },
    { limite: 100000, saldo: -10000 },
    { limite: 100000, saldo: -11000 },
    { limite: 100000, saldo: -10000 },
    { limite: 100000, saldo: -10000 },
    { limite: 100000, saldo: -11000 },
    { limite: 100000, saldo: -10000 },
    { limite: 100000, saldo: -11000 },
    { limite: 100000, saldo: -12000 },
    { limite: 100000, saldo: -12000 },
    { limite: 100000, saldo: -12000 },
    { limite: 100000, saldo: -11000 },
    { limite: 100000, saldo: -12000 },
    { limite: 100000, saldo: -15000 },
    { limite: 100000, saldo: -12000 },
    { limite: 100000, saldo: -13000 },
    { limite: 100000, saldo: -13000 },
    { limite: 100000, saldo: -13000 },
    { limite: 100000, saldo: -13000 },
    { limite: 100000, saldo: -14000 },
    { limite: 100000, saldo: -14000 },
    { limite: 100000, saldo: -13000 },
    { limite: 100000, saldo: -14000 },
    { limite: 100000, saldo: -14000 },
    { limite: 100000, saldo: -15000 },
    { limite: 100000, saldo: -14000 },
    { limite: 100000, saldo: -15000 },
    { limite: 100000, saldo: -15000 },
    { limite: 100000, saldo: -14000 },
    { limite: 100000, saldo: -15000 },
    { limite: 100000, saldo: -16000 },
    { limite: 100000, saldo: -15000 },
    { limite: 100000, saldo: -16000 },
    { limite: 100000, saldo: -16000 },
    { limite: 100000, saldo: -18000 },
    { limite: 100000, saldo: -17000 },
    { limite: 100000, saldo: -17000 },
    { limite: 100000, saldo: -17000 },
    { limite: 100000, saldo: -16000 },
    { limite: 100000, saldo: -17000 },
    { limite: 100000, saldo: -15000 },
    { limite: 100000, saldo: -19000 },
    { limite: 100000, saldo: -18000 },
    { limite: 100000, saldo: -18000 },
    { limite: 100000, saldo: -18000 },
    { limite: 100000, saldo: -18000 },
    { limite: 100000, saldo: -18000 },
    { limite: 100000, saldo: -17000 },
    { limite: 100000, saldo: -19000 },
    { limite: 100000, saldo: -17000 }
  ]
}

Reparem que a última requisição deixou o saldo do cliente como -17000. Ou seja, race condition confirmado.

Após rodar esse script, eu rodei os testes de carga para certificar que essa API seria travada, mas acabou não pegando, como vocês podem ver aqui:

image

Estou abrindo um Pull Request para resolver esse problema.

CI apagando arquivo

Queria entender porque meu arquivo participantes/asfernandes-cpp-haproxy-mongoose-firebird/config/haproxy.cfg foi apagado nesse commit: 14589b2

Pode mais de um projeto por participante?

Notei no exemplo que a participante metafórica Ana adicionou em participantes um diretório ana-01..

Pessoas que quiserem realizar mais de uma versão do desafio podem mandar com esse padrão de nomes? Ex.: ana-01 para um app em PHP e ana-02 para um app em Rust

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.