Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bench/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/*
package-lock.json
8 changes: 8 additions & 0 deletions bench/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Running benchmark
===
This benchmark compare the latest version of pg against the current source code.
- pg-latest: is the latest version of pg published on npm
- pg-current: is the current source code
```
npm run bench
```
27 changes: 27 additions & 0 deletions bench/cases/access.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import BaseCase from "./base.js";
import { WideRowsGenerator } from "../generators/index.js";

class AccessCase extends BaseCase {
constructor(pgLib) {
super(pgLib, 'processing');
}
async prepare() {
await super.prepare(WideRowsGenerator, { rows: 10000, fields: 100 });
}
async implementation(generator) {
let result = await generator.query(`SELECT * FROM "${generator.id}"`);
this.startMeasurement('processing');
for (let row of result.rows) {
this.runRow(row);
}
this.endMeasurement('processing');
}
runRow(row) {
let result = '';
for (let key in row) {
result += row[key];
}
}
}

export default AccessCase;
92 changes: 92 additions & 0 deletions bench/cases/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as Generators from "../generators/index.js";
import _ from 'lodash';

class BaseCase {
#measurements = {};
#mainMeasurement;
#generator;
#pgLib;
#pool;
#client;
constructor(pgLib, mainMeasurement = 'full') {
this.#pgLib = pgLib;
this.#mainMeasurement = mainMeasurement;
}
async prepare(generator, cfg = {}) {
if (generator) {
if (generator instanceof Generators.BaseGenerator) {
this.#generator = generator;
await this.#generator.run();
} else if (typeof generator == 'function' && 'prototype' in generator) {
this.#generator = new generator(this.#client);
await this.#generator.run(cfg);
}
}
}
async #setup() {
this.#pool = new this.#pgLib.Pool({

});
this.#client = await this.#pool.connect();
await this.#client.query(`SET work_mem = '512MB'`);
}
async #teardown() {
await this.#client.release();
await this.#pool.end();
}
async run() {
this.startMeasurement('total');
this.startMeasurement('setup');
await this.#setup();
this.endMeasurement('setup');
this.startMeasurement('prepare');
await this.prepare();
this.endMeasurement('prepare');

this.startMeasurement('full');
if (typeof this.implementation != 'function') {
throw new Error(`Implementation not provided`);
}
await this.implementation(this.#generator);
this.endMeasurement('full');

this.startMeasurement('clean');
await this.clean();
this.endMeasurement('clean');
this.startMeasurement('teardown');
await this.#teardown();
this.endMeasurement('teardown');
this.endMeasurement('total');
let details = _.mapValues(this.#measurements, (obj, k) => _.round(obj.total, 2));

return {
main: details[this.#mainMeasurement],
details
};
}
async clean() {

}
startMeasurement(name) {
if (this.#measurements[name]) {
throw new Error(`Measurement ${name} was already started`);
}
this.#measurements[name] = {
start: process.hrtime.bigint(),
end: null,
total: null
};
}
endMeasurement(name) {
if (!this.#measurements[name]) {
throw new Error(`Measurement ${name} was never started`);
}
if (this.#measurements[name].end) {
throw new Error(`Measurement ${name} has already ended`);
}
this.#measurements[name].end = process.hrtime.bigint();
this.#measurements[name].total = new Number(this.#measurements[name].end - this.#measurements[name].start) / 1000 / 1000;
}
}

export default BaseCase;
24 changes: 24 additions & 0 deletions bench/cases/clone_using_assign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import BaseCase from "./base.js";
import { WideRowsGenerator } from "../generators/index.js";

class CloneUsingAssignCase extends BaseCase {
constructor(pgLib) {
super(pgLib, 'processing');
}
async prepare() {
await super.prepare(WideRowsGenerator, { rows: 10000, fields: 100 });
}
async implementation(generator) {
let result = await generator.query(`SELECT * FROM "${generator.id}"`);
this.startMeasurement('processing');
for (let row of result.rows) {
this.runRow(row);
}
this.endMeasurement('processing');
}
runRow(row) {
let clone = Object.assign({}, row);
}
}

export default CloneUsingAssignCase;
24 changes: 24 additions & 0 deletions bench/cases/clone_using_spread.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import BaseCase from "./base.js";
import { WideRowsGenerator } from "../generators/index.js";

class CloneUsingSpreadCase extends BaseCase {
constructor(pgLib) {
super(pgLib, 'processing');
}
async prepare() {
await super.prepare(WideRowsGenerator, { rows: 10000, fields: 100 });
}
async implementation(generator) {
let result = await generator.query(`SELECT * FROM "${generator.id}"`);
this.startMeasurement('processing');
for (let row of result.rows) {
this.runRow(row);
}
this.endMeasurement('processing');
}
runRow(row) {
let clone = { ... row };
}
}

export default CloneUsingSpreadCase;
13 changes: 13 additions & 0 deletions bench/cases/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import CloneUsingAssignCase from "./clone_using_assign.js";
import CloneUsingSpreadCase from "./clone_using_spread.js";
import AccessCase from "./access.js";
import ValuesCase from "./values.js";
import KeysCase from "./keys.js";

export {
CloneUsingAssignCase,
CloneUsingSpreadCase,
AccessCase,
ValuesCase,
KeysCase
};
24 changes: 24 additions & 0 deletions bench/cases/keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import BaseCase from "./base.js";
import { WideRowsGenerator } from "../generators/index.js";

class KeysCase extends BaseCase {
constructor(pgLib) {
super(pgLib, 'processing');
}
async prepare() {
await super.prepare(WideRowsGenerator, { rows: 10000, fields: 100 });
}
async implementation(generator) {
let result = await generator.query(`SELECT * FROM "${generator.id}"`);
this.startMeasurement('processing');
for (let row of result.rows) {
this.runRow(row);
}
this.endMeasurement('processing');
}
runRow(row) {
return Object.keys(row);
}
}

export default KeysCase;
24 changes: 24 additions & 0 deletions bench/cases/values.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import BaseCase from "./base.js";
import { WideRowsGenerator } from "../generators/index.js";

class ValuesCase extends BaseCase {
constructor(pgLib) {
super(pgLib, 'processing');
}
async prepare() {
await super.prepare(WideRowsGenerator, { rows: 10000, fields: 100 });
}
async implementation(generator) {
let result = await generator.query(`SELECT * FROM "${generator.id}"`);
this.startMeasurement('processing');
for (let row of result.rows) {
this.runRow(row);
}
this.endMeasurement('processing');
}
runRow(row) {
return Object.values(row);
}
}

export default ValuesCase;
64 changes: 64 additions & 0 deletions bench/generators/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import crypto from 'node:crypto';
import _ from 'lodash';
import { v4 as uuidv4 } from 'uuid';

class BaseGenerator {
#id;
#client;
constructor(client) {
this.#client = client;
this.#id = this.randomString();
}
get id() {
return this.#id;
}
async run() {
throw new Error(`Run not implemented in generator`);
}
async cleanup() {
throw new Error(`Cleanup not implemented in generator`);
}
async query(query, values) {
let start = +new Date();
//console.log(`> ${query.substr(0, 100)}`);
let result = await this.#client.query(query, values);
let end = +new Date();
//console.log(`< ${query.substr(0, 100)}; ${end-start}ms`);
return result;
}
async create(tableName, fields) {
await this.query(`CREATE TABLE "${tableName}" (${_.map(fields, f => `${f.name} ${f.type}`).join(', ')})`);
}
async insert(tableName, fields, data, chunkSize = 10000) {
let chunks = _.chunk(data, chunkSize);
for (let chunk of chunks) {
let valuePlaceHolders = [];
let values = [];
let i = 1;
for (let field of fields) {
let fieldData = new Array(chunk.length)
for (let j = 0; j < chunk.length; j++) {
fieldData[j] = chunk[j][field.name];
}
valuePlaceHolders.push(`$${i}::${field.type}[]`);
values.push(fieldData);
i++;
}
let start = +new Date();
await this.query(`INSERT INTO "${tableName}" (${_.map(fields, f => f.name).join(', ')}) SELECT * FROM UNNEST (${valuePlaceHolders.join(', ')})`, values);
let end = +new Date();
//console.log('actual insert took', end - start);
}
}
async drop(tableName) {
await this.query(`DROP TABLE "${tableName}"`);
}
uuid() {
return crypto.randomUUID();
}
randomString(length = 16) {
return Math.random().toString(20).substr(2, 6);
}
}

export default BaseGenerator;
7 changes: 7 additions & 0 deletions bench/generators/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import WideRowsGenerator from "./wide_rows.js";
import BaseGenerator from "./base.js";

export {
BaseGenerator,
WideRowsGenerator
};
58 changes: 58 additions & 0 deletions bench/generators/wide_rows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import BaseGenerator from "./base.js";

class WideRowsGenerator extends BaseGenerator {
#typeConfigs = {
int: () => {
return Math.floor(Math.random() * 1000000);
},
uuid: () => {
return this.uuid();
},
text: () => {
return this.uuid(100);
}
};
constructor(client) {
super(client)
}
async run(config) {
if (!config) {
throw new Error(`Config not passed to run`);
}
if (!config.rows) {
throw new Error(`Rows not passed`);
}
if (!config.fields) {
throw new Error(`Fields not passed`);
}
let fields = [];
for (let i = 0; i < config.fields; i++) {
let typeIndex = i % Object.keys(this.#typeConfigs).length;
fields.push({
name: `col_${i}`,
type: Object.keys(this.#typeConfigs)[typeIndex],
generator: this.#typeConfigs[Object.keys(this.#typeConfigs)[typeIndex]]
});
}
await this.create(this.id, fields);
let data = [];
let beforeGenerate = +new Date();
for (let i = 0; i < config.rows; i++) {
let obj = {};
for (let field of fields) {
obj[field.name] = field.generator();
}
data.push(obj);
}
let afterGenerate = +new Date();
//console.log(`generating; ${afterGenerate-beforeGenerate}ms`);
await this.insert(this.id, fields, data);
let afterInsert = +new Date();
//console.log(`inserting; ${afterInsert-afterGenerate}ms`);
}
async cleanup() {
await this.drop(this.id);
}
}

export default WideRowsGenerator;
Loading
Loading