forked from TechEmpower/FrameworkBenchmarks
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[JavaScript] Add HyperExpress (TechEmpower#8305)
* [JavaScript] Add HyperExpress * [HyperExpress] Create readme.md * Rename readme.md to README.md * [HyperExpress] Tidy up codes * [HyperExpress] Add mysql * [HyperExpress] Tweaking github actions for db pool max connections * [HyperExpress] Instantiating json for each request (#1) * [HyperExpress] Instantiating json for each request * [HyperExpress] add max connections to postgres * [HyperExpress] Removing postgres pool max connections * [HyperExpress] Fix starting app in single instance * [HyperExpress] Remove unused scripts * [HyperExpress] Removing postgres pool max connections
- Loading branch information
Showing
12 changed files
with
1,095 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# HyperExpress Benchmarking Test | ||
|
||
HyperExpress is a high performance Node.js webserver with a simple-to-use API powered by µWebSockets.js under the hood. (https://github.com/kartikk221/hyper-express) | ||
|
||
µWebSockets.js is a web server bypass for Node.js (https://github.com/uNetworking/uWebSockets.js) | ||
|
||
## Important Libraries | ||
|
||
The tests were run with: | ||
|
||
- [hyper-express](https://github.com/kartikk221/hyper-express) | ||
- [postgres](https://github.com/porsager/postgres) | ||
- [mysql2](https://github.com/sidorares/node-mysql2) | ||
- [lru-cache](https://github.com/isaacs/node-lru-cache) | ||
|
||
## Database | ||
|
||
There are individual handlers for each DB approach. The logic for each of them are found here: | ||
|
||
- [Postgres](database/postgres.js) | ||
- [MySQL](database/mysql.js) | ||
|
||
There are **no database endpoints** or drivers attached by default. | ||
|
||
To initialize the application with one of these, run any _one_ of the following commands: | ||
|
||
```sh | ||
$ DATABASE=postgres npm start | ||
$ DATABASE=mysql npm start | ||
``` | ||
|
||
## Test Endpoints | ||
|
||
> Visit the test requirements [here](https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview) | ||
```sh | ||
$ curl localhost:8080/json | ||
$ curl localhost:8080/plaintext | ||
|
||
# The following are only available with the DATABASE env var | ||
|
||
$ curl localhost:8080/db | ||
$ curl localhost:8080/fortunes | ||
|
||
$ curl localhost:8080/queries?queries=2 | ||
$ curl localhost:8080/queries?queries=0 | ||
$ curl localhost:8080/queries?queries=foo | ||
$ curl localhost:8080/queries?queries=501 | ||
$ curl localhost:8080/queries?queries= | ||
|
||
$ curl localhost:8080/updates?queries=2 | ||
$ curl localhost:8080/updates?queries=0 | ||
$ curl localhost:8080/updates?queries=foo | ||
$ curl localhost:8080/updates?queries=501 | ||
$ curl localhost:8080/updates?queries= | ||
|
||
$ curl localhost:8080/cached-worlds?count=2 | ||
$ curl localhost:8080/cached-worlds?count=0 | ||
$ curl localhost:8080/cached-worlds?count=foo | ||
$ curl localhost:8080/cached-worlds?count=501 | ||
$ curl localhost:8080/cached-worlds?count= | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { escape } from 'html-escaper' | ||
import { Server } from 'hyper-express' | ||
import { LRUCache } from 'lru-cache' | ||
import cluster, { isWorker } from 'node:cluster' | ||
import { maxQuery, maxRows } from './config.js' | ||
const { DATABASE } = process.env | ||
const db = DATABASE ? await import(`./database/${DATABASE}.js`) : null | ||
|
||
const generateRandomNumber = () => Math.ceil(Math.random() * maxRows) | ||
|
||
const parseQueries = (i) => Math.min(Math.max(parseInt(i, 10) || 1, 1), maxQuery) | ||
|
||
const cache = new LRUCache({ | ||
max: maxRows | ||
}) | ||
|
||
const app = new Server() | ||
|
||
// use middleware to add `Server` into response header | ||
app.use((_request, response, next) => { | ||
response.header('Server', 'hyperexpress') | ||
next() | ||
}) | ||
|
||
app.get('/plaintext', (_request, response) => { | ||
response.atomic(() => { | ||
response | ||
.type('text') | ||
.send('Hello, World!') | ||
}) | ||
}) | ||
|
||
app.get('/json', (_request, response) => { | ||
response.json({ message: 'Hello, World!' }) | ||
}) | ||
|
||
if (db) { | ||
// populate cache | ||
(async () => { | ||
const worlds = await db.getAllWorlds() | ||
for (let i = 0; i < worlds.length; i++) { | ||
cache.set(worlds[i].id, worlds[i]) | ||
} | ||
})() | ||
|
||
app.get('/db', async (_request, response) => { | ||
try { | ||
const world = await db.find(generateRandomNumber()) | ||
response.json(world) | ||
} catch (error) { | ||
throw error | ||
} | ||
}) | ||
|
||
app.get('/queries', async (request, response) => { | ||
try { | ||
const queries = parseQueries(request.query.queries) | ||
const worldPromises = [] | ||
|
||
for (let i = 0; i < queries; i++) { | ||
worldPromises.push(db.find(generateRandomNumber())) | ||
} | ||
|
||
const worlds = await Promise.all(worldPromises) | ||
response.json(worlds) | ||
} catch (error) { | ||
throw error | ||
} | ||
}) | ||
|
||
app.get('/updates', async (request, response) => { | ||
try { | ||
const queries = parseQueries(request.query.queries) | ||
const worldPromises = [] | ||
|
||
for (let i = 0; i < queries; i++) { | ||
worldPromises.push(db.find(generateRandomNumber())) | ||
} | ||
|
||
const worlds = await Promise.all(worldPromises) | ||
|
||
const updatedWorlds = await Promise.all(worlds.map(async (world) => { | ||
world.randomNumber = generateRandomNumber() | ||
await db.update(world) | ||
return world | ||
})) | ||
response.json(updatedWorlds) | ||
} catch (error) { | ||
throw error | ||
} | ||
}) | ||
|
||
app.get('/fortunes', async (_request, response) => { | ||
try { | ||
const fortunes = await db.fortunes() | ||
|
||
fortunes.push({ id: 0, message: 'Additional fortune added at request time.' }) | ||
|
||
fortunes.sort((a, b) => a.message.localeCompare(b.message)) | ||
|
||
let i = 0, html = '<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>' | ||
for (; i < fortunes.length; i++) html += `<tr><td>${fortunes[i].id}</td><td>${escape(fortunes[i].message)}</td></tr>` | ||
html += '</table></body></html>' | ||
|
||
response.atomic(() => { | ||
response | ||
// .type('html') | ||
.header('Content-Type', 'text/html; charset=utf-8') | ||
.send(html) | ||
}) | ||
} catch (error) { | ||
throw error | ||
} | ||
}) | ||
|
||
app.get('/cached-worlds', async (request, response) => { | ||
try { | ||
const count = parseQueries(request.query.count) | ||
const worlds = [] | ||
|
||
for (let i = 0; i < count; i++) { | ||
worlds[i] = cache.get(generateRandomNumber()) | ||
} | ||
|
||
response.json(worlds) | ||
} catch (error) { | ||
throw error | ||
} | ||
}) | ||
} | ||
|
||
app.listen(8080).then(() => { | ||
console.log(`${isWorker ? `${cluster.worker.id}: ` : ''}Successfully bound to http://0.0.0.0:8080`) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
{ | ||
"framework": "hyperexpress", | ||
"tests": [ | ||
{ | ||
"default": { | ||
"json_url": "/json", | ||
"plaintext_url": "/plaintext", | ||
"port": 8080, | ||
"approach": "Realistic", | ||
"classification": "Micro", | ||
"database": "None", | ||
"framework": "hyperexpress", | ||
"language": "JavaScript", | ||
"flavor": "None", | ||
"orm": "Raw", | ||
"platform": "NodeJS", | ||
"webserver": "µws", | ||
"os": "Linux", | ||
"database_os": "Linux", | ||
"display_name": "hyperexpress", | ||
"notes": "", | ||
"versus": "nodejs" | ||
}, | ||
"postgres": { | ||
"db_url": "/db", | ||
"fortune_url": "/fortunes", | ||
"query_url": "/queries?queries=", | ||
"update_url": "/updates?queries=", | ||
"cached_query_url": "/cached-worlds?count=", | ||
"port": 8080, | ||
"approach": "Realistic", | ||
"classification": "Micro", | ||
"database": "Postgres", | ||
"framework": "hyperexpress", | ||
"language": "JavaScript", | ||
"flavor": "None", | ||
"orm": "Raw", | ||
"platform": "NodeJS", | ||
"webserver": "µws", | ||
"os": "Linux", | ||
"database_os": "Linux", | ||
"display_name": "hyperexpress", | ||
"notes": "", | ||
"versus": "nodejs" | ||
}, | ||
"mysql": { | ||
"db_url": "/db", | ||
"fortune_url": "/fortunes", | ||
"query_url": "/queries?queries=", | ||
"update_url": "/updates?queries=", | ||
"cached_query_url": "/cached-worlds?count=", | ||
"port": 8080, | ||
"approach": "Realistic", | ||
"classification": "Micro", | ||
"database": "MySQL", | ||
"framework": "hyperexpress", | ||
"language": "JavaScript", | ||
"flavor": "None", | ||
"orm": "Raw", | ||
"platform": "NodeJS", | ||
"webserver": "µws", | ||
"os": "Linux", | ||
"database_os": "Linux", | ||
"display_name": "hyperexpress", | ||
"notes": "", | ||
"versus": "nodejs" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import cluster, { isPrimary, setupPrimary, fork } from 'node:cluster' | ||
import { cpus } from 'node:os' | ||
|
||
if (isPrimary) { | ||
setupPrimary({ | ||
exec: 'app.js', | ||
}) | ||
cluster.on('exit', (worker) => { | ||
console.log(`worker ${worker.process.pid} died`) | ||
process.exit(1) | ||
}) | ||
for (let i = 0; i < cpus().length; i++) { | ||
fork() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const maxQuery = 500 | ||
export const maxRows = 10000 | ||
export const clientOpts = { | ||
host: 'tfb-database', | ||
user: 'benchmarkdbuser', | ||
password: 'benchmarkdbpass', | ||
database: 'hello_world', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { createPool, createConnection } from 'mysql2/promise' | ||
import { isWorker } from 'node:cluster' | ||
import { cpus } from 'node:os' | ||
import { clientOpts } from '../config.js' | ||
|
||
const client = await createConnection(clientOpts) | ||
|
||
const res = await client.query('SHOW VARIABLES LIKE "max_connections"') | ||
|
||
let maxConnections = 150 | ||
|
||
if (isWorker) { | ||
maxConnections = cpus().length > 2 ? Math.ceil(res[0][0].Value * 0.96 / cpus().length) : maxConnections | ||
} | ||
|
||
await client.end() | ||
|
||
const pool = createPool(Object.assign({ ...clientOpts }, { | ||
connectionLimit: maxConnections, | ||
idleTimeout: 600000 | ||
})) | ||
|
||
const execute = async (text, values) => (await pool.execute(text, values || undefined))[0] | ||
|
||
export const fortunes = async () => execute('SELECT * FROM fortune') | ||
|
||
export const find = async (id) => execute('SELECT id, randomNumber FROM world WHERE id = ?', [id]).then(arr => arr[0]) | ||
|
||
export const getAllWorlds = async () => execute('SELECT * FROM world') | ||
|
||
export const update = async (obj) => execute('UPDATE world SET randomNumber = ? WHERE id = ?', [obj.randomNumber, obj.id]) | ||
|
||
await Promise.all([...Array(maxConnections).keys()].map(fortunes)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import postgres from 'postgres' | ||
import { clientOpts } from '../config.js' | ||
|
||
const sql = postgres(clientOpts) | ||
|
||
export const fortunes = async () => sql`SELECT * FROM fortune` | ||
|
||
export const find = async (id) => sql`SELECT id, randomNumber FROM world WHERE id = ${id}`.then((arr) => arr[0]) | ||
|
||
export const getAllWorlds = async () => sql`SELECT * FROM world` | ||
|
||
export const update = async (obj) => sql`UPDATE world SET randomNumber = ${obj.randomNumber} WHERE id = ${obj.id}` | ||
|
||
await Promise.all([...Array(150).keys()].map(fortunes)) |
18 changes: 18 additions & 0 deletions
18
frameworks/JavaScript/hyperexpress/hyperexpress-mysql.dockerfile
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# syntax=docker/dockerfile:1 | ||
FROM node:18-slim | ||
|
||
WORKDIR /app | ||
|
||
COPY --chown=node:node . . | ||
|
||
ENV NODE_ENV production | ||
|
||
ENV DATABASE mysql | ||
|
||
RUN npm install | ||
|
||
USER node | ||
|
||
EXPOSE 8080 | ||
|
||
CMD ["node", "clustered.js"] |
18 changes: 18 additions & 0 deletions
18
frameworks/JavaScript/hyperexpress/hyperexpress-postgres.dockerfile
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# syntax=docker/dockerfile:1 | ||
FROM node:18-slim | ||
|
||
WORKDIR /app | ||
|
||
COPY --chown=node:node . . | ||
|
||
ENV NODE_ENV production | ||
|
||
ENV DATABASE postgres | ||
|
||
RUN npm install | ||
|
||
USER node | ||
|
||
EXPOSE 8080 | ||
|
||
CMD ["node", "clustered.js"] |
Oops, something went wrong.