Browse Source

[Elysia] add compiled configuration, use cluster mode, optimize performance (#9251)

* feat(elysia): use cluster mode, static resource, add compiled mode

* feat(elysia): use imperative for-loop instead of map to optimize performance
SaltyAom 11 months ago
parent
commit
aac290b65e

+ 23 - 0
frameworks/TypeScript/elysia/benchmark_config.json

@@ -62,6 +62,29 @@
         "display_name": "Elysia [smol] [PostgreSQL]",
         "display_name": "Elysia [smol] [PostgreSQL]",
         "notes": "",
         "notes": "",
         "versus": "nodejs"
         "versus": "nodejs"
+      },
+      "compiled": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "elysia",
+        "language": "TypeScript",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "bun",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Elysia [Compiled]",
+        "notes": "",
+        "versus": "nodejs"
       }
       }
     }
     }
   ]
   ]

BIN
frameworks/TypeScript/elysia/bun.lockb


+ 15 - 0
frameworks/TypeScript/elysia/elysia-compiled.dockerfile

@@ -0,0 +1,15 @@
+FROM oven/bun:1.1
+
+EXPOSE 8080
+
+COPY . .
+
+ENV NODE_ENV production
+
+RUN bun install --production
+
+ENV DATABASE postgres
+
+RUN bun run compile
+
+CMD ["./server"]

+ 3 - 3
frameworks/TypeScript/elysia/elysia-postgres.dockerfile

@@ -8,8 +8,8 @@ ENV NODE_ENV production
 
 
 RUN bun install --production
 RUN bun install --production
 
 
-RUN bun run build
-
 ENV DATABASE postgres
 ENV DATABASE postgres
 
 
-CMD ["bun", "spawn.ts"]
+RUN bun run build
+
+CMD ["bun", "./dist/index.js"]

+ 4 - 4
frameworks/TypeScript/elysia/elysia-smol-postgres.dockerfile

@@ -6,12 +6,12 @@ COPY . .
 
 
 ENV NODE_ENV production
 ENV NODE_ENV production
 
 
-RUN bun install --production
-
-RUN bun run build
+RUN bun install
 
 
 ENV DATABASE postgres
 ENV DATABASE postgres
 
 
+RUN bun run build
+
 RUN sed -i 's/smol = false/smol = true/g' bunfig.toml
 RUN sed -i 's/smol = false/smol = true/g' bunfig.toml
 
 
-CMD ["bun", "spawn.ts"]
+CMD ["bun", "./dist/index.js"]

+ 1 - 1
frameworks/TypeScript/elysia/elysia.dockerfile

@@ -10,4 +10,4 @@ RUN bun install --production
 
 
 RUN bun run build
 RUN bun run build
 
 
-CMD ["bun", "spawn.ts"]
+CMD ["bun", "./dist/index.js"]

+ 4 - 4
frameworks/TypeScript/elysia/package.json

@@ -3,16 +3,16 @@
   "version": "0.0.1",
   "version": "0.0.1",
   "module": "src/index.js",
   "module": "src/index.js",
   "devDependencies": {
   "devDependencies": {
-    "bun-types": "^1.1.23",
     "typescript": "^5.5.4"
     "typescript": "^5.5.4"
   },
   },
   "scripts": {
   "scripts": {
     "dev": "bun run --watch src/index.ts",
     "dev": "bun run --watch src/index.ts",
     "start": "bun run src/index.ts",
     "start": "bun run src/index.ts",
-    "build": "bun build --compile --minify --outfile server src/index.ts"
+    "build": "bun build --minify --target bun --outdir dist src/index.ts",
+    "compile": "bun build --compile --minify --target bun --outfile server src/index.ts"
   },
   },
   "dependencies": {
   "dependencies": {
-    "elysia": "^1.1.6",
+    "elysia": "^1.1.12",
     "postgres": "^3.4.4"
     "postgres": "^3.4.4"
   }
   }
-}
+}

+ 0 - 18
frameworks/TypeScript/elysia/spawn.ts

@@ -1,18 +0,0 @@
-const cpus = navigator.hardwareConcurrency;
-const buns = new Array(cpus);
-
-for (let i = 0; i < cpus; i++) {
-  buns[i] = Bun.spawn(['./server'], {
-    stdio: ['inherit', 'inherit', 'inherit'],
-    env: { ...process.env },
-  });
-}
-
-function kill() {
-  for (const bun of buns) {
-    bun.kill();
-  }
-}
-
-process.on('SIGINT', kill);
-process.on('exit', kill);

+ 28 - 31
frameworks/TypeScript/elysia/src/db-handlers.ts

@@ -1,64 +1,63 @@
-import Elysia from 'elysia';
-import * as db from './postgres';
-import { Fortune } from './types';
+import { Elysia, t } from "elysia";
+import * as db from "./postgres";
+import { Fortune } from "./types";
 
 
-function rand () {
-  return Math.ceil(Math.random() * 10000)
+function rand() {
+  return Math.ceil(Math.random() * 10000);
 }
 }
 
 
-function parseQueriesNumber (q?: string) {
-  return Math.min(parseInt(q || '1') || 1, 500)
+function parseQueriesNumber(q?: string) {
+  return Math.min(parseInt(q || "1") || 1, 500);
 }
 }
 
 
-function renderTemplate (fortunes: Fortune[]) {
+function renderTemplate(fortunes: Fortune[]) {
   const n = fortunes.length;
   const n = fortunes.length;
 
 
-  let html = '';
+  let html = "";
   for (let i = 0; i < n; i++) {
   for (let i = 0; i < n; i++) {
     html += `<tr><td>${fortunes[i].id}</td><td>${Bun.escapeHTML(
     html += `<tr><td>${fortunes[i].id}</td><td>${Bun.escapeHTML(
-      fortunes[i].message
+      fortunes[i].message,
     )}</td></tr>`;
     )}</td></tr>`;
   }
   }
 
 
   return `<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>${html}</table></body></html>`;
   return `<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>${html}</table></body></html>`;
 }
 }
 
 
-const dbHandlers = new Elysia({
-  name: 'db-handlers',
-})
-  .onAfterHandle(({ set }) => {
-    set.headers['server'] = 'Elysia';
+export const dbHandlers = new Elysia()
+  .headers({
+    server: "Elysia",
   })
   })
-
-  .get('/db', () => db.find(rand()))
-
-  .get('/fortunes', async ({ set }) => {
+  .get("/db", () => db.find(rand()))
+  .get("/fortunes", async (c) => {
     const fortunes = await db.fortunes();
     const fortunes = await db.fortunes();
 
 
     fortunes.push({
     fortunes.push({
       id: 0,
       id: 0,
-      message: 'Additional fortune added at request time.',
+      message: "Additional fortune added at request time.",
+    });
+
+    fortunes.sort((a, b) => {
+      if (a.message < b.message) return -1;
+
+      return 1;
     });
     });
 
 
-    fortunes.sort((a, b) => (a.message < b.message ? -1 : 1));
+    c.set.headers["content-type"] = "text/html; charset=utf-8";
 
 
-    set.headers['content-type'] = 'text/html; charset=utf-8';
     return renderTemplate(fortunes);
     return renderTemplate(fortunes);
   })
   })
-
-  .get('/queries', async ({ query }) => {
-    const num = parseQueriesNumber(query.queries)
+  .get("/queries", (c) => {
+    const num = parseQueriesNumber(c.query.queries);
     const worldPromises = new Array(num);
     const worldPromises = new Array(num);
 
 
     for (let i = 0; i < num; i++) {
     for (let i = 0; i < num; i++) {
       worldPromises[i] = db.find(rand());
       worldPromises[i] = db.find(rand());
     }
     }
 
 
-    return await Promise.all(worldPromises);
+    return Promise.all(worldPromises);
   })
   })
-
-  .get('/updates', async ({ query }) => {
-    const num = parseQueriesNumber(query.queries)
+  .get("/updates", async (c) => {
+    const num = parseQueriesNumber(c.query.queries);
     const worldPromises = new Array(num);
     const worldPromises = new Array(num);
 
 
     for (let i = 0; i < num; i++) {
     for (let i = 0; i < num; i++) {
@@ -74,5 +73,3 @@ const dbHandlers = new Elysia({
     await db.bulkUpdate(worlds);
     await db.bulkUpdate(worlds);
     return worlds;
     return worlds;
   });
   });
-
-export default dbHandlers;

+ 15 - 24
frameworks/TypeScript/elysia/src/index.ts

@@ -1,29 +1,20 @@
-import { Elysia } from 'elysia';
-import dbHandlers from './db-handlers';
+import cluster from "node:cluster";
+import os from "node:os";
+import process from "node:process";
 
 
-const app = new Elysia({
-  serve: {
-    reusePort: true,
-  },
-})
-  .get('/plaintext', ({ set }) => {
-    set.headers['server'] = 'Elysia';
-    return 'Hello, World!';
-  })
+if (cluster.isPrimary) {
+  console.log(`Primary ${process.pid} is running`);
 
 
-  .get('/json', ({ set }) => {
-    set.headers = {
-      'content-type': 'application/json',
-      'server': 'Elysia',
-    };
+  const numCPUs = os.availableParallelism();
+  for (let i = 0; i < numCPUs; i++) {
+    cluster.fork();
+  }
 
 
-    return JSON.stringify({ message: 'Hello, World!' });
+  cluster.on("exit", (worker) => {
+    console.log(`worker ${worker.process.pid} died`);
+    process.exit(1);
   });
   });
-
-if (Bun.env.DATABASE) {
-  app.use(dbHandlers);
+} else {
+  await import("./server");
+  console.log(`Worker ${process.pid} started`);
 }
 }
-
-app.listen(8080);
-
-console.info(`🦊 Elysia is running at ${app.server!.url}`);

+ 20 - 17
frameworks/TypeScript/elysia/src/postgres.ts

@@ -1,27 +1,30 @@
-import postgres from 'postgres';
-import { Fortune, World } from './types';
+import postgres from "postgres";
+import { Fortune, World } from "./types";
 
 
 const sql = postgres({
 const sql = postgres({
-  host: 'tfb-database',
-  user: 'benchmarkdbuser',
-  password: 'benchmarkdbpass',
-  database: 'hello_world',
+  host: "tfb-database",
+  user: "benchmarkdbuser",
+  password: "benchmarkdbpass",
+  database: "hello_world",
   max: 1,
   max: 1,
 });
 });
 
 
-export const fortunes = () =>
-  sql<Fortune[]>`SELECT id, message FROM fortune`;
+export const fortunes = () => sql<Fortune[]>`SELECT id, message FROM fortune`;
 
 
 export const find = (id: number) =>
 export const find = (id: number) =>
   sql<World[]>`SELECT id, randomNumber FROM world WHERE id = ${id}`.then(
   sql<World[]>`SELECT id, randomNumber FROM world WHERE id = ${id}`.then(
-    (arr) => arr[0]
+    (arr) => arr[0],
   );
   );
 
 
-export const bulkUpdate = (worlds: World[]) =>
-  sql`UPDATE world SET randomNumber = (update_data.randomNumber)::int
-  FROM (VALUES ${sql(
-    worlds
-      .map((world) => [world.id, world.randomNumber])
-      .sort((a, b) => (a[0] < b[0] ? -1 : 1))
-  )}) AS update_data (id, randomNumber)
-  WHERE world.id = (update_data.id)::int`;
+export const bulkUpdate = (worlds: World[]) => {
+  worlds = worlds.toSorted((a, b) => a.id - b.id);
+
+  const values = new Array(worlds.length);
+  for (let i = 0; i < worlds.length; i++) {
+    values[i] = [worlds[i].id, worlds[i].randomNumber];
+  }
+
+  return sql`UPDATE world SET randomNumber = (update_data.randomNumber)::int
+	FROM (VALUES ${sql(values)}) AS update_data (id, randomNumber)
+	WHERE world.id = (update_data.id)::int`;
+};

+ 20 - 0
frameworks/TypeScript/elysia/src/server.ts

@@ -0,0 +1,20 @@
+import { Elysia } from "elysia";
+import { dbHandlers } from "./db-handlers";
+
+const app = new Elysia()
+  .headers({
+    server: "Elysia",
+  })
+  .get("/plaintext", "Hello, World!")
+  // As state on xiv in https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview#requirements
+  // The serialization to JSON must not be cached;
+  // the computational effort to serialize an object to JSON must occur within the scope of handling each request.
+  .get("/json", () => ({ message: "Hello, World!" }))
+  .use((app) => {
+    if (Bun.env.DATABASE) app.use(dbHandlers);
+
+    return app;
+  })
+  .listen(8080);
+
+console.info(`🦊 Elysia is running at ${app.server!.url}`);