Skip to content

Commit 8edd742

Browse files
committed
feat(observability): spin up benchmarking against the Spanner emulator
1 parent f01516e commit 8edd742

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Observability Benchmarking
2+
3+
This code serves to produce an impact assessment of the effect of adding observability to each call.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*!
2+
* Copyright 2024 Google LLC. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
'use strict';
18+
19+
const {MutationSet, Spanner} = require('@google-cloud/spanner');
20+
21+
async function main(projectId, instanceId, databaseId) {
22+
const spanner = new Spanner({
23+
projectId: projectId,
24+
observabilityOptions: {
25+
enableExtendedTracing: true,
26+
},
27+
});
28+
29+
const instance = spanner.instance(instanceId);
30+
const database = instance.database(databaseId);
31+
32+
const runners = [
33+
databaseWriteAtLeastOnce,
34+
dqlSelect1,
35+
dqlSelectWithSyntaxError,
36+
dmlDeleteThenInsert,
37+
dmlWithDuplicates,
38+
databasBatcheCreateSessions,
39+
databasGetSessions,
40+
databaseRun,
41+
databaseRunWithSyntaxError,
42+
databaseRunWithDelete,
43+
databaseRunWithDeleteFromNonExistentTable,
44+
];
45+
46+
console.log('Running benchmarks!');
47+
const withSyntaxErrors = [false];
48+
49+
const latencies = new Map();
50+
for (const fn of runners) {
51+
const method = fn.name;
52+
console.log(method);
53+
for (const withSyntaxError of withSyntaxErrors) {
54+
const latencyL = [];
55+
let i = 0;
56+
for (i = 0; i < 100; i++) {
57+
const startTime = process.hrtime.bigint();
58+
try {
59+
await fn(database);
60+
} catch (e) {
61+
} finally {
62+
latencyL.push(process.hrtime.bigint() - startTime);
63+
}
64+
}
65+
66+
latencyL.sort((a, b) => {
67+
if (a < b) return -1;
68+
if (a > b) return 1;
69+
return 0;
70+
});
71+
72+
if (withSyntaxError) {
73+
processLatencies(method +' withSyntaxError', latencyL);
74+
} else {
75+
processLatencies(method, latencyL);
76+
}
77+
}
78+
}
79+
}
80+
81+
function processLatencies(method, latencyL) {
82+
const n = latencyL.length;
83+
const p50 = humanize(latencyL[Math.floor(n * 0.5)]);
84+
const p75 = humanize(latencyL[Math.floor(n * 0.75)]);
85+
const p95 = humanize(latencyL[Math.floor(n * 0.95)]);
86+
const p99 = humanize(latencyL[Math.floor(n * 0.99)]);
87+
console.log(
88+
`\tp50: ${p50}\n\tp75: ${p75}\n\tp95: ${p95}\n\tp99: ${p99}\n`
89+
);
90+
}
91+
92+
const units = ['ns', 'us', 'ms', 's'];
93+
94+
function humanize(ns) {
95+
let value = ns;
96+
for (const unit of units) {
97+
if (value < 1000) {
98+
return `${value} ${unit}`;
99+
}
100+
value = value/1000n;
101+
}
102+
103+
return `${value} ${units[units.length-1]}`;
104+
}
105+
106+
async function dqlSelect1(database) {
107+
const [snapshot] = await database.getSnapshot();
108+
const [rows] = await snapshot.run('SELECT 1');
109+
await snapshot.end();
110+
}
111+
112+
async function dqlSelectWithSyntaxError(database) {
113+
const [snapshot] = await database.getSnapshot();
114+
try {
115+
const [rows] = await snapshot.run('SELECT 1');
116+
} finally {
117+
await snapshot.end();
118+
}
119+
}
120+
121+
async function dmlDeleteThenInsert(database) {
122+
await database.runTransactionAsync(async tx => {
123+
const [updateCount1] = await tx.runUpdate('DELETE FROM Singers WHERE 1=1');
124+
const [updateCount2] = await tx.runUpdate(
125+
"INSERT INTO Singers(SingerId, firstName) VALUES(1, 'DTB')"
126+
);
127+
await tx.commit();
128+
});
129+
}
130+
131+
async function dmlWithDuplicates(database) {
132+
return await database.runTransactionAsync(async tx => {
133+
try {
134+
const [updateCount1] = await tx.runUpdate(
135+
"INSERT INTO Singers(SingerId, firstName) VALUES(1, 'DTB')"
136+
);
137+
const [updateCount2] = await tx.runUpdate(
138+
"INSERT INTO Singers(SingerId, firstName) VALUES(1, 'DTB')"
139+
);
140+
} catch(e) {
141+
} finally {
142+
await tx.end();
143+
}
144+
});
145+
}
146+
147+
async function databasBatcheCreateSessions(database) {
148+
return await database.batchCreateSessions(10);
149+
}
150+
151+
async function databasGetSessions(database) {
152+
return await database.getSessions();
153+
}
154+
155+
async function databaseRun(database) {
156+
return await database.run('SELECT 1');
157+
}
158+
159+
async function databaseRunWithSyntaxError(database) {
160+
return await database.run('SELECT 10 p');
161+
}
162+
163+
async function databaseRunWithDelete(database) {
164+
return await database.run('DELETE FROM Singers WHERE 1=1');
165+
}
166+
167+
async function databaseRunWithDeleteFromNonExistentTable(database) {
168+
return await database.run('DELETE FROM NonExistent WHERE 1=1');
169+
}
170+
171+
async function databaseWriteAtLeastOnce(database) {
172+
const mutations = new MutationSet();
173+
mutations.upsert('Singers', {
174+
SingerId: 1,
175+
FirstName: 'Scarlet',
176+
LastName: 'Terry',
177+
});
178+
mutations.upsert('Singers', {
179+
SingerId: 2,
180+
FirstName: 'Marc',
181+
LastName: 'Richards',
182+
});
183+
184+
const [response, err] = await database.writeAtLeastOnce(mutations, {});
185+
}
186+
187+
process.on('unhandledRejection', err => {
188+
console.error(err.message);
189+
process.exitCode = 1;
190+
});
191+
main(...process.argv.slice(2));

0 commit comments

Comments
 (0)