# README
Rinha
This is my take at the second edition of Rinha de Backend, which would roughly translate to Backend Fight (or Cockfight).
Basically, it is a challenge to create an app with specific endpoints that can handle tests the creators of the challenge have prepared. The theme of this Backend Fight is to handle multiple data reads and writes to the same resource and how to avoid data races and data inconsistency.
The stack has to consist at least of:
- Load balancer
- Two instances of the server
- Database
I tackled it using Go, a language I'm just learning, and thus decided to try and
learn more on how to create and use middlewares and the new functionalities of
the standard serveMux
introduced in go version 1.22.0
. Also, I'm just trying
to think of a directory structure to organize everything.
Tech Stack
The idea was to use only Go's standard library, but I used a driver for the database as well.
- Nginx as load balancer
- Go for the api with the driver
pgx/v4
- Postgres as the database
How To
I have made two different docker images, the dev
and the prod
images, using
Docker's really cool multi stage builds.
The difference is that the dev
also uses air
for live reloading of the app, making the dev experience better, while the prod
image just compiles and run the binary.
To help development and testing, make was also used as a command runner
(not as a build tool). Run make help
to see the options.
You don't have to have go installed, as you can run everything inside docker containers.
For dev, you can just run:
docker compose up
# or
make server
Now you'll have the server accepting connections at localhost:4000
.
By default the port will be 4000, but you can change it in docker-compose.yml
by changing the SERVER_ADDRESS
env var. Don't forget to change it in the
ports
section, too!
To seed the database (You may want to do this before testing the endpoints.):
make seed
To test the endpoints, there is a convenience script located in scripts/curl-endpoints.sh
.
You'll need curl installed to use it.
It also features a help option:
./scripts/curl-endpoints.sh help
Gatling Test
To run the Gatling tests, you'll need to install Gatling.
To install it, all you need to do is download, unzip it and set an environment variable
named GATLING_HOME
to the directory after unzipping it. This environment variable
is used by this repo's test script (load-test/start.sh).
Then you should stop the running dev containers, if any, and run the prod containers. To do so:
make down
make prod.up
To start the tests, run:
make prod.gatling
You can see some stats and logs with:
make prod.stats
# Services names in prod are api1, api2, db and nginx
docker compose -f docker-compose-prod.yml logs <service_name> -f
It will place the results in load-test/user-files/results/
.
Here is an example ran in 2024/02/23:
Results
On the 14th of March of 2024, the results were published. Every contender started with a certain amount of points and some criteria could deduct points. They were as follows:
- If at least 98% of the requests were responded with less than 250ms, you'd get no deductions. If your api had less than 98%, you'd lose points based on how many % of requests were responded on 250ms or more.
- If the balance were not consistent, you'd lose some points based on how many inconsistencies the system had.
Inconsistencies would discount more points than speed of requests, meaning that consistency was more important.
This API had a grand total of 0 penalties! Meaning it passed with max score. 🎉
This talks a lot about Golang's efficiency. Also, most of the work was done in Postgresql handling the locks.
Results can be seen here.
Access this page and search for lucassperez-go-crebito
. Yay! (:
Endpoints
GET /clientes/{id}/extrato
Params
Url query param id
: The id of the cliente
.
Response
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"
}
]
}
saldo
total
client's balancedata_extrato
timestamp of when the request was madelimite
client's limit
ultimas_transacoes
client's last transactionsvalor
value of transactiontipo
type of transaction (c
is credit,d
is debit)descricao
string between 1 and 10 charactersrealizada_em
timestamp of when transaction was made
POST /clientes/{id}/transacoes
Params
Url query param id
: The id of the cliente
.
Body:
{
"valor": 1000,
"tipo" : "c",
"descricao" : "descricao"
}
valor
must be a non negative integertipo
one of"c"
(credit) or"d"
(debit)descricao
a string between 1 and 10 characters
Response
HTTP 200 OK
{
"limite" : 100000,
"saldo" : -9098
}
limite
client's limitsaldo
client's new balance
Balance can never be less than -limit.