Browse Source

Created Node.js tests with postgres.js module (#8521)

* 1. Added the postgres.js module for postgres queries in addition to sequelize currently used.
2. Removed chakra as it is not running at all.
3. Added proper display names for tests so that its clear from the results.
4. Added seperate tests for each of the postgresjs tests.
5. Used slow-json-stringify to improve json parsing.
6. Uses different sort method to slightly improve perfomance.
7. Updated the nodejs version to pickup latest stable version instead of manually updating every year.
8. Added Environment variable node_env as production to improve performance.
9. Implemented all database routes using postgresjs including cached using node-cache.
10. Improved fortunes by avoiding handlebars based rendering.

* 11. updated with  Major.Minus version instead of using latest lts version.

12. Removed extra permutations created and merged all postgresjs tests into one test.

13. Removed extra permutations for plaintext, cached queries
srisaiswaroop 1 year ago
parent
commit
895541d40b

+ 5 - 1
frameworks/JavaScript/nodejs/app.js

@@ -1,7 +1,7 @@
 const cluster = require('cluster');
 const numCPUs = require('os').cpus().length;
 
-process.env.NODE_HANDLER = 'mysql-raw';
+process.env.NODE_HANDLER = 'postgres';
 
 if (process.env.TFB_TEST_NAME === 'nodejs-mongodb') {
   process.env.NODE_HANDLER = 'mongoose';
@@ -9,8 +9,12 @@ if (process.env.TFB_TEST_NAME === 'nodejs-mongodb') {
   process.env.NODE_HANDLER = 'mongodb-raw';
 } else if (process.env.TFB_TEST_NAME === 'nodejs-mysql') {
   process.env.NODE_HANDLER = 'sequelize';
+} else if (process.env.TFB_TEST_NAME === 'nodejs-mysql-raw') {
+  process.env.NODE_HANDLER = 'mysql-raw';
 } else if (process.env.TFB_TEST_NAME === 'nodejs-postgres') {
   process.env.NODE_HANDLER = 'sequelize-postgres';
+}else if (process.env.TFB_TEST_NAME === 'nodejs-postgresjs-raw') {
+  process.env.NODE_HANDLER = 'postgres';
 }
 
 if (cluster.isPrimary) {

+ 39 - 24
frameworks/JavaScript/nodejs/benchmark_config.json

@@ -4,61 +4,75 @@
     "default": {
       "json_url": "/json",
       "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "Postgres",
+      "framework": "nodejs",
+      "language": "JavaScript",
+      "flavor": "NodeJS",
+      "orm": "Raw",
+      "platform": "nodejs",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "nodejs",
+      "notes": "",
+      "versus": "nodejs"
+    },
+    "mongodb": {
+      "dockerfile": "nodejs.dockerfile",
       "db_url": "/db",
       "query_url": "/queries?queries=",
-      "cached_query_url": "/cached?queries=",
       "update_url": "/updates?queries=",
-      "fortune_url": "/fortunes",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
-      "database": "MySQL",
+      "database": "MongoDB",
       "framework": "nodejs",
       "language": "JavaScript",
       "flavor": "NodeJS",
-      "orm": "Raw",
+      "orm": "Full",
       "platform": "nodejs",
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "nodejs",
+      "display_name": "nodejs-mongodb",
       "notes": "",
       "versus": "nodejs"
     },
-    "chakra": {
+    "mongodb-raw": {
       "dockerfile": "nodejs.dockerfile",
-      "json_url": "/json",
-      "plaintext_url": "/plaintext",
       "db_url": "/db",
       "query_url": "/queries?queries=",
-      "cached_query_url": "/cached?queries=",
       "update_url": "/updates?queries=",
       "fortune_url": "/fortunes",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
-      "database": "MySQL",
-      "framework": "None",
+      "database": "MongoDB",
+      "framework": "nodejs",
       "language": "JavaScript",
-      "flavor": "node-chakracore",
+      "flavor": "NodeJS",
       "orm": "Raw",
       "platform": "nodejs",
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "node-chakracore",
+      "display_name": "nodejs-mongodb-raw",
       "notes": "",
       "versus": "nodejs"
     },
-    "mongodb": {
+    "mysql": {
       "dockerfile": "nodejs.dockerfile",
       "db_url": "/db",
       "query_url": "/queries?queries=",
       "update_url": "/updates?queries=",
+      "fortune_url": "/fortunes",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
-      "database": "MongoDB",
+      "database": "MySQL",
       "framework": "nodejs",
       "language": "JavaScript",
       "flavor": "NodeJS",
@@ -67,20 +81,21 @@
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "nodejs",
+      "display_name": "nodejs-mysql",
       "notes": "",
       "versus": "nodejs"
     },
-    "mongodb-raw": {
+    "mysql-raw": {
       "dockerfile": "nodejs.dockerfile",
       "db_url": "/db",
       "query_url": "/queries?queries=",
       "update_url": "/updates?queries=",
       "fortune_url": "/fortunes",
+      "cached_query_url": "/cached?queries=",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
-      "database": "MongoDB",
+      "database": "MySQL",
       "framework": "nodejs",
       "language": "JavaScript",
       "flavor": "NodeJS",
@@ -89,11 +104,11 @@
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "nodejs",
+      "display_name": "nodejs-mysql-raw",
       "notes": "",
       "versus": "nodejs"
     },
-    "mysql": {
+    "postgresjs-raw": {
       "dockerfile": "nodejs.dockerfile",
       "db_url": "/db",
       "query_url": "/queries?queries=",
@@ -102,16 +117,16 @@
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
-      "database": "MySQL",
+      "database": "Postgres",
       "framework": "nodejs",
       "language": "JavaScript",
       "flavor": "NodeJS",
-      "orm": "Full",
+      "orm": "Raw",
       "platform": "nodejs",
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "nodejs",
+      "display_name": "nodejs-postgresjs-raw",
       "notes": "",
       "versus": "nodejs"
     },
@@ -133,7 +148,7 @@
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "nodejs",
+      "display_name": "nodejs-postgres-sequel",
       "notes": "",
       "versus": "nodejs"
     }

+ 109 - 0
frameworks/JavaScript/nodejs/handlers/postgres.js

@@ -0,0 +1,109 @@
+const postgres = require("postgres");
+const NodeCache = require("node-cache");
+const h = require("../helper");
+
+const sql = postgres({
+  host: "tfb-database",
+  user: "benchmarkdbuser",
+  password: "benchmarkdbpass",
+  database: "hello_world",
+  max: 1,
+});
+
+const dbfortunes = async () => await sql`SELECT id, message FROM fortune`;
+
+const dbfind = async (id) =>
+  await sql`SELECT id, randomNumber FROM world WHERE id = ${id}`.then(
+    (arr) => arr[0]
+  );
+
+const dbbulkUpdate = async (worlds) =>
+  await 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`;
+
+const dbgetAllWorlds = async () => sql`SELECT id, randomNumber FROM world`;
+
+const extra = h.additionalFortune();
+
+const myCache = new NodeCache({ stdTTL: 0, checkperiod: 0 });
+
+let isCachePopulated = false;
+
+const populateCache = (callback) => {
+  if (isCachePopulated) return callback();
+
+  dbgetAllWorlds().then((worlds) => {
+    for (let i = 0; i < worlds.length; i++) {
+      myCache.set(worlds[i].id, worlds[i]);
+    }
+    isCachePopulated = true;
+    callback();
+  });
+};
+
+module.exports = {
+  SingleQuery: async (req, res) => {
+    const row = await dbfind(h.generateRandomNumber());
+    h.writeResponse(res, h.worldObjectSerializer(row));
+  },
+
+  MultipleQueries: async (queries, req, res) => {
+    const databaseJobs = new Array(queries);
+    for (let i = 0; i < queries; i++) {
+      databaseJobs[i] = dbfind(h.generateRandomNumber());
+    }
+    const worldObjects = await Promise.all(databaseJobs);
+
+    h.writeResponse(res, JSON.stringify(worldObjects));
+  },
+
+  Fortunes: async (req, res) => {
+    const rows = [extra, ...(await dbfortunes())];
+    h.sortByMessage(rows);
+    const n = rows.length;
+    let html = "", i = 0;
+    for (; i < n; i++) {
+      html += `<tr><td>${rows[i].id}</td><td>${h.escapeHtmlFromText(rows[i].message)}</td></tr>`;
+    }
+
+    h.writeResponse(
+      res,
+      `<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>${html}</table></body></html>`,
+      h.headerTypes["html"]
+    );
+  },
+
+  Updates: async (queries, req, res) => {
+    const databaseJobs = new Array(queries);
+
+    for (let i = 0; i < queries; i++) {
+      databaseJobs[i] = dbfind(h.generateRandomNumber());
+    }
+
+    const worldObjects = await Promise.all(databaseJobs);
+
+    for (let i = 0; i < queries; i++) {
+      worldObjects[i].randomNumber = h.generateRandomNumber();
+    }
+
+    await dbbulkUpdate(worldObjects);
+
+    h.writeResponse(res, JSON.stringify(worldObjects));
+  },
+  CachedQueries: (queries, req, res) => {
+    populateCache(() => {
+      const worlds = new Array(queries);
+
+      for (let i = 0; i < queries; i++) {
+        worlds[i] = myCache.get(h.generateRandomNumber());
+      }
+
+      h.writeResponse(res, JSON.stringify(worlds));
+    });
+  },
+};

+ 73 - 30
frameworks/JavaScript/nodejs/helper.js

@@ -1,4 +1,5 @@
 const Handlebars = require('handlebars');
+const { sjs, attr } = require("slow-json-stringify");
 
 const GREETING = "Hello, World!";
 
@@ -8,33 +9,79 @@ const headerTypes = {
   html:  'text/html; charset=UTF-8'
 };
 
-const self = module.exports = {
+function sortByMessage(arr) {
+  const n = arr.length;
+  for (let i = 1; i < n; i++) {
+    const c = arr[i];
+    let j = i - 1;
+    while (j > -1 && c.message < arr[j].message) {
+      arr[j + 1] = arr[j];
+      j--;
+    }
+    arr[j + 1] = c;
+  }
+  return arr;
+}
+
+function generateRandomNumber(){
+  return Math.ceil(Math.random() * 10000);
+}
+
+const jsonSerializer = sjs({ message: attr("string") });
+const worldObjectSerializer = sjs({ id: attr("number"), randomnumber: attr("number")});
+const escapeHTMLRules = { '&': '&#38;', '<': '&#60;', '>': '&#62;', '"': '&#34;', "'": '&#39;', '/': '&#47;' }
+
+const unsafeHTMLMatcher = /[&<>"'\/]/g
+
+function escapeHtmlFromText(text) {
+  if (unsafeHTMLMatcher.test(text) === false) return text;
+  return text.replace(unsafeHTMLMatcher, function (m) { return escapeHTMLRules[m] || m; });
+}
+
+function writeResponse(res, text, type = headerTypes["json"]) {
+  res.writeHead(200, {
+    "content-type": type,
+    server: "Node",
+  });
+  res.end(text);
+}
+
+const self = (module.exports = {
+  sortByMessage,
+  generateRandomNumber,
+  jsonSerializer,
+  worldObjectSerializer,
+  escapeHtmlFromText,
+  writeResponse,
+  headerTypes,
 
   additionalFortune: () => ({
     id: 0,
-    message: 'Additional fortune added at request time.'
+    message: "Additional fortune added at request time.",
   }),
 
-  fortunesTemplate: Handlebars.compile([
-    "<!DOCTYPE html>",
-    "<html>",
-    "<head><title>Fortunes</title></head>",
-    "<body>",
-    "<table>",
+  fortunesTemplate: Handlebars.compile(
+    [
+      "<!DOCTYPE html>",
+      "<html>",
+      "<head><title>Fortunes</title></head>",
+      "<body>",
+      "<table>",
       "<tr>",
-        "<th>id</th>",
-        "<th>message</th>",
+      "<th>id</th>",
+      "<th>message</th>",
       "</tr>",
       "{{#fortunes}}",
       "<tr>",
-        "<td>{{id}}</td>",
-        "<td>{{message}}</td>",
+      "<td>{{id}}</td>",
+      "<td>{{message}}</td>",
       "</tr>",
       "{{/fortunes}}",
-    "</table>",
-    "</body>",
-    "</html>"
-  ].join('')),
+      "</table>",
+      "</body>",
+      "</html>",
+    ].join("")
+  ),
 
   randomTfbNumber: () => Math.floor(Math.random() * 10000) + 1,
 
@@ -49,27 +96,23 @@ const self = module.exports = {
   addTfbHeaders: (res, headerType) => {
     res.setHeader('Server', 'Node');
     res.setHeader('Content-Type', headerTypes[headerType]);
-},
+      },
 
   responses: {
-
     jsonSerialization: (req, res) => {
-      const HELLO_OBJ = { message: GREETING };
-      self.addTfbHeaders(res, 'json');
-      res.end(JSON.stringify(HELLO_OBJ));
+      writeResponse(res, jsonSerializer({ message: GREETING }));
     },
 
     plaintext: (req, res) => {
-      self.addTfbHeaders(res, 'plain');
-      res.end(GREETING);
+      writeResponse(res, GREETING, headerTypes['plain']);
     },
 
     routeNotImplemented: (req, res) => {
-      res.writeHead(501, {'Content-Type': 'text/plain; charset=UTF-8'});
-      const reason = { reason: "`" + req.url + "` is not an implemented route" };
+      res.writeHead(501, { "Content-Type": "text/plain; charset=UTF-8" });
+      const reason = {
+        reason: "`" + req.url + "` is not an implemented route",
+      };
       res.end(JSON.stringify(reason));
-    }
-
-  }
-
-};
+    },
+  },
+});

+ 2 - 1
frameworks/JavaScript/nodejs/nodejs.dockerfile

@@ -1,4 +1,4 @@
-FROM node:18.12.1-slim
+FROM node:21.1.0-slim
 
 ARG TFB_TEST_NAME
 
@@ -6,6 +6,7 @@ COPY ./ ./
 
 RUN npm install
 
+ENV NODE_ENV production
 ENV TFB_TEST_NAME=$TFB_TEST_NAME
 
 EXPOSE 8080

+ 2 - 0
frameworks/JavaScript/nodejs/package.json

@@ -12,6 +12,8 @@
     "parseurl": "1.3.2",
     "pg": "8.5.0",
     "pg-hstore": "2.3.2",
+    "postgres": "^3.4.3",
+    "slow-json-stringify": "^2.0.1",
     "sequelize": "6.29.0",
     "node-cache": "4.1.1"
   },