Skip to content

Commit dd84413

Browse files
authored
Express: fine tuning (#10495)
* perf: remove middleware (is slow) * perf: optimize response * fix: cache * fix: serializer * perf: use end * fix: benchmark * fix: maxRows * fix: query count * fix: count * chore: force run github action
1 parent 55b1217 commit dd84413

File tree

5 files changed

+108
-77
lines changed

5 files changed

+108
-77
lines changed

frameworks/JavaScript/express/app.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ const cluster = require('cluster'),
77
numCPUs = require('os').cpus().length,
88
express = require('express');
99

10+
const {
11+
jsonSerializer,
12+
GREETING,
13+
} = require("./src/utils.mjs");
14+
1015

1116
if (cluster.isPrimary) {
1217
console.log(`Primary ${process.pid} is running`);
@@ -23,22 +28,22 @@ if (cluster.isPrimary) {
2328
const app = module.exports = express();
2429

2530
app.set('x-powered-by', false);
26-
app.set('etag', false)
27-
28-
// Set headers for all routes
29-
app.use((req, res, next) => {
30-
res.setHeader("Server", "Express");
31-
return next();
32-
});
33-
34-
app.set('view engine', 'jade');
35-
app.set('views', __dirname + '/views');
31+
app.set('etag', false);
3632

3733
// Routes
38-
app.get('/json', (req, res) => res.send({ message: 'Hello, World!' }));
34+
app.get('/json', (req, res) => {
35+
res.writeHead(200, {
36+
"content-type": "application/json",
37+
server: "Express",
38+
}).end(jsonSerializer({ message: GREETING }))
39+
});
3940

40-
app.get('/plaintext', (req, res) =>
41-
res.header('Content-Type', 'text/plain').send('Hello, World!'));
41+
app.get('/plaintext', (req, res) => {
42+
res.writeHead(200, {
43+
"content-type": "text/plain",
44+
server: "Express",
45+
}).end(GREETING);
46+
});
4247

4348
const server = app.listen(8080);
4449
server.keepAliveTimeout = 0;

frameworks/JavaScript/express/benchmark_config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"db_url": "/db",
8787
"query_url": "/queries?queries=",
8888
"update_url": "/updates?queries=",
89+
"cached_query_url": "/cached-worlds?count=",
8990
"fortune_url": "/fortunes",
9091
"port": 8080,
9192
"approach": "Realistic",

frameworks/JavaScript/express/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"escape-html": "1.0.3",
88
"express": "5.2.1",
99
"fast-json-stringify": "^6.1.1",
10+
"lru-cache": "^11.2.4",
1011
"mongoose": "9.1.1",
1112
"mysql2": "3.16.0",
1213
"pg": "8.16.3",

frameworks/JavaScript/express/src/server.mjs

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,126 @@
11
import express from "express";
2+
import { LRUCache } from 'lru-cache';
23
import {
34
generateRandomNumber,
4-
getQueriesCount,
5-
handleError,
5+
parseQueries,
66
escape,
77
jsonSerializer,
88
worldObjectSerializer,
99
worldsObjectSerializer,
10-
sortByMessage,
11-
writeResponse,
12-
headerTypes,
1310
GREETING,
11+
maxRows
1412
} from "./utils.mjs";
1513

14+
1615
let db;
1716
const { DATABASE } = process.env;
1817
if (DATABASE) db = await import(`./database/${DATABASE}.mjs`);
1918

19+
const cache = new LRUCache({
20+
max: maxRows
21+
});
22+
2023
const extra = { id: 0, message: "Additional fortune added at request time." };
2124

2225
const app = express();
2326

27+
app.set('x-powered-by', false);
28+
app.set('etag', false);
29+
2430
app.get("/plaintext", (req, res) => {
25-
writeResponse(res, GREETING, headerTypes["plain"]);
31+
res.writeHead(200, {
32+
"content-type": "text/plain",
33+
server: "Express",
34+
}).end(GREETING);
2635
});
2736

2837
app.get("/json", (req, res) => {
29-
writeResponse(res, jsonSerializer({ message: GREETING }));
38+
res.writeHead(200, {
39+
"content-type": "application/json",
40+
server: "Express",
41+
}).end(jsonSerializer({ message: GREETING }));
3042
});
3143

3244
if (db) {
3345
app.get("/db", async (req, res) => {
34-
try {
35-
const row = await db.find(generateRandomNumber());
36-
writeResponse(res, worldObjectSerializer(row));
37-
} catch (error) {
38-
handleError(error, res);
39-
}
46+
const row = await db.find(generateRandomNumber());
47+
res.writeHead(200, {
48+
"content-type": "application/json",
49+
server: "Express",
50+
}).end(worldObjectSerializer(row));
4051
});
4152

4253
app.get("/queries", async (req, res) => {
43-
try {
44-
const queries = getQueriesCount(req);
45-
const worldPromises = new Array(queries);
46-
for (let i = 0; i < queries; i++) {
47-
worldPromises[i] = db.find(generateRandomNumber());
48-
}
49-
const worlds = await Promise.all(worldPromises);
50-
writeResponse(res, worldsObjectSerializer(worlds));
51-
} catch (error) {
52-
handleError(error, res);
54+
const queries = parseQueries(req.query.queries);
55+
const worldPromises = new Array(queries);
56+
for (let i = 0; i < queries; i++) {
57+
worldPromises[i] = db.find(generateRandomNumber());
5358
}
59+
const worlds = await Promise.all(worldPromises);
60+
res.writeHead(200, {
61+
"content-type": "application/json",
62+
server: "Express",
63+
}).end(worldsObjectSerializer(worlds));
5464
});
5565

5666
app.get("/fortunes", async (req, res) => {
57-
try {
58-
const rows = [extra, ...(await db.fortunes())];
59-
sortByMessage(rows);
60-
const n = rows.length;
61-
let html = "",
62-
i = 0;
63-
for (; i < n; i++) {
64-
html += `<tr><td>${rows[i].id}</td><td>${escape(
65-
rows[i].message
66-
)}</td></tr>`;
67-
}
68-
69-
writeResponse(
70-
res,
71-
`<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>${html}</table></body></html>`,
72-
headerTypes["html"]
73-
);
74-
} catch (error) {
75-
handleError(error, res);
67+
const rows = await db.fortunes();
68+
rows.push(extra);
69+
rows.sort((a, b) => (a.message < b.message) ? -1 : 1);
70+
const n = rows.length;
71+
let html = "",
72+
i = 0;
73+
for (; i < n; i++) {
74+
const row = rows[i];
75+
html += `<tr><td>${row.id}</td><td>${escape(row.message)}</td></tr>`;
7676
}
77+
res.writeHead(200, {
78+
"content-type": "text/html; charset=UTF-8",
79+
server: "Express",
80+
}).end(`<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>${html}</table></body></html>`);
7781
});
7882

7983
app.get("/updates", async (req, res) => {
80-
try {
81-
const queriesCount = getQueriesCount(req);
82-
const databaseJobs = new Array(queriesCount);
83-
for (let i = 0; i < queriesCount; i++) {
84-
databaseJobs[i] = db.find(generateRandomNumber());
85-
}
86-
const worldObjects = await Promise.all(databaseJobs);
84+
const queriesCount = parseQueries(req.query.queries);
85+
const databaseJobs = new Array(queriesCount);
86+
for (let i = 0; i < queriesCount; i++) {
87+
databaseJobs[i] = db.find(generateRandomNumber());
88+
}
89+
const worldObjects = await Promise.all(databaseJobs);
8790

88-
for (let i = 0; i < queriesCount; i++) {
89-
worldObjects[i].randomNumber = generateRandomNumber();
91+
for (let i = 0; i < queriesCount; i++) {
92+
worldObjects[i].randomNumber = generateRandomNumber();
93+
}
94+
await db.bulkUpdate(worldObjects);
95+
res.writeHead(200, {
96+
"content-type": "application/json",
97+
server: "Express",
98+
}).end(worldsObjectSerializer(worldObjects));
99+
});
100+
101+
let isCachePopulated = false
102+
app.get('/cached-worlds', async (req, res) => {
103+
if (!isCachePopulated) {
104+
const worlds = await db.getAllWorlds();
105+
for (let i = 0; i < worlds.length; i++) {
106+
cache.set(worlds[i].id, worlds[i]);
90107
}
91-
await db.bulkUpdate(worldObjects);
92-
writeResponse(res, JSON.stringify(worldObjects));
93-
} catch (error) {
94-
handleError(error, res);
108+
isCachePopulated = true;
95109
}
110+
const count = parseQueries(req.query.count);
111+
const worlds = new Array(count);
112+
113+
for (let i = 0; i < count; i++) {
114+
worlds[i] = cache.get(generateRandomNumber());
115+
}
116+
117+
res.writeHead(200, {
118+
"content-type": "application/json",
119+
server: "Express",
120+
}).end(worldsObjectSerializer(worlds));
96121
});
97122
}
98123

99-
app.all("/{*splat}", (req, res) => {
100-
res.status(404).send("Not Found");
101-
});
102-
103124
const host = process.env.HOST || "0.0.0.0";
104125
const port = parseInt(process.env.PORT || "8080");
105126
app.listen(port, host, () => {

frameworks/JavaScript/express/src/utils.mjs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import fjs from 'fast-json-stringify';
22

33
export const GREETING = "Hello, World!";
44

5+
export const maxQuery = 500
6+
export const maxRows = 10000
7+
58
export const headerTypes = {
69
plain: "text/plain",
710
json: "application/json",
@@ -22,11 +25,13 @@ export function handleError(error, response) {
2225
}
2326

2427
export function getQueriesCount(request) {
25-
return Math.min(parseInt(request.query["queries"]) || 1, 500);
28+
return Math.min(parseInt(request.query["queries"]) || 1, maxQuery);
2629
}
2730

31+
export const parseQueries = (i) => i > maxQuery ? maxQuery : (i | 0) || 1;
32+
2833
export function generateRandomNumber() {
29-
return Math.ceil(Math.random() * 10000);
34+
return ((Math.random() * maxRows) | 0) + 1;
3035
}
3136

3237
const escapeHTMLRules = {
@@ -42,9 +47,7 @@ const unsafeHTMLMatcher = /[&<>"'\/]/g;
4247

4348
export function escape(text) {
4449
if (unsafeHTMLMatcher.test(text) === false) return text;
45-
return text.replace(unsafeHTMLMatcher, function (m) {
46-
return escapeHTMLRules[m] || m;
47-
});
50+
return text.replace(unsafeHTMLMatcher, (m) => escapeHTMLRules[m] || m);
4851
}
4952

5053
export const jsonSerializer = fjs({

0 commit comments

Comments
 (0)