Skip to content

Commit 384070e

Browse files
committed
docs: sync README and wiki with code
1 parent a7ae740 commit 384070e

9 files changed

Lines changed: 304 additions & 92 deletions

File tree

.github/workflows/build-check.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ jobs:
2727
- name: Build the application
2828
run: cargo build --release --manifest-path ./src/WebApi/Cargo.toml
2929

30+
- name: Check README and wiki drift
31+
run: python3 scripts/check_docs_drift.py
32+
3033
container-test:
3134
runs-on: ubuntu-latest
3235
steps:

README.md

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,27 @@
1010

1111
## About
1212

13-
A Rust implementation of the Brazilian backend challenge Rinha de Backend 2024/Q1, where a fictional bank API must handle concurrent transactions under strict resource constraints (1.5 CPU, 550MB RAM total). Built as a minimal single-file API (~140 lines) using Actix-web 4 with Tokio async runtime and SQLx 0.8 for compile-time checked PostgreSQL queries.
13+
A Rust implementation of the Brazilian backend challenge Rinha de Backend 2024/Q1, where a fictional bank API must handle concurrent transactions under strict resource constraints (1.5 CPU, 550MB RAM total). The current API is a single `src/WebApi/main.rs` entrypoint (173 total lines at this revision) using Actix-web 4 with the Tokio runtime and SQLx 0.8 for PostgreSQL access.
1414

1515
## Tech Stack
1616

17-
| Technology | Version | Purpose |
18-
|-----------|---------|---------|
19-
| Rust | 1.94 | API implementation |
17+
| Technology | Version / Source | Purpose |
18+
|-----------|------------------|---------|
19+
| Rust | edition 2024; Docker builder `rust:1.95`; CI uses `dtolnay/rust-toolchain@stable` | API implementation |
2020
| Actix-web | 4 | Async HTTP framework |
21-
| SQLx | 0.8 | Compile-time checked PostgreSQL driver |
22-
| Tokio | 1 | Async runtime |
23-
| PostgreSQL | 16.7 | Database with stored procedures |
24-
| NGINX | 1.27 | Reverse proxy and load balancer (least_conn) |
25-
| Docker | - | Multi-stage build and orchestration |
26-
| k6 | - | Stress testing |
21+
| SQLx | 0.8 | PostgreSQL driver with offline query metadata (`.sqlx/`) |
22+
| Tokio | 1 (`full`) | Async runtime |
23+
| PostgreSQL | 16.7 Alpine | Database with stored procedures |
24+
| NGINX | 1.27 Alpine | Reverse proxy and load balancer (`least_conn`) |
25+
| Docker | Compose + Buildx | Multi-stage image build and orchestration |
26+
| k6 | `ghcr.io/jonathanperis/rinha2-back-end-k6:latest` | Stress testing |
2727

2828
## Architecture
2929

3030
```
3131
NGINX (:9999, least_conn)
32-
├── webapi1-rust (:8080, 0.4 CPU, 100MB)
33-
├── webapi2-rust (:8080, 0.4 CPU, 100MB)
32+
├── webapi1-rust (:8080, 0.4 CPU, 100MB; host :6968 in dev)
33+
├── webapi2-rust (:8080, 0.4 CPU, 100MB; host :6969 in dev)
3434
└── PostgreSQL (0.5 CPU, 330MB)
3535
├── InsertTransacao() — atomic balance update + validation
3636
└── GetSaldoClienteById() — statement with JSONB aggregation
@@ -45,16 +45,16 @@ NGINX (:9999, least_conn)
4545

4646
## Features
4747

48-
- Minimal single-file API implementation (~140 lines of Rust)
49-
- Zero-cost async with Tokio runtime and Actix-web
50-
- SQLx compile-time query validation with offline cache (`.sqlx/`)
51-
- PostgreSQL stored procedures for atomic server-side business logic
52-
- UNLOGGED tables for maximum write throughput (no WAL)
53-
- Lazy static HashMap for client validation (no DB round-trip)
54-
- PostgreSQL tuned: `synchronous_commit=0`, `fsync=0`, `full_page_writes=0`
55-
- Multi-platform Docker image (amd64/arm64) published to GHCR
56-
- Observability stack with Prometheus, Grafana, and InfluxDB
57-
- All requests under 800ms at 250MB RAM usage (60% below limit)
48+
- Single Rust API entrypoint (`src/WebApi/main.rs`, 173 total lines at this revision)
49+
- Actix-web route handlers for transactions, statements, and `/healthz`
50+
- SQLx compile-time query validation with offline cache (`src/WebApi/.sqlx/`)
51+
- PostgreSQL stored procedures for server-side balance updates and statement aggregation
52+
- UNLOGGED `Clientes` and `Transacoes` tables for challenge-oriented write throughput
53+
- Lazy static `HashMap` for the five seeded client limits (no DB round-trip for client existence checks)
54+
- PostgreSQL tuned for benchmark throughput: `synchronous_commit=0`, `fsync=0`, `full_page_writes=0`
55+
- Multi-platform GHCR release flow: `latest` is assembled as a multi-arch manifest from amd64 and arm64 builds; `latest-arm64` is also pushed during the arm64 leg
56+
- Development observability stack with Prometheus, Grafana, InfluxDB, and postgres-exporter
57+
- Committed stress-test report artifacts under `docs/public/reports/`; treat generated reports, not hard-coded README numbers, as the performance source of truth
5858

5959
## Getting Started
6060

@@ -70,50 +70,68 @@ cd rinha2-back-end-rust
7070
docker compose up nginx -d --build
7171
```
7272

73-
API available at `http://localhost:9999`
73+
API available at `http://localhost:9999`.
74+
75+
The dev compose file also exposes the API instances directly on `localhost:6968` and `localhost:6969`; use the NGINX port (`9999`) for challenge-compatible requests.
7476

7577
### API Endpoints
7678

7779
| Method | Path | Status Codes | Description |
7880
|--------|------|-------------|-------------|
79-
| POST | `/clientes/{id}/transacoes` | 200, 404, 422 | Submit debit or credit transaction |
80-
| GET | `/clientes/{id}/extrato` | 200, 404 | Get account balance statement |
81-
| GET | `/healthz` | 200 | Health check |
81+
| POST | `/clientes/{id}/transacoes` | 200, 404, 422 | Submit debit or credit transaction for client IDs 1-5 |
82+
| GET | `/clientes/{id}/extrato` | 200, 404 | Get account balance statement with the 10 most recent transactions |
83+
| GET | `/healthz` | 200 | Health check returning `Healthy` |
84+
85+
Transaction payload validation is handled in Rust before the stored procedure call: `tipo` must be `c` or `d`, `descricao` must be non-empty and at most 10 characters, and `valor` must be positive.
8286

8387
### Run Stress Tests
8488

8589
```bash
8690
docker compose up k6 --build --force-recreate
8791
```
8892

93+
The dev compose path runs k6 in `MODE=dev` with InfluxDB/Grafana export. The production compose file used by the release workflow runs k6 in `MODE=prod` and exports an HTML report artifact.
94+
8995
## Project Structure
9096

9197
```
9298
rinha2-back-end-rust/
9399
├── src/WebApi/
94-
│ ├── main.rs # Complete API (~140 lines)
95-
│ ├── Cargo.toml # Dependencies
96-
│ ├── Dockerfile # Multi-stage: rust:1.94 → debian:bookworm-slim
100+
│ ├── main.rs # Complete API entrypoint (173 total lines at this revision)
101+
│ ├── Cargo.toml # Rust 2024 package and dependencies
102+
│ ├── Dockerfile # Multi-stage: rust:1.95 → debian:bookworm-slim
97103
│ └── .sqlx/ # SQLx offline query cache
98104
├── docker-entrypoint-initdb.d/
99-
│ └── rinha.dump.sql # Schema + stored procedures + seed data
100-
├── docker-compose.yml # Dev stack with observability
101-
├── prod/docker-compose.yml # Prod stack with GHCR images
105+
│ └── rinha.dump.sql # UNLOGGED schema + stored procedures + seed data
106+
├── docker-compose.yml # Dev stack: APIs, PostgreSQL, NGINX, observability, k6
107+
├── prod/docker-compose.yml # Release/load-test stack using GHCR images and prod k6 mode
102108
├── nginx.conf # Load balancer config
103-
└── .github/workflows/ # CI/CD (build-check, main-release, codeql)
109+
├── docs/ # Astro/Bun GitHub Pages site and wiki markdown
110+
├── scripts/check_docs_drift.py # Source-backed README/wiki drift guard
111+
└── .github/workflows/ # build-check, main-release, deploy, codeql
104112
```
105113

106114
## CI/CD
107115

108116
| Workflow | Trigger | Description |
109117
|----------|---------|-------------|
110-
| Build Check | Pull requests | Cargo build (release) + Docker health check |
111-
| Main Release | Push to main | Build + Multi-platform Docker push (amd64/arm64) to GHCR + k6 load test + GitHub Pages report |
112-
| CodeQL | Push/PR + weekly | Security and quality analysis for Rust |
113-
| Deploy | Push to main | Deploy documentation to GitHub Pages |
118+
| Build Check | Pull requests to `main`; manual dispatch | Rust release build, docs drift guard, and Docker Compose `/healthz` test |
119+
| Main Release | Push to `main`; manual dispatch | Rust release build, amd64 + arm64 GHCR image pushes, multi-arch manifest merge, production compose healthcheck, k6 report artifact upload |
120+
| CodeQL | Push to `main`, pull requests to `main`, Monday weekly schedule | Rust CodeQL analysis with `security-and-quality` queries |
121+
| Deploy to GitHub Pages | Push to `main`; manual dispatch | Reusable shared workflow that builds `docs/` with Bun and deploys GitHub Pages |
114122

115123
**Docker image:** `ghcr.io/jonathanperis/rinha2-back-end-rust:latest`
116124

125+
## Documentation Drift Guard
126+
127+
README and wiki facts that mirror code, Docker, Compose, or workflow state are checked by:
128+
129+
```bash
130+
python3 scripts/check_docs_drift.py
131+
```
132+
133+
The guard verifies high-value source-backed facts such as API line count, Docker builder version, Rust edition, endpoint coverage, workflow triggers, wiki navigation coverage, and known-stale phrases.
134+
117135
## License
118136

119137
MIT — see [LICENSE](LICENSE)

docs/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"dev": "astro dev",
88
"build": "astro build",
99
"preview": "astro preview",
10-
"lint": "eslint"
10+
"lint": "eslint",
11+
"check:drift": "python3 ../scripts/check_docs_drift.py"
1112
},
1213
"dependencies": {
1314
"@astrojs/sitemap": "^3",

docs/wiki/architecture.md

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,49 @@
22

33
## Overview
44

5-
The system follows a shared architecture across all Rinha de Backend implementations: two API instances behind an Nginx reverse proxy, with a single PostgreSQL database and an observability stack.
5+
The system follows the Rinha de Backend topology used by this repository: two Rust/Actix-web API containers behind NGINX, one PostgreSQL database, and optional observability/load-test services in the development compose file.
66

77
## Services
88

99
| Service | Role | CPU | RAM |
1010
|---------|------|-----|-----|
11-
| webapi1 | Rust/Actix-web 4 API instance (SQLx 0.8 + Tokio) | 0.4 | 100MB |
12-
| webapi2 | Rust/Actix-web 4 API instance (SQLx 0.8 + Tokio) | 0.4 | 100MB |
13-
| nginx | Reverse proxy / load balancer (least-conn) | 0.2 | 20MB |
14-
| postgresql | Database with stored procedures | 0.5 | 330MB |
15-
| k6 | Load testing | (not counted) | (not counted) |
16-
| grafana + influxdb | Observability dashboards | (not counted) | (not counted) |
11+
| `webapi1-rust` | Actix-web 4 API instance using SQLx 0.8 + Tokio | 0.4 | 100MB |
12+
| `webapi2-rust` | Actix-web 4 API instance using SQLx 0.8 + Tokio | 0.4 | 100MB |
13+
| `nginx` | Reverse proxy / load balancer on port 9999 (`least_conn`) | 0.2 | 20MB |
14+
| `db` | PostgreSQL 16.7 Alpine with stored procedures | 0.5 | 330MB |
15+
| `k6` | Shared load-test runner (`MODE=dev` in dev compose, `MODE=prod` in prod compose) | not counted | not counted |
16+
| `prometheus`, `grafana`, `influxdb`, `postgres-exporter` | Development observability stack | not counted | not counted |
17+
18+
The compose CPU/RAM limits for the challenge services total 1.5 CPU and 550MB.
1719

1820
## Load Balancing
1921

20-
Nginx uses `least_conn` strategy to distribute requests across the two API instances.
22+
NGINX listens on `:9999` and proxies all routes to an upstream named `api` with `least_conn` across `webapi1-rust:8080` and `webapi2-rust:8080`. The dev compose file also maps the two API containers to host ports `6968` and `6969`, but challenge-compatible traffic should go through NGINX.
23+
24+
## API Runtime
25+
26+
The API is a single Rust entrypoint (`src/WebApi/main.rs`, 173 total lines at this revision):
27+
28+
- `GET /clientes/{id}/extrato` validates the client ID against a lazy static `HashMap`, then calls `GetSaldoClienteById($1)`.
29+
- `POST /clientes/{id}/transacoes` validates the client ID and payload (`tipo`, `descricao`, `valor`), then calls `InsertTransacao($1, $2, $3, $4)`.
30+
- `GET /healthz` returns `Healthy` for compose and CI smoke checks.
31+
- The SQLx pool is created from `DATABASE_URL` with `max_connections(5)` per API instance.
2132

2233
## Database
2334

24-
Business logic is implemented in PostgreSQL stored procedures. The database is tuned for maximum write performance:
35+
Business logic is implemented in PostgreSQL stored procedures over UNLOGGED tables:
36+
37+
- `Clientes` stores the five seeded clients and their current balance (`SaldoInicial`).
38+
- `Transacoes` stores accepted transactions and has an index on `(ClienteId, Id DESC)` for recent-statement reads.
39+
- `InsertTransacao` applies credit/debit balance updates and inserts the transaction only when the update succeeds.
40+
- `GetSaldoClienteById` returns the current balance, limit, timestamp, and up to 10 latest transactions as JSONB.
41+
42+
The database command is tuned for benchmark throughput, not durability:
2543

2644
- `synchronous_commit=0` — no wait for WAL flush
2745
- `fsync=0` — skip fsync on writes
2846
- `full_page_writes=0` — skip full page writes
2947

30-
## Implementation Details
48+
## Container Images
3149

32-
- Minimal single-file Rust API (~140 lines)
33-
- Actix-web 4 HTTP framework with Tokio async runtime
34-
- SQLx 0.8 async PostgreSQL driver
35-
- Multi-stage Docker build for minimal container image size
50+
The API Dockerfile builds with `rust:1.95` and runs on `debian:bookworm-slim` as a non-root `app` user. The release workflow publishes `ghcr.io/jonathanperis/rinha2-back-end-rust:latest` as the multi-arch image used by `prod/docker-compose.yml`.

docs/wiki/ci-cd-pipeline.md

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,40 @@
22

33
## Workflows
44

5-
This repository uses four GitHub Actions workflows:
5+
This repository uses four GitHub Actions workflows plus a source-backed docs drift guard.
66

77
### build-check.yml
88

9-
- **Trigger:** Pull requests to main
10-
- **Steps:** Builds the Rust release binary, then runs Docker Compose and validates the healthcheck endpoint
11-
- **Purpose:** Catch build failures and regressions before merging
9+
- **Trigger:** Pull requests to `main` and manual dispatch
10+
- **Rust build:** `cargo build --release --manifest-path ./src/WebApi/Cargo.toml`
11+
- **Docs drift guard:** `python3 scripts/check_docs_drift.py`
12+
- **Container smoke:** `docker compose -f ./docker-compose.yml up nginx --wait`, then `GET http://localhost:9999/healthz`
13+
- **Purpose:** Catch Rust build failures, README/wiki drift, and compose health regressions before merging
1214

1315
### main-release.yml
1416

15-
- **Trigger:** Push to main branch
16-
- **Steps:** Builds the release binary, builds and pushes a multi-platform Docker image (amd64/arm64) to GHCR, runs container healthcheck, then runs k6 load tests and uploads the stress test report as an artifact
17-
- **Purpose:** Automated release of production-ready container images with load test validation
17+
- **Trigger:** Push to `main` and manual dispatch
18+
- **Build/release path:** build Rust release binary, push amd64 image as `latest`, push arm64 image as `latest-arm64`, merge both digests into the multi-arch `latest` manifest
19+
- **Validation:** start `prod/docker-compose.yml`, poll `/healthz`, then run k6 in `MODE=prod`
20+
- **Artifact:** upload `./prod/conf/stress-test/reports/stress-test-report.html` as `stress-test-report`
21+
- **Purpose:** Publish production-ready container images and preserve the load-test report from the release run
1822

1923
### codeql.yml
2024

21-
- **Trigger:** Push to main, pull requests to main, weekly schedule (Mondays)
22-
- **Steps:** Runs CodeQL static analysis for Rust with security-and-quality queries
25+
- **Trigger:** Push to `main`, pull requests to `main`, and weekly schedule (`0 3 * * 1`)
26+
- **Steps:** Set up stable Rust, initialize CodeQL for Rust with `security-and-quality` queries, build the release binary, perform analysis
2327
- **Purpose:** Continuous security and code quality analysis
2428

2529
### deploy.yml
2630

27-
- **Trigger:** Push to main branch
28-
- **Steps:** Deploys the `docs/` directory to GitHub Pages using the actions/deploy-pages workflow
29-
- **Purpose:** Publish project documentation and stress test reports to GitHub Pages
31+
- **Trigger:** Push to `main` and manual dispatch
32+
- **Steps:** Calls the shared `jonathanperis/.github/.github/workflows/pages-docs-deploy.yml@main` reusable workflow with `package-manager: bun`
33+
- **Purpose:** Build and publish the Astro docs site to GitHub Pages
34+
35+
## Local Documentation Drift Check
36+
37+
```bash
38+
python3 scripts/check_docs_drift.py
39+
```
40+
41+
The guard checks README/wiki claims against the current code and configuration: line count, Rust edition, Docker builder image, endpoints, workflow triggers, image tags, compose resources, and wiki navigation coverage. It also rejects known-stale phrases such as old line-count/version/performance claims.

docs/wiki/getting-started.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,49 @@ docker compose up nginx -d --build
1414

1515
## Access
1616

17-
The API is available at `http://localhost:9999`
17+
The challenge-facing API is available at `http://localhost:9999` through NGINX.
18+
19+
The dev compose file also maps the two API containers directly:
20+
21+
- `http://localhost:6968``webapi1-rust:8080`
22+
- `http://localhost:6969``webapi2-rust:8080`
23+
24+
Use port `9999` for normal validation and load-test requests.
1825

1926
## Endpoints
2027

21-
| Endpoint | Method | Description |
22-
|----------|--------|-------------|
23-
| `/clientes/{id}/transacoes` | POST | Submit debit or credit transaction |
24-
| `/clientes/{id}/extrato` | GET | Get account balance statement |
28+
| Endpoint | Method | Status Codes | Description |
29+
|----------|--------|--------------|-------------|
30+
| `/clientes/{id}/transacoes` | POST | 200, 404, 422 | Submit a debit or credit transaction for client IDs 1-5 |
31+
| `/clientes/{id}/extrato` | GET | 200, 404 | Get current balance, credit limit, timestamp, and up to 10 recent transactions |
32+
| `/healthz` | GET | 200 | Health check returning `Healthy` |
33+
34+
Transaction payloads must have `tipo` equal to `c` or `d`, a non-empty `descricao` of at most 10 characters, and a positive integer `valor`.
2535

2636
## Example Requests
2737

38+
### Health Check
39+
40+
```bash
41+
curl http://localhost:9999/healthz
42+
```
43+
2844
### Create Transaction
2945

3046
```bash
31-
curl -X POST http://localhost:9999/clientes/1/transacoes \
32-
-H "Content-Type: application/json" \
33-
-d '{"valor": 1000, "tipo": "c", "descricao": "deposito"}'
47+
curl -X POST http://localhost:9999/clientes/1/transacoes -H "Content-Type: application/json" -d '{"valor": 1000, "tipo": "c", "descricao": "deposito"}'
3448
```
3549

3650
### Get Statement
3751

3852
```bash
3953
curl http://localhost:9999/clientes/1/extrato
4054
```
55+
56+
## Stress Tests
57+
58+
```bash
59+
docker compose up k6 --build --force-recreate
60+
```
61+
62+
In `docker-compose.yml`, k6 runs in `MODE=dev` and exports time-series data to InfluxDB/Grafana. The release workflow uses `prod/docker-compose.yml`, where k6 runs in `MODE=prod` and writes an HTML stress-test report artifact.

docs/wiki/home.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
# rinha2-back-end-rust
22

3-
Rust/Actix-web 4 implementation for the Rinha de Backend 2024/Q1 challenge. Manages a fictional bank API with transaction processing and balance statements under strict resource constraints (1.5 CPU, 550MB RAM total across all containers).
3+
Rust/Actix-web 4 implementation for the Rinha de Backend 2024/Q1 challenge. It manages a fictional bank API with transaction processing and balance statements under strict resource constraints (1.5 CPU, 550MB RAM total across the challenge containers).
44

55
## Wiki Pages
66

77
| Page | Description |
88
|------|-------------|
9-
| [Challenge](#challenge) | What is Rinha de Backend 2024/Q1 |
10-
| [Architecture](#architecture) | Stack, services, resource constraints |
11-
| [Getting Started](#getting-started) | Prerequisites and how to run |
12-
| [Performance](#performance) | Results, benchmarks, resource usage |
13-
| [CI/CD Pipeline](#ci-cd-pipeline) | GitHub Actions workflows |
9+
| [Challenge](#challenge) | What Rinha de Backend 2024/Q1 requires |
10+
| [Architecture](#architecture) | Current stack, services, resource constraints, and runtime flow |
11+
| [Getting Started](#getting-started) | Prerequisites, run commands, endpoint smoke tests |
12+
| [Performance](#performance) | How to read benchmark/report artifacts without hard-coding stale numbers |
13+
| [CI/CD Pipeline](#ci-cd-pipeline) | GitHub Actions workflows and release/deploy path |
1414

1515
## Key Features
1616

17-
- Minimal single-file API implementation (~140 lines of Rust)
17+
- Single Rust API entrypoint (`src/WebApi/main.rs`, 173 total lines at this revision)
1818
- Actix-web 4 with Tokio async runtime and SQLx 0.8
19-
- PostgreSQL stored procedures for server-side business logic
20-
- All requests under 800ms at 250MB RAM usage
19+
- Rust 2024 package; Docker builder currently uses `rust:1.95`
20+
- PostgreSQL stored procedures for balance updates and statement aggregation
21+
- Health endpoint at `/healthz` returns `Healthy`
22+
- Source-backed drift guard: `python3 scripts/check_docs_drift.py`
2123

2224
---
2325

0 commit comments

Comments
 (0)