Skip to content

Commit

Permalink
finished challenge
Browse files Browse the repository at this point in the history
  • Loading branch information
lukas8219 committed Aug 13, 2023
1 parent 2b5b2e8 commit b374c30
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 82 deletions.
1 change: 1 addition & 0 deletions .dockerignore
@@ -0,0 +1 @@
node_modules
6 changes: 6 additions & 0 deletions Dockerfile
@@ -0,0 +1,6 @@
FROM node:18.12.1-alpine

COPY . .
RUN npm ci

CMD ["node", "index.js"]
13 changes: 11 additions & 2 deletions README.md
@@ -1,2 +1,11 @@
- Batch
- Split entre escrita e leitura? https://serverfault.com/questions/152745/nginx-proxy-by-request-method
# Rinha BE 2023 - Q3 @luucaspole/@lukas8219

### Tecnologias usadas:

- Node
- Redis (vou meter ainda)
- Postgres


### Tática:
Apenas fazer batch no client e rezar pro eventloop não morrer
150 changes: 83 additions & 67 deletions database.js
Expand Up @@ -2,20 +2,52 @@ const pg = require('pg');
const { logger } = require('./logger');
const { callbackify, promisify } = require('util');

const pool = new pg.Pool({ password: '12345678', user: 'postgres' });
const connection = pool.connect().then((connection) => {
logger.info(`Connect to db`)
return connection;
const URL = process.env.DB_URL || 'postgres://postgres:12345678@localhost:5432/postgres';

const pool = new pg.Pool({ connectionString: URL, min: 8, max: 8 });

pool.on('error', connect);

pool.once('connect', () => {
logger.info(`database.js: Connected to db ${URL}`)
logger.info(`Creating table "pessoas" if not exists`);
return pool.query(`
CREATE TABLE IF NOT EXISTS pessoas (
id SERIAL PRIMARY KEY,
apelido VARCHAR(32) UNIQUE NOT NULL,
nome VARCHAR(100) NOT NULL,
nascimento DATE NOT NULL,
stack VARCHAR(32)[]
);
CREATE INDEX IF NOT EXISTS term_search_index_apelido ON pessoas
USING gin(to_tsvector('english', apelido));
CREATE INDEX IF NOT EXISTS term_search_index_nome ON pessoas
USING gin(to_tsvector('english', nome));
`)
});

module.exports.db = async function createConnection(){
async function connect() {
try {
await pool.connect();
} catch(err){
setTimeout(() => {
connect();
logger.error(`database.js: an error occured when connecting ${err} retrying connection on 3 secs`);
}, 3000)
}
}

connect();

module.exports.db = async function createConnection() {
return new Promise(async (res) => {
return connection.then(res);
return connection.catch(connectionErrorHandler).then(res);
})
};

module.exports.insertPerson = async function({ apelido, nome, nascimento, stack }){
const con = await connection;
module.exports.insertPerson = async function ({ apelido, nome, nascimento, stack }) {
const stackRaw = (stack || []);
const stackValue = stackRaw.length ? `ARRAY[${stackRaw.filter(Boolean).map((s) => `'${stack}'`)}]` : null;
const query = `
Expand All @@ -33,92 +65,76 @@ module.exports.insertPerson = async function({ apelido, nome, nascimento, stack
${stackValue}
)
`
return con.query(query);
return pool.query(query);
}

module.exports.findById = async function(id){
const con = await connection;
module.exports.findById = async function (id) {
const query = `
SELECT
*
id,
apelido,
nome,
to_char(nascimento, 'YYYY-MM-DD') as nascimento,
stack
FROM
pessoas
WHERE "id" = ${id}
`
return con.query(query);
return pool.query(query);
}

module.exports.findByTerm = async function findByTerm(term){
const con = await connection;
module.exports.findByTerm = async function findByTerm(term) {
const query = `
SELECT
*
id,
apelido,
nome,
to_char(nascimento, 'YYYY-MM-DD') as nascimento,
stack
FROM
pessoas
WHERE
to_tsvector('english', apelido) @@ to_tsquery('${term}:*')
OR to_tsvector('english', nome) @@ to_tsquery('${term}:*')
OR to_tsvector(array_to_string(stack, ' ')) @@ to_tsquery('${term}:*')
LIMIT 50;`
return con.query(query);
return pool.query(query);
}

module.exports.count = async function(){
return connection.then((con) => {
return con.query(`SELECT COUNT(id) FROM pessoas`);
})
module.exports.count = async function () {
return pool.query(`SELECT COUNT(id) FROM pessoas`);
}

const batchItems = {};

const toBeBatched = ['findById', 'findByTerm'];

process.env.BATCH === 'true' ? (() => {
toBeBatched.forEach((moduleKey) => {
batchItems[moduleKey] = new Map();
const fn = module.exports[moduleKey];

function newApi(){
const args = new Array(...arguments).slice(0, arguments.length - 1);
const key = JSON.stringify(args);
const queue = batchItems[moduleKey].get(key);
const realCb = arguments[arguments.length - 1];
const cb = function(){
realCb(...arguments);
setImmediate(() => {
(queue || []).forEach((cb) => cb(...arguments))
batchItems[moduleKey].delete(key);
});
};
if(queue){
logger.info(`${moduleKey} has been queued: for args ${key}`);
return queue.push(cb);
toBeBatched.forEach((moduleKey) => {
batchItems[moduleKey] = new Map();
const fn = module.exports[moduleKey];

function newApi() {
const args = new Array(...arguments).slice(0, arguments.length - 1);
const key = JSON.stringify(args);
const queue = batchItems[moduleKey].get(key);
const realCb = arguments[arguments.length - 1];
const cb = function () {
realCb(...arguments);
setImmediate(() => {
(queue || []).forEach((cb) => cb(...arguments))
batchItems[moduleKey].delete(key);
});
};
if (queue) {
logger.info(`${moduleKey} has been queued: for args ${key}`);
return queue.push(cb);
}
batchItems[moduleKey].set(key, new Array([cb]));
arguments[arguments.length - 1] = cb;
callbackify(fn).call(this, ...arguments);
}
batchItems[moduleKey].set(key, new Array([cb]));
arguments[arguments.length - 1] = cb;
callbackify(fn).call(this, ...arguments);
}

module.exports[moduleKey] = promisify(newApi);
})
})() : null;



connection.then((connection) => {
return connection.query(`
CREATE TABLE IF NOT EXISTS pessoas (
id SERIAL PRIMARY KEY,
apelido VARCHAR(32) UNIQUE NOT NULL,
nome VARCHAR(100) NOT NULL,
nascimento DATE NOT NULL,
stack VARCHAR(32)[]
);
CREATE INDEX IF NOT EXISTS term_search_index_apelido ON pessoas
USING gin(to_tsvector('english', apelido));
CREATE INDEX IF NOT EXISTS term_search_index_nome ON pessoas
USING gin(to_tsvector('english', nome));
`)
})
module.exports[moduleKey] = promisify(newApi);
})
})() : null;
59 changes: 59 additions & 0 deletions docker-compose.yaml
@@ -0,0 +1,59 @@
version: '3.4'
services:
postgres:
image: postgres
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 12345678
ports:
- 5432:5432
deploy:
resources:
limits:
cpus: '0.75'
memory: '0.5GB'
app1:
image: lukas8219/rinha-be-2023-q3:latest
environment:
DB_URL: "postgres://postgres:12345678@postgres:5432/postgres"
BATCH: "true"
CLUSTER: "false"
ports:
- "8080"
depends_on:
- postgres
deploy:
resources:
limits:
cpus: '0.25'
memory: '0.5GB'
app2:
image: lukas8219/rinha-be-2023-q3:latest
environment:
DB_URL: "postgres://postgres:12345678@postgres:5432/postgres"
BATCH: "true"
CLUSTER: "false"
ports:
- "8080"
depends_on:
- postgres
deploy:
resources:
limits:
cpus: '0.25'
memory: '0.5GB'
nginx: # Load Balancer
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app1
- app2
ports:
- "9999:9999"
deploy:
resources:
limits:
cpus: '0.25'
memory: '0.5GB'
13 changes: 3 additions & 10 deletions index.js
Expand Up @@ -8,19 +8,12 @@ const os = require('os');
const cluster = require('cluster');
const process = require('process');

process.env.UV_THREADPOOL_SIZE = os.cpus().length

const app = express();

app.use(bodyParser.json())

/*
{
"apelido" : "josé",
"nome" : "José Roberto",
"nascimento" : "2000-10-01",
"stack" : ["C#", "Node", "Oracle"]
}
*/

app.post('/pessoas', validateBody, async (req, res, next) => {
try {
await insertPerson(req.body);
Expand Down Expand Up @@ -65,7 +58,7 @@ app.use(errorHandler);

const numCPUs = Math.ceil(os.cpus().length / 2);

if(cluster.isPrimary){
if(cluster.isPrimary && process.env.CLUSTER === 'true'){
logger.info(`index.js: Primary ${process.pid} is running`);

for (let i = 0; i < numCPUs; i++) {
Expand Down
2 changes: 1 addition & 1 deletion middleware.js
Expand Up @@ -53,7 +53,7 @@ module.exports.validateBody = async function validate(req, res, next){
})
}

if(stack && stack.length && stack.some((s) => s === undefined || s === null)){
if(stack && stack.length && stack.some((s) => s === undefined || s === null || s === "" || !_.isString(s))){
res.status(422);
return res.json({
error: 'stack com item invalido'
Expand Down
15 changes: 15 additions & 0 deletions nginx.conf
@@ -0,0 +1,15 @@
events {
# configure como quiser
}
http {
upstream api {
server app1:8080;
server app2:8080;
}
server {
listen 9999;
location / {
proxy_pass http://api;
}
}
}
4 changes: 2 additions & 2 deletions package.json
@@ -1,13 +1,13 @@
{
"name": "stress-test-zan",
"name": "rinha-be-2023-qq",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"author": "lukas8219",
"license": "ISC",
"dependencies": {
"body-parser": "^1.20.2",
Expand Down

0 comments on commit b374c30

Please sign in to comment.