this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

:tada: created project with endpoints and valkey configured

Pedro Correa db2aac4f

+1033
+1
.envrc
··· 1 + use flake
+2
.gitignore
··· 1 + .direnv/ 2 + build/
+24
README.md
··· 1 + # rinha_2025 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/rinha_2025)](https://hex.pm/packages/rinha_2025) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/rinha_2025/) 5 + 6 + ```sh 7 + gleam add rinha_2025@1 8 + ``` 9 + ```gleam 10 + import rinha_2025 11 + 12 + pub fn main() -> Nil { 13 + // TODO: An example of the project in use 14 + } 15 + ``` 16 + 17 + Further documentation can be found at <https://hexdocs.pm/rinha_2025>. 18 + 19 + ## Development 20 + 21 + ```sh 22 + gleam run # Run the project 23 + gleam test # Run the tests 24 + ```
+19
docker-compose.yml
··· 1 + services: 2 + valkey: 3 + image: valkey/valkey:alpine 4 + container_name: backend-fight-redis 5 + restart: unless-stopped 6 + networks: 7 + - backend 8 + ports: 9 + - "6379:6379" 10 + deploy: 11 + resources: 12 + limits: 13 + cpus: "0.15" 14 + memory: "30MB" 15 + 16 + networks: 17 + backend: 18 + payment-processor: 19 + external: true
+61
flake.lock
··· 1 + { 2 + "nodes": { 3 + "flake-utils": { 4 + "inputs": { 5 + "systems": "systems" 6 + }, 7 + "locked": { 8 + "lastModified": 1731533236, 9 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 + "owner": "numtide", 11 + "repo": "flake-utils", 12 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 + "type": "github" 14 + }, 15 + "original": { 16 + "owner": "numtide", 17 + "repo": "flake-utils", 18 + "type": "github" 19 + } 20 + }, 21 + "nixpkgs": { 22 + "locked": { 23 + "lastModified": 1753750875, 24 + "narHash": "sha256-J1P0aQymehe8AHsID9wwoMjbaYrIB2eH5HftoXhF9xk=", 25 + "owner": "NixOS", 26 + "repo": "nixpkgs", 27 + "rev": "871381d997e4a063f25a3994ce8a9ac595246610", 28 + "type": "github" 29 + }, 30 + "original": { 31 + "owner": "NixOS", 32 + "ref": "nixpkgs-unstable", 33 + "repo": "nixpkgs", 34 + "type": "github" 35 + } 36 + }, 37 + "root": { 38 + "inputs": { 39 + "flake-utils": "flake-utils", 40 + "nixpkgs": "nixpkgs" 41 + } 42 + }, 43 + "systems": { 44 + "locked": { 45 + "lastModified": 1681028828, 46 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 + "owner": "nix-systems", 48 + "repo": "default", 49 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 + "type": "github" 51 + }, 52 + "original": { 53 + "owner": "nix-systems", 54 + "repo": "default", 55 + "type": "github" 56 + } 57 + } 58 + }, 59 + "root": "root", 60 + "version": 7 61 + }
+29
flake.nix
··· 1 + { 2 + description = "A Nix-flake-based Gleam development environment"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 + 7 + flake-utils.url = "github:numtide/flake-utils"; 8 + }; 9 + 10 + outputs = { self, nixpkgs, flake-utils }: 11 + flake-utils.lib.eachDefaultSystem (system: 12 + let 13 + inherit (pkgs.lib) optional optionals; 14 + 15 + pkgs = import nixpkgs { inherit system; }; 16 + in 17 + { 18 + devShells.default = pkgs.mkShell { 19 + buildInputs = with pkgs; [ 20 + erlang 21 + gleam 22 + rebar3 23 + k6 24 + hurl 25 + ]; 26 + }; 27 + } 28 + ); 29 + }
+27
gleam.toml
··· 1 + name = "rinha_2025" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + envoy = ">= 1.0.2 and < 2.0.0" 18 + mist = ">= 5.0.2 and < 6.0.0" 19 + wisp = ">= 1.8.0 and < 2.0.0" 20 + gleam_http = ">= 4.1.1 and < 5.0.0" 21 + gleam_json = ">= 3.0.2 and < 4.0.0" 22 + gleam_erlang = ">= 1.2.0 and < 2.0.0" 23 + valkyrie = ">= 3.0.0 and < 4.0.0" 24 + gleam_otp = ">= 1.0.0 and < 2.0.0" 25 + 26 + [dev-dependencies] 27 + gleeunit = ">= 1.0.0 and < 2.0.0"
+173
load-test/requests.js
··· 1 + import { Httpx } from "https://jslib.k6.io/httpx/0.1.0/index.js"; 2 + import exec from "k6/execution"; 3 + 4 + const initialToken = "123"; 5 + export const token = __ENV.TOKEN ?? initialToken; 6 + 7 + const paymentProcessorDefaultHttp = new Httpx({ 8 + baseURL: "http://localhost:8001", 9 + headers: { 10 + "Content-Type": "application/json", 11 + "X-Rinha-Token": token, 12 + }, 13 + timeout: 1500, 14 + }); 15 + 16 + const paymentProcessorFallbacktHttp = new Httpx({ 17 + baseURL: "http://localhost:8002", 18 + headers: { 19 + "Content-Type": "application/json", 20 + "X-Rinha-Token": token, 21 + }, 22 + timeout: 1500, 23 + }); 24 + 25 + const backendHttp = new Httpx({ 26 + baseURL: "http://localhost:9999", 27 + //baseURL: "http://localhost:5123", 28 + headers: { 29 + "Content-Type": "application/json", 30 + }, 31 + timeout: 1500, 32 + }); 33 + 34 + const paymentProcessorHttp = { 35 + default: paymentProcessorDefaultHttp, 36 + fallback: paymentProcessorFallbacktHttp, 37 + }; 38 + 39 + export async function setPPToken(service, token) { 40 + const httpClient = paymentProcessorHttp[service]; 41 + const params = { headers: { "X-Rinha-Token": initialToken } }; 42 + 43 + const payload = JSON.stringify({ 44 + token: token, 45 + }); 46 + 47 + const response = await httpClient.asyncPut( 48 + "/admin/configurations/token", 49 + payload, 50 + params, 51 + ); 52 + 53 + if (response.status != 204) { 54 + exec.test.abort( 55 + `Erro ao definir token para ${service} (HTTP ${response.status}).`, 56 + ); 57 + } 58 + } 59 + 60 + export async function setPPDelay(service, ms) { 61 + const httpClient = paymentProcessorHttp[service]; 62 + 63 + const payload = JSON.stringify({ 64 + delay: ms, 65 + }); 66 + 67 + const response = await httpClient.asyncPut( 68 + "/admin/configurations/delay", 69 + payload, 70 + ); 71 + 72 + if (response.status != 200) { 73 + exec.test.abort( 74 + `Erro ao definir delay para ${service} (HTTP ${response.status}).`, 75 + ); 76 + } 77 + } 78 + 79 + export async function setPPFailure(service, failure) { 80 + const httpClient = paymentProcessorHttp[service]; 81 + 82 + const payload = JSON.stringify({ 83 + failure: failure, 84 + }); 85 + 86 + const response = await httpClient.asyncPut( 87 + "/admin/configurations/failure", 88 + payload, 89 + ); 90 + 91 + if (response.status != 200) { 92 + exec.test.abort( 93 + `Erro ao definir failure para ${service} (HTTP ${response.status}).`, 94 + ); 95 + } 96 + } 97 + 98 + export async function resetPPDatabase(service) { 99 + const httpClient = paymentProcessorHttp[service]; 100 + const response = await httpClient.asyncPost("/admin/purge-payments"); 101 + 102 + if (response.status != 200) { 103 + exec.test.abort( 104 + `Erro ao resetar database para ${service} (HTTP ${response.status}).`, 105 + ); 106 + } 107 + } 108 + 109 + export async function getPPPaymentsSummary(service, from, to) { 110 + const httpClient = paymentProcessorHttp[service]; 111 + const response = await httpClient.asyncGet( 112 + `/admin/payments-summary?from=${from}&to=${to}`, 113 + ); 114 + 115 + if (response.status == 200) { 116 + return JSON.parse(response.body); 117 + } 118 + 119 + console.error( 120 + `Não foi possível obter resposta de '/admin/payments-summary?from=${from}&to=${to}' para ${service} (HTTP ${response.status})`, 121 + ); 122 + 123 + return { 124 + totalAmount: 0, 125 + totalRequests: 0, 126 + feePerTransaction: 0, 127 + totalFee: 0, 128 + }; 129 + } 130 + 131 + export async function resetBackendDatabase() { 132 + try { 133 + await backendHttp.asyncPost("/purge-payments"); 134 + } catch (error) { 135 + console.info( 136 + "Seu backend provavelmente não possui um endpoint para resetar o banco. Isso não é um problem.", 137 + error.message, 138 + ); 139 + } 140 + } 141 + 142 + export async function getBackendPaymentsSummary(from, to) { 143 + const response = await backendHttp.asyncGet( 144 + `/payments-summary?from=${from}&to=${to}`, 145 + ); 146 + 147 + if (response.status == 200) { 148 + return JSON.parse(response.body); 149 + } 150 + 151 + console.error( 152 + `Não foi possível obter resposta de '/payments-summary?from=${from}&to=${to}' para o backend (HTTP ${response.status})`, 153 + ); 154 + 155 + return { 156 + default: { 157 + totalAmount: 0, 158 + totalRequests: 0, 159 + }, 160 + fallback: { 161 + totalAmount: 0, 162 + totalRequests: 0, 163 + }, 164 + }; 165 + } 166 + 167 + export async function requestBackendPayment(payload) { 168 + const response = await backendHttp.asyncPost( 169 + "/payments", 170 + JSON.stringify(payload), 171 + ); 172 + return response; 173 + }
+394
load-test/rinha.js
··· 1 + import { textSummary } from "https://jslib.k6.io/k6-summary/0.1.0/index.js"; 2 + import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.4.0/index.js"; 3 + import { sleep } from "k6"; 4 + import exec from "k6/execution"; 5 + import { Counter } from "k6/metrics"; 6 + import { 7 + token, 8 + setPPToken, 9 + setPPDelay, 10 + setPPFailure, 11 + resetPPDatabase, 12 + getPPPaymentsSummary, 13 + resetBackendDatabase, 14 + getBackendPaymentsSummary, 15 + requestBackendPayment, 16 + } from "./requests.js"; 17 + 18 + // https://mikemcl.github.io/big.js/ 19 + import Big from "https://cdn.jsdelivr.net/npm/big.js@7.0.1/big.min.js"; 20 + 21 + const MAX_REQUESTS = __ENV.MAX_REQUESTS ?? 500; 22 + 23 + export const options = { 24 + summaryTrendStats: ["p(99)", "count"], 25 + thresholds: { 26 + //http_req_failed: [{ threshold: "rate < 0.01", abortOnFail: false }], 27 + //payments_inconsistency: ["count == 0"] 28 + //http_req_duration: ['p(99) < 50'], 29 + //payments_count: ['count > 3500'], 30 + }, 31 + scenarios: { 32 + payments: { 33 + exec: "payments", 34 + executor: "ramping-vus", 35 + startVUs: 1, 36 + gracefulRampDown: "0s", 37 + stages: [{ target: MAX_REQUESTS, duration: "60s" }], 38 + }, 39 + payments_consistency: { 40 + exec: "checkPaymentsConsistency", 41 + executor: "constant-vus", 42 + //startTime: "5s", 43 + duration: "60s", 44 + vus: "1", 45 + }, 46 + stage_00: { 47 + exec: "define_stage", 48 + startTime: "1s", 49 + executor: "constant-vus", 50 + vus: 1, 51 + duration: "1s", 52 + tags: { 53 + defaultDelay: "0", 54 + defaultFailure: "false", 55 + fallbackDelay: "0", 56 + fallbackFailure: "false", 57 + }, 58 + }, 59 + stage_01: { 60 + exec: "define_stage", 61 + startTime: "10s", 62 + executor: "constant-vus", 63 + vus: 1, 64 + duration: "1s", 65 + tags: { 66 + defaultDelay: "100", 67 + defaultFailure: "false", 68 + fallbackDelay: "0", 69 + fallbackFailure: "false", 70 + }, 71 + }, 72 + stage_02: { 73 + exec: "define_stage", 74 + startTime: "20s", 75 + executor: "constant-vus", 76 + vus: 1, 77 + duration: "1s", 78 + tags: { 79 + defaultDelay: "100", 80 + defaultFailure: "true", 81 + fallbackDelay: "0", 82 + fallbackFailure: "false", 83 + }, 84 + }, 85 + stage_03: { 86 + exec: "define_stage", 87 + startTime: "30s", 88 + executor: "constant-vus", 89 + vus: 1, 90 + duration: "1s", 91 + tags: { 92 + defaultDelay: "2000", 93 + defaultFailure: "true", 94 + fallbackDelay: "1000", 95 + fallbackFailure: "true", 96 + }, 97 + }, 98 + stage_04: { 99 + exec: "define_stage", 100 + startTime: "40s", 101 + executor: "constant-vus", 102 + vus: 1, 103 + duration: "1s", 104 + tags: { 105 + defaultDelay: "20", 106 + defaultFailure: "false", 107 + fallbackDelay: "20", 108 + fallbackFailure: "false", 109 + }, 110 + }, 111 + stage_05: { 112 + exec: "define_stage", 113 + startTime: "50s", 114 + executor: "constant-vus", 115 + vus: 1, 116 + duration: "1s", 117 + tags: { 118 + defaultDelay: "0", 119 + defaultFailure: "false", 120 + fallbackDelay: "5000", 121 + fallbackFailure: "false", 122 + }, 123 + }, 124 + }, 125 + }; 126 + 127 + const transactionsSuccessCounter = new Counter("transactions_success"); 128 + const transactionsFailureCounter = new Counter("transactions_failure"); 129 + const totalTransactionsAmountCounter = new Counter("total_transactions_amount"); 130 + const paymentsInconsistencyCounter = new Counter("payments_inconsistency"); 131 + 132 + const defaultTotalAmountCounter = new Counter("default_total_amount"); 133 + const defaultTotalRequestsCounter = new Counter("default_total_requests"); 134 + const fallbackTotalAmountCounter = new Counter("fallback_total_amount"); 135 + const fallbackTotalRequestsCounter = new Counter("fallback_total_requests"); 136 + 137 + const defaultTotalFeeCounter = new Counter("default_total_fee"); 138 + const fallbackTotalFeeCounter = new Counter("fallback_total_fee"); 139 + 140 + export async function setup() { 141 + await setPPToken("default", token); 142 + await setPPToken("fallback", token); 143 + await resetPPDatabase("default"); 144 + await resetPPDatabase("fallback"); 145 + await resetBackendDatabase(); 146 + } 147 + 148 + const paymentRequestFixedAmount = new Big(19.9); 149 + 150 + export async function teardown() { 151 + const to = new Date(); 152 + const from = new Date(to.getTime() - 70 * 1000); // 1 minuto e 10 segundos atrás 153 + 154 + console.info(`summaries from ${from.toISOString()} to ${to.toISOString()}`); 155 + 156 + const defaultResponse = await getPPPaymentsSummary( 157 + "default", 158 + from.toISOString(), 159 + to.toISOString(), 160 + ); 161 + const fallbackResponse = await getPPPaymentsSummary( 162 + "fallback", 163 + from.toISOString(), 164 + to.toISOString(), 165 + ); 166 + const backendPaymentsSummary = await getBackendPaymentsSummary( 167 + from.toISOString(), 168 + to.toISOString(), 169 + ); 170 + 171 + const totalTransactionsAmount = new Big( 172 + backendPaymentsSummary.default.totalAmount, 173 + ).plus(backendPaymentsSummary.fallback.totalAmount); 174 + 175 + totalTransactionsAmountCounter.add(totalTransactionsAmount.toNumber()); 176 + 177 + defaultTotalAmountCounter.add(backendPaymentsSummary.default.totalAmount); 178 + defaultTotalRequestsCounter.add(backendPaymentsSummary.default.totalRequests); 179 + fallbackTotalAmountCounter.add(backendPaymentsSummary.fallback.totalAmount); 180 + fallbackTotalRequestsCounter.add( 181 + backendPaymentsSummary.fallback.totalRequests, 182 + ); 183 + 184 + const defaultTotalFee = new Big(defaultResponse.feePerTransaction).times( 185 + backendPaymentsSummary.default.totalAmount, 186 + ); 187 + const fallbackTotalFee = new Big(fallbackResponse.feePerTransaction).times( 188 + backendPaymentsSummary.fallback.totalAmount, 189 + ); 190 + 191 + defaultTotalFeeCounter.add(defaultTotalFee.toNumber()); 192 + fallbackTotalFeeCounter.add(fallbackTotalFee.toNumber()); 193 + } 194 + 195 + export async function payments() { 196 + const payload = { 197 + correlationId: uuidv4(), 198 + amount: paymentRequestFixedAmount.toNumber(), 199 + }; 200 + 201 + const response = await requestBackendPayment(payload); 202 + 203 + if ([200, 201, 202, 204].includes(response.status)) { 204 + transactionsSuccessCounter.add(1); 205 + transactionsFailureCounter.add(0); 206 + } else { 207 + transactionsSuccessCounter.add(0); 208 + transactionsFailureCounter.add(1); 209 + } 210 + 211 + sleep(1); 212 + } 213 + 214 + export async function checkPaymentsConsistency() { 215 + const now = new Date(); 216 + 217 + const from = new Date(now - 1000 * 10).toISOString(); 218 + const to = new Date(now - 100).toISOString(); 219 + 220 + const defaultAdminPaymentsSummaryPromise = getPPPaymentsSummary( 221 + "default", 222 + from, 223 + to, 224 + ); 225 + const fallbackAdminPaymentsSummaryPromise = getPPPaymentsSummary( 226 + "fallback", 227 + from, 228 + to, 229 + ); 230 + const backendPaymentsSummaryPromise = getBackendPaymentsSummary(from, to); 231 + 232 + const [ 233 + defaultAdminPaymentsSummary, 234 + fallbackAdminPaymentsSummary, 235 + backendPaymentsSummary, 236 + ] = await Promise.all([ 237 + defaultAdminPaymentsSummaryPromise, 238 + fallbackAdminPaymentsSummaryPromise, 239 + backendPaymentsSummaryPromise, 240 + ]); 241 + 242 + const inconsistencies = Math.abs( 243 + backendPaymentsSummary.default.totalRequests - 244 + defaultAdminPaymentsSummary.totalRequests + 245 + (backendPaymentsSummary.fallback.totalRequests - 246 + fallbackAdminPaymentsSummary.totalRequests), 247 + ); 248 + 249 + paymentsInconsistencyCounter.add(inconsistencies); 250 + 251 + if (inconsistencies > 0) { 252 + console.warn(`${inconsistencies} inconsistências encontradas.`); 253 + } 254 + 255 + sleep(10); 256 + } 257 + 258 + export async function define_stage() { 259 + const defaultMs = parseInt(exec.vu.metrics.tags["defaultDelay"]); 260 + const fallbackMs = parseInt(exec.vu.metrics.tags["fallbackDelay"]); 261 + const defaultFailure = exec.vu.metrics.tags["defaultFailure"] === "true"; 262 + const fallbackFailure = exec.vu.metrics.tags["fallbackFailure"] === "true"; 263 + 264 + await setPPDelay("default", defaultMs); 265 + await setPPDelay("fallback", fallbackMs); 266 + 267 + await setPPFailure("default", defaultFailure); 268 + await setPPFailure("fallback", fallbackFailure); 269 + 270 + sleep(1); 271 + } 272 + 273 + export function handleSummary(data) { 274 + const total_transactions_requested = 275 + data.metrics.transactions_success.values.count; 276 + const actual_total_amount = 277 + data.metrics.total_transactions_amount.values.count; 278 + 279 + const default_total_fee = data.metrics.default_total_fee.values.count; 280 + const fallback_total_fee = data.metrics.fallback_total_fee.values.count; 281 + const total_fee = new Big(default_total_fee) 282 + .plus(fallback_total_fee) 283 + .toNumber(); 284 + 285 + const p_99 = new Big( 286 + data.metrics["http_req_duration{expected_response:true}"].values["p(99)"], 287 + ) 288 + .round(2) 289 + .toNumber(); 290 + const p_99_bonus = Math.max( 291 + new Big((11 - p_99) * 0.02).round(2).toNumber(), 292 + 0, 293 + ); 294 + const contains_inconsistencies = 295 + data.metrics.payments_inconsistency.values.count > 0; 296 + 297 + const inconsistencies_fine = contains_inconsistencies ? 0.35 : 0; 298 + 299 + // caixa dois 300 + const lag = 301 + data.metrics.transactions_success.values.count - 302 + (data.metrics.default_total_requests.values.count + 303 + data.metrics.fallback_total_requests.values.count); 304 + const slush_fund = lag < 0; 305 + 306 + const liquid_partial_amount = new Big(actual_total_amount) 307 + .minus(total_fee) 308 + .toNumber(); 309 + 310 + const liquid_amount = new Big(liquid_partial_amount) 311 + .plus(new Big(liquid_partial_amount).times(p_99_bonus)) 312 + .minus(new Big(liquid_partial_amount).times(inconsistencies_fine)) 313 + .toNumber(); 314 + 315 + const name = __ENV.PARTICIPANT ?? "anonymous"; 316 + 317 + const custom_data = { 318 + participante: name, 319 + total_liquido: liquid_amount, 320 + total_bruto: actual_total_amount, 321 + total_taxas: total_fee, 322 + descricao: 323 + "'total_liquido' é sua pontuação final. Equivale ao seu lucro. Fórmula: total_liquido + (total_liquido * p99.bonus) - (total_liquido * multa.porcentagem)", 324 + p99: { 325 + valor: `${p_99}ms`, 326 + bonus: `${new Big(p_99_bonus).times(100)}%`, 327 + max_requests: MAX_REQUESTS, 328 + descricao: "Fórmula para o bônus: max((11 - p99.valor) * 0.02, 0)", 329 + }, 330 + multa: { 331 + porcentagem: inconsistencies_fine, 332 + total: new Big(liquid_partial_amount) 333 + .times(inconsistencies_fine) 334 + .toNumber(), 335 + composicao: { 336 + num_inconsistencias: data.metrics.payments_inconsistency.values.count, 337 + descricao: "Se 'num_inconsistencias' > 0, há multa de 35%.", 338 + }, 339 + }, 340 + caixa_dois: { 341 + detectado: slush_fund, 342 + descricao: 343 + "Se 'lag' for negativo, significa que seu backend registrou mais pagamentos do que solicitado, automaticamente desclassificando sua submissão!", 344 + }, 345 + lag: { 346 + num_pagamentos_total: 347 + data.metrics.default_total_requests.values.count + 348 + data.metrics.fallback_total_requests.values.count, 349 + num_pagamentos_solicitados: 350 + data.metrics.transactions_success.values.count, 351 + lag: 352 + data.metrics.transactions_success.values.count - 353 + (data.metrics.default_total_requests.values.count + 354 + data.metrics.fallback_total_requests.values.count), 355 + descricao: 356 + "Lag é a diferença entre a quantidade de solicitações de pagamentos e o que foi realmente computado pelo backend. Mostra a perda de pagamentos possivelmente por estarem enfileirados.", 357 + }, 358 + pagamentos_solicitados: { 359 + qtd_sucesso: data.metrics.transactions_success.values.count, 360 + qtd_falha: data.metrics.transactions_failure.values.count, 361 + descricao: 362 + "'qtd_sucesso' foram requests bem sucedidos para 'POST /payments' e 'qtd_falha' os requests com erro.", 363 + }, 364 + pagamentos_realizados_default: { 365 + total_bruto: data.metrics.default_total_amount.values.count, 366 + num_pagamentos: data.metrics.default_total_requests.values.count, 367 + total_taxas: data.metrics.default_total_fee.values.count, 368 + descricao: 369 + "Informações do backend sobre solicitações de pagamento para o Payment Processor Default.", 370 + }, 371 + pagamentos_realizados_fallback: { 372 + total_bruto: data.metrics.fallback_total_amount.values.count, 373 + num_pagamentos: data.metrics.fallback_total_requests.values.count, 374 + total_taxas: data.metrics.fallback_total_fee.values.count, 375 + descricao: 376 + "Informações do backend sobre solicitações de pagamento para o Payment Processor Fallback.", 377 + }, 378 + }; 379 + 380 + const result = { 381 + stdout: textSummary(data), 382 + }; 383 + 384 + const participant = __ENV.PARTICIPANT; 385 + let summaryJsonFileName = `../participantes/${participant}/partial-results.json`; 386 + 387 + if (participant == undefined) { 388 + summaryJsonFileName = `./partial-results.json`; 389 + } 390 + 391 + result[summaryJsonFileName] = JSON.stringify(custom_data, null, 2); 392 + 393 + return result; 394 + }
+45
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "bath", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_deque", "gleam_erlang", "gleam_otp", "gleam_stdlib", "logging"], otp_app = "bath", source = "hex", outer_checksum = "BB7A25E5177BC80E4106BC691E026F756A606E9752350F938F2284DB81D8A2C9" }, 6 + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, 7 + { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, 8 + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, 9 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 10 + { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, 11 + { name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" }, 12 + { name = "gleam_erlang", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "F91CE62A2D011FA13341F3723DB7DB118541AAA5FE7311BD2716D018F01EF9E3" }, 13 + { name = "gleam_http", version = "4.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "DD0271B32C356FB684EC7E9F48B1E835D0480168848581F68983C0CC371405D4" }, 14 + { name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" }, 15 + { name = "gleam_otp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "7020E652D18F9ABAC9C877270B14160519FA0856EE80126231C505D719AD68DA" }, 16 + { name = "gleam_stdlib", version = "0.62.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "DC8872BC0B8550F6E22F0F698CFE7F1E4BDA7312FDEB40D6C3F44C5B706C8310" }, 17 + { name = "gleam_time", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "DCDDC040CE97DA3D2A925CDBBA08D8A78681139745754A83998641C8A3F6587E" }, 18 + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, 19 + { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" }, 20 + { name = "glisten", version = "8.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "534BB27C71FB9E506345A767C0D76B17A9E9199934340C975DC003C710E3692D" }, 21 + { name = "gramps", version = "3.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "75F0F20C867A6217CBB632A7E563568D6A6366B850815041E8E0B4F179681E53" }, 22 + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, 23 + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, 24 + { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, 25 + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, 26 + { name = "mist", version = "5.0.2", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "0716CE491EA13E1AA1EFEC4B427593F8EB2B953B6EBDEBE41F15BE3D06A22918" }, 27 + { name = "mug", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "49AD2B71690C6A615453D272300951C4BDE19FBF55B167D9C951F5CD89FEC820" }, 28 + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, 29 + { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" }, 30 + { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, 31 + { name = "valkyrie", version = "3.0.0", build_tools = ["gleam"], requirements = ["bath", "gleam_erlang", "gleam_otp", "gleam_stdlib", "gleam_time", "mug"], otp_app = "valkyrie", source = "hex", outer_checksum = "E418ADDD0CB7A51A17F70D5219A213D17717D82D1B75B3F6919AE0AA40361F3E" }, 32 + { name = "wisp", version = "1.8.0", build_tools = ["gleam"], requirements = ["directories", "exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "0FE9049AFFB7C8D5FC0B154EEE2704806F4D51B97F44925D69349B3F4F192957" }, 33 + ] 34 + 35 + [requirements] 36 + envoy = { version = ">= 1.0.2 and < 2.0.0" } 37 + gleam_erlang = { version = ">= 1.2.0 and < 2.0.0" } 38 + gleam_http = { version = ">= 4.1.1 and < 5.0.0" } 39 + gleam_json = { version = ">= 3.0.2 and < 4.0.0" } 40 + gleam_otp = { version = ">= 1.0.0 and < 2.0.0" } 41 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 42 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 43 + mist = { version = ">= 5.0.2 and < 6.0.0" } 44 + valkyrie = { version = ">= 3.0.0 and < 4.0.0" } 45 + wisp = { version = ">= 1.8.0 and < 2.0.0" }
+96
payment-processor/docker-compose.yml
··· 1 + x-service-templates: 2 + payment-processor: &payment-processor 3 + image: zanfranceschi/payment-processor:amd64-20250728111932 4 + networks: 5 + - payment-processor 6 + - payment-processor-db 7 + deploy: 8 + restart_policy: 9 + condition: on-failure 10 + delay: 2s 11 + max_attempts: 10 12 + window: 5s 13 + resources: 14 + limits: 15 + cpus: "1.5" 16 + memory: "100MB" 17 + 18 + payment-processor-db: &payment-processor-db 19 + image: postgres:17-alpine 20 + command: postgres -c checkpoint_timeout=600 -c max_wal_size=4096 -c synchronous_commit=0 -c fsync=0 -c full_page_writes=0 21 + networks: 22 + - payment-processor-db 23 + environment: 24 + - POSTGRES_PASSWORD=postgres 25 + - POSTGRES_USER=postgres 26 + - POSTGRES_DB=rinha 27 + deploy: 28 + restart_policy: 29 + condition: on-failure 30 + delay: 2s 31 + max_attempts: 10 32 + window: 5s 33 + resources: 34 + limits: 35 + cpus: "1.5" 36 + memory: "250MB" 37 + 38 + services: 39 + payment-processor-1: 40 + <<: *payment-processor 41 + container_name: payment-processor-default 42 + hostname: payment-processor-default 43 + environment: 44 + - TRANSACTION_FEE=0.05 45 + - RATE_LIMIT_SECONDS=5 46 + - INITIAL_TOKEN=123 47 + - DB_CONNECTION_STRING=Host=/var/run/postgresql;Port=5432;Database=rinha;Username=postgres;Password=postgres;Minimum Pool Size=15;Maximum Pool Size=20;Connection Pruning Interval=3 48 + ports: 49 + - 8001:8080 50 + volumes: 51 + - postgres-socket-01:/var/run/postgresql 52 + depends_on: 53 + - payment-processor-db-1 54 + 55 + payment-processor-db-1: 56 + <<: *payment-processor-db 57 + container_name: payment-processor-default-db 58 + hostname: payment-processor-default-db 59 + volumes: 60 + - ./init.sql:/docker-entrypoint-initdb.d/init.sql 61 + - postgres-socket-01:/var/run/postgresql 62 + 63 + payment-processor-2: 64 + <<: *payment-processor 65 + container_name: payment-processor-fallback 66 + hostname: payment-processor-fallback 67 + environment: 68 + - TRANSACTION_FEE=0.15 69 + - RATE_LIMIT_SECONDS=5 70 + - INITIAL_TOKEN=123 71 + - DB_CONNECTION_STRING=Host=/var/run/postgresql;Port=5432;Database=rinha;Username=postgres;Password=postgres;Minimum Pool Size=15;Maximum Pool Size=20;Connection Pruning Interval=3 72 + ports: 73 + - 8002:8080 74 + volumes: 75 + - postgres-socket-02:/var/run/postgresql 76 + depends_on: 77 + - payment-processor-db-2 78 + 79 + payment-processor-db-2: 80 + <<: *payment-processor-db 81 + container_name: payment-processor-fallback-db 82 + hostname: payment-processor-fallback-db 83 + volumes: 84 + - ./init.sql:/docker-entrypoint-initdb.d/init.sql 85 + - postgres-socket-02:/var/run/postgresql 86 + 87 + networks: 88 + payment-processor: 89 + name: payment-processor 90 + driver: bridge 91 + payment-processor-db: 92 + driver: bridge 93 + 94 + volumes: 95 + postgres-socket-01: 96 + postgres-socket-02:
+7
payment-processor/init.sql
··· 1 + CREATE UNLOGGED TABLE payments ( 2 + correlationId UUID PRIMARY KEY, 3 + amount DECIMAL NOT NULL, 4 + requested_at TIMESTAMP NOT NULL 5 + ); 6 + 7 + CREATE INDEX payments_requested_at ON payments (requested_at);
+5
request.hurl
··· 1 + POST http://localhost:8000/payments 2 + { 3 + "amount": 19.90, 4 + "correlationId": "" 5 + }
+21
src/redis.gleam
··· 1 + import gleam/erlang/process 2 + import gleam/option 3 + import valkyrie 4 + 5 + pub fn create_supervised_pool() { 6 + let name = process.new_name("connection_pool") 7 + 8 + #( 9 + name, 10 + valkyrie.default_config() 11 + |> valkyrie.supervised_pool( 12 + size: 10, 13 + name: option.Some(name), 14 + timeout: 1000, 15 + ), 16 + ) 17 + } 18 + 19 + pub fn enqueue_payments(body: List(String), conn: valkyrie.Connection) { 20 + valkyrie.lpush(conn, "payments_created", body, 1000) 21 + }
+21
src/rinha_2025.gleam
··· 1 + import gleam/erlang/process 2 + import gleam/otp/static_supervisor as supervisor 3 + import redis 4 + import valkyrie 5 + import web/server 6 + import web/web 7 + 8 + pub fn main() -> Nil { 9 + let #(valkey_pool_name, valkey_pool) = redis.create_supervised_pool() 10 + 11 + let ctx = 12 + server.Context(valkye_conn: valkyrie.named_connection(valkey_pool_name)) 13 + 14 + let assert Ok(_) = 15 + supervisor.new(supervisor.OneForOne) 16 + |> supervisor.add(valkey_pool) 17 + |> supervisor.add(web.create_server_supervised(ctx)) 18 + |> supervisor.start 19 + 20 + process.sleep_forever() 21 + }
+43
src/web/controllers/payment_controller.gleam
··· 1 + import gleam/dynamic 2 + import gleam/dynamic/decode 3 + import gleam/json 4 + import gleam/list 5 + import redis 6 + import web/server 7 + import wisp 8 + 9 + type PaymentRequest { 10 + PaymentRequest(correlation_id: String, amount: Float) 11 + } 12 + 13 + fn decode_payment_body( 14 + body: dynamic.Dynamic, 15 + cb: fn(PaymentRequest) -> wisp.Response, 16 + ) { 17 + let decoder = { 18 + use correlation <- decode.field("correlationId", decode.string) 19 + use amount <- decode.field("amount", decode.float) 20 + 21 + decode.success(PaymentRequest(correlation_id: correlation, amount: amount)) 22 + } 23 + case decode.run(body, decoder) { 24 + Ok(body) -> cb(body) 25 + Error(_) -> wisp.bad_request() 26 + } 27 + } 28 + 29 + pub fn handle_payment_post(req: wisp.Request, ctx: server.Context) { 30 + use json <- wisp.require_json(req) 31 + use body <- decode_payment_body(json) 32 + 33 + let assert Ok(_) = 34 + echo json.object([ 35 + #("correlationId", json.string(body.correlation_id)), 36 + #("amount", json.float(body.amount)), 37 + ]) 38 + |> json.to_string 39 + |> list.wrap 40 + |> redis.enqueue_payments(ctx.valkye_conn) 41 + 42 + wisp.no_content() 43 + }
+14
src/web/middleware.gleam
··· 1 + import wisp 2 + 3 + pub fn middleware( 4 + req: wisp.Request, 5 + handle_request: fn(wisp.Request) -> wisp.Response, 6 + ) -> wisp.Response { 7 + let req = wisp.method_override(req) 8 + 9 + use <- wisp.log_request(req) 10 + use <- wisp.rescue_crashes 11 + use req <- wisp.handle_head(req) 12 + 13 + handle_request(req) 14 + }
+21
src/web/router.gleam
··· 1 + import gleam/http 2 + import web/controllers/payment_controller 3 + import web/middleware 4 + import web/server 5 + import wisp 6 + 7 + pub fn handle_request(req: wisp.Request, ctx: server.Context) -> wisp.Response { 8 + use req <- middleware.middleware(req) 9 + 10 + case wisp.path_segments(req) { 11 + ["payments"] -> { 12 + use <- wisp.require_method(req, http.Post) 13 + payment_controller.handle_payment_post(req, ctx) 14 + } 15 + ["payments-summary"] -> { 16 + use <- wisp.require_method(req, http.Get) 17 + todo 18 + } 19 + _ -> wisp.not_found() 20 + } 21 + }
+5
src/web/server.gleam
··· 1 + import valkyrie 2 + 3 + pub type Context { 4 + Context(valkye_conn: valkyrie.Connection) 5 + }
+12
src/web/web.gleam
··· 1 + import mist 2 + import web/router 3 + import web/server 4 + import wisp/wisp_mist 5 + 6 + pub fn create_server_supervised(ctx: server.Context) { 7 + router.handle_request(_, ctx) 8 + |> wisp_mist.handler("secret") 9 + |> mist.new 10 + |> mist.port(8000) 11 + |> mist.supervised 12 + }
+13
test/rinha_2025_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }