orez 0.2.26 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +9 -1
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/pg-proxy-do-backend.d.ts +2 -0
- package/dist/pg-proxy-do-backend.d.ts.map +1 -1
- package/dist/pg-proxy-do-backend.js +49 -7
- package/dist/pg-proxy-do-backend.js.map +1 -1
- package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
- package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
- package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
- package/dist/pg-sqlite-compiler/index.d.ts +12 -0
- package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/index.js +59 -0
- package/dist/pg-sqlite-compiler/index.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
- package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
- package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
- package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
- package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
- package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/index.js +39 -0
- package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
- package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/types.js +103 -0
- package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
- package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
- package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
- package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
- package/dist/pg-sqlite-compiler/types.d.ts +55 -0
- package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/types.js +2 -0
- package/dist/pg-sqlite-compiler/types.js.map +1 -0
- package/package.json +8 -4
- package/src/admin/admin-data.test.ts +0 -348
- package/src/admin/http-proxy.ts +0 -252
- package/src/admin/log-store.ts +0 -192
- package/src/admin/server.ts +0 -471
- package/src/admin/ui.ts +0 -1322
- package/src/bench/proxy-throughput.bench.ts +0 -343
- package/src/bench/serial-mutations.bench.ts +0 -270
- package/src/browser.ts +0 -203
- package/src/cf-do/.wrangler/cache/cf.json +0 -1
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
- package/src/cf-do/ARCHITECTURE.md +0 -83
- package/src/cf-do/watermark.test.ts +0 -103
- package/src/cf-do/watermark.ts +0 -118
- package/src/cf-do/worker.ts +0 -1033
- package/src/cf-do/wrangler.toml +0 -11
- package/src/cf-pglite/README.md +0 -19
- package/src/change-tracking.ts +0 -25
- package/src/child-process.test.ts +0 -147
- package/src/child-process.ts +0 -90
- package/src/cli-entry.ts +0 -72
- package/src/cli.test.ts +0 -38
- package/src/cli.ts +0 -1214
- package/src/config.ts +0 -150
- package/src/do-sql-tracking.test.ts +0 -19
- package/src/do-sql-tracking.ts +0 -19
- package/src/index.ts +0 -1215
- package/src/integration/integration.test.ts +0 -517
- package/src/integration/native-binary.guard.test.ts +0 -13
- package/src/integration/native-startup.test.ts +0 -44
- package/src/integration/replication-latency.test.ts +0 -428
- package/src/integration/restore-live-stress.test.ts +0 -433
- package/src/integration/restore-reset.test.ts +0 -400
- package/src/integration/restore.test.ts +0 -274
- package/src/integration/test-permissions.ts +0 -147
- package/src/load-config.ts +0 -46
- package/src/log.ts +0 -96
- package/src/mutex.ts +0 -47
- package/src/pg-proxy-browser.singledb.test.ts +0 -233
- package/src/pg-proxy-browser.ts +0 -2022
- package/src/pg-proxy-do-backend.test.ts +0 -3890
- package/src/pg-proxy-do-backend.ts +0 -7157
- package/src/pg-proxy.ts +0 -1087
- package/src/pglite-ipc.test.ts +0 -116
- package/src/pglite-ipc.ts +0 -266
- package/src/pglite-manager.ts +0 -557
- package/src/pglite-web-proxy.test.ts +0 -57
- package/src/pglite-web-proxy.ts +0 -221
- package/src/pglite-web-worker.ts +0 -152
- package/src/pglite-worker-thread.ts +0 -253
- package/src/port.ts +0 -25
- package/src/process-title.ts +0 -9
- package/src/recovery.ts +0 -155
- package/src/replication/change-tracker.test.ts +0 -357
- package/src/replication/change-tracker.ts +0 -279
- package/src/replication/handler.test.ts +0 -511
- package/src/replication/handler.ts +0 -1190
- package/src/replication/pgoutput-encoder.test.ts +0 -697
- package/src/replication/pgoutput-encoder.ts +0 -373
- package/src/replication/tcp-replication.test.ts +0 -876
- package/src/replication/zero-compat.test.ts +0 -1150
- package/src/restore-stress.test.ts +0 -188
- package/src/s3-local.ts +0 -203
- package/src/shim/hooks.mjs +0 -120
- package/src/shim/register.mjs +0 -4
- package/src/sqlite-mode/apply-mode.ts +0 -224
- package/src/sqlite-mode/index.ts +0 -15
- package/src/sqlite-mode/native-binary.ts +0 -89
- package/src/sqlite-mode/package-resolve.ts +0 -17
- package/src/sqlite-mode/resolve-mode.ts +0 -80
- package/src/sqlite-mode/shim-template.ts +0 -159
- package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
- package/src/sqlite-mode/types.ts +0 -30
- package/src/vite-plugin.ts +0 -67
- package/src/wasm-sqlite.test.ts +0 -537
- package/src/worker/browser-admin.ts +0 -52
- package/src/worker/browser-build-config.test.ts +0 -71
- package/src/worker/browser-build-config.ts +0 -109
- package/src/worker/browser-embed-admin.test.ts +0 -75
- package/src/worker/browser-embed.ts +0 -345
- package/src/worker/cf-patches.ts +0 -384
- package/src/worker/embed-integration.test.ts +0 -321
- package/src/worker/index.ts +0 -138
- package/src/worker/shims/fastify.test.ts +0 -255
- package/src/worker/shims/fastify.ts +0 -306
- package/src/worker/shims/http-service.test.ts +0 -355
- package/src/worker/shims/http-service.ts +0 -293
- package/src/worker/shims/node-stub.ts +0 -290
- package/src/worker/shims/oxfmt.ts +0 -3
- package/src/worker/shims/postgres-browser.ts +0 -59
- package/src/worker/shims/postgres-socket.test.ts +0 -576
- package/src/worker/shims/postgres-socket.ts +0 -310
- package/src/worker/shims/postgres.test.ts +0 -364
- package/src/worker/shims/postgres.ts +0 -1454
- package/src/worker/shims/sqlite-browser.test.ts +0 -233
- package/src/worker/shims/sqlite-browser.ts +0 -175
- package/src/worker/shims/sqlite.test.ts +0 -786
- package/src/worker/shims/sqlite.ts +0 -978
- package/src/worker/shims/stream-browser.ts +0 -15
- package/src/worker/shims/ws-browser.test.ts +0 -205
- package/src/worker/shims/ws-browser.ts +0 -248
- package/src/worker/shims/ws.test.ts +0 -288
- package/src/worker/shims/ws.ts +0 -467
- package/src/worker/shims/zero-process-env.ts +0 -11
- package/src/worker/types.ts +0 -75
- package/src/worker/worker-integration.test.ts +0 -223
- package/src/worker/worker.test.ts +0 -136
- package/src/worker/zero-cache-embed-cf.ts +0 -463
- package/src/worker/zero-cache-embed.ts +0 -277
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* benchmark: proxy throughput
|
|
3
|
-
*
|
|
4
|
-
* measures raw query throughput through the TCP proxy pipeline:
|
|
5
|
-
* TCP socket → main thread proxy → worker thread → PGlite WASM → back
|
|
6
|
-
*
|
|
7
|
-
* tests both serial and concurrent query patterns to expose
|
|
8
|
-
* single-thread bottlenecks and mutex contention.
|
|
9
|
-
*
|
|
10
|
-
* run: bun src/bench/proxy-throughput.bench.ts
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { createConnection, type Socket } from 'node:net'
|
|
14
|
-
|
|
15
|
-
import { startZeroLite } from '../index.js'
|
|
16
|
-
|
|
17
|
-
import type { PGlite } from '@electric-sql/pglite'
|
|
18
|
-
|
|
19
|
-
const textEncoder = new TextEncoder()
|
|
20
|
-
const textDecoder = new TextDecoder()
|
|
21
|
-
|
|
22
|
-
// --- wire protocol helpers ---
|
|
23
|
-
|
|
24
|
-
function buildSimpleQuery(sql: string): Uint8Array {
|
|
25
|
-
const queryBytes = textEncoder.encode(sql + '\0')
|
|
26
|
-
const buf = new Uint8Array(5 + queryBytes.length)
|
|
27
|
-
buf[0] = 0x51 // 'Q'
|
|
28
|
-
new DataView(buf.buffer).setInt32(1, 4 + queryBytes.length)
|
|
29
|
-
buf.set(queryBytes, 5)
|
|
30
|
-
return buf
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function buildStartupMessage(user: string, database: string): Buffer {
|
|
34
|
-
const params = `user\0${user}\0database\0${database}\0\0`
|
|
35
|
-
const paramsBytes = Buffer.from(params, 'utf-8')
|
|
36
|
-
const len = 4 + 4 + paramsBytes.length // length + protocol version + params
|
|
37
|
-
const buf = Buffer.alloc(len)
|
|
38
|
-
buf.writeInt32BE(len, 0)
|
|
39
|
-
buf.writeInt32BE(196608, 4) // protocol 3.0
|
|
40
|
-
paramsBytes.copy(buf, 8)
|
|
41
|
-
return buf
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function buildPasswordMessage(password: string): Buffer {
|
|
45
|
-
const passBytes = Buffer.from(password + '\0', 'utf-8')
|
|
46
|
-
const len = 4 + passBytes.length
|
|
47
|
-
const buf = Buffer.alloc(1 + len)
|
|
48
|
-
buf[0] = 0x70 // 'p'
|
|
49
|
-
buf.writeInt32BE(len, 1)
|
|
50
|
-
passBytes.copy(buf, 5)
|
|
51
|
-
return buf
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// read messages from socket until we get ReadyForQuery (0x5a)
|
|
55
|
-
function readUntilReady(socket: Socket): Promise<Buffer[]> {
|
|
56
|
-
return new Promise((resolve, reject) => {
|
|
57
|
-
const messages: Buffer[] = []
|
|
58
|
-
let buffer = Buffer.alloc(0)
|
|
59
|
-
const timeout = setTimeout(
|
|
60
|
-
() => reject(new Error('timeout waiting for ReadyForQuery')),
|
|
61
|
-
10000
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
const onData = (data: Buffer) => {
|
|
65
|
-
buffer = Buffer.concat([buffer, data])
|
|
66
|
-
// parse complete messages
|
|
67
|
-
while (buffer.length >= 5) {
|
|
68
|
-
const msgType = buffer[0]
|
|
69
|
-
const msgLen = buffer.readInt32BE(1)
|
|
70
|
-
const totalLen = 1 + msgLen
|
|
71
|
-
if (buffer.length < totalLen) break
|
|
72
|
-
const msg = buffer.subarray(0, totalLen)
|
|
73
|
-
messages.push(Buffer.from(msg))
|
|
74
|
-
buffer = buffer.subarray(totalLen)
|
|
75
|
-
if (msgType === 0x5a) {
|
|
76
|
-
// ReadyForQuery
|
|
77
|
-
clearTimeout(timeout)
|
|
78
|
-
socket.off('data', onData)
|
|
79
|
-
resolve(messages)
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
socket.on('data', onData)
|
|
85
|
-
socket.on('error', (err) => {
|
|
86
|
-
clearTimeout(timeout)
|
|
87
|
-
reject(err)
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function connectPg(
|
|
93
|
-
port: number,
|
|
94
|
-
user: string,
|
|
95
|
-
password: string,
|
|
96
|
-
database = 'postgres'
|
|
97
|
-
): Promise<Socket> {
|
|
98
|
-
const socket = await new Promise<Socket>((resolve, reject) => {
|
|
99
|
-
const s = createConnection({ host: '127.0.0.1', port }, () => resolve(s))
|
|
100
|
-
s.setMaxListeners(0) // suppress MaxListenersExceeded warnings for benchmarks
|
|
101
|
-
s.on('error', reject)
|
|
102
|
-
})
|
|
103
|
-
socket.setNoDelay(true)
|
|
104
|
-
|
|
105
|
-
// startup
|
|
106
|
-
socket.write(buildStartupMessage(user, database))
|
|
107
|
-
|
|
108
|
-
// wait for auth request
|
|
109
|
-
await new Promise<void>((resolve) => {
|
|
110
|
-
const onData = (data: Buffer) => {
|
|
111
|
-
if (data[0] === 0x52) {
|
|
112
|
-
// AuthenticationCleartextPassword
|
|
113
|
-
socket.off('data', onData)
|
|
114
|
-
resolve()
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
socket.on('data', onData)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
// send password
|
|
121
|
-
socket.write(buildPasswordMessage(password))
|
|
122
|
-
|
|
123
|
-
// wait for ReadyForQuery
|
|
124
|
-
await readUntilReady(socket)
|
|
125
|
-
return socket
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function sendQuery(
|
|
129
|
-
socket: Socket,
|
|
130
|
-
sql: string
|
|
131
|
-
): Promise<{ messages: Buffer[]; elapsed: number }> {
|
|
132
|
-
const t0 = performance.now()
|
|
133
|
-
socket.write(buildSimpleQuery(sql))
|
|
134
|
-
const messages = await readUntilReady(socket)
|
|
135
|
-
return { messages, elapsed: performance.now() - t0 }
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// --- benchmarks ---
|
|
139
|
-
|
|
140
|
-
interface BenchResult {
|
|
141
|
-
name: string
|
|
142
|
-
ops: number
|
|
143
|
-
totalMs: number
|
|
144
|
-
opsPerSec: number
|
|
145
|
-
avgLatencyMs: number
|
|
146
|
-
p50Ms: number
|
|
147
|
-
p99Ms: number
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function percentile(sorted: number[], p: number): number {
|
|
151
|
-
const idx = Math.ceil(sorted.length * p) - 1
|
|
152
|
-
return sorted[Math.max(0, idx)]
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function formatResult(r: BenchResult) {
|
|
156
|
-
return [
|
|
157
|
-
` ${r.name}`,
|
|
158
|
-
` ops: ${r.ops}`,
|
|
159
|
-
` total: ${r.totalMs.toFixed(1)}ms`,
|
|
160
|
-
` throughput: ${r.opsPerSec.toFixed(0)} ops/sec`,
|
|
161
|
-
` avg latency: ${r.avgLatencyMs.toFixed(2)}ms`,
|
|
162
|
-
` p50: ${r.p50Ms.toFixed(2)}ms`,
|
|
163
|
-
` p99: ${r.p99Ms.toFixed(2)}ms`,
|
|
164
|
-
].join('\n')
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async function benchSerial(
|
|
168
|
-
socket: Socket,
|
|
169
|
-
sql: string,
|
|
170
|
-
ops: number,
|
|
171
|
-
name: string
|
|
172
|
-
): Promise<BenchResult> {
|
|
173
|
-
const latencies: number[] = []
|
|
174
|
-
const t0 = performance.now()
|
|
175
|
-
for (let i = 0; i < ops; i++) {
|
|
176
|
-
const { elapsed } = await sendQuery(socket, sql)
|
|
177
|
-
latencies.push(elapsed)
|
|
178
|
-
}
|
|
179
|
-
const totalMs = performance.now() - t0
|
|
180
|
-
latencies.sort((a, b) => a - b)
|
|
181
|
-
return {
|
|
182
|
-
name,
|
|
183
|
-
ops,
|
|
184
|
-
totalMs,
|
|
185
|
-
opsPerSec: (ops / totalMs) * 1000,
|
|
186
|
-
avgLatencyMs: totalMs / ops,
|
|
187
|
-
p50Ms: percentile(latencies, 0.5),
|
|
188
|
-
p99Ms: percentile(latencies, 0.99),
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async function benchConcurrent(
|
|
193
|
-
sockets: Socket[],
|
|
194
|
-
sql: string,
|
|
195
|
-
opsPerSocket: number,
|
|
196
|
-
name: string
|
|
197
|
-
): Promise<BenchResult> {
|
|
198
|
-
const allLatencies: number[] = []
|
|
199
|
-
const t0 = performance.now()
|
|
200
|
-
await Promise.all(
|
|
201
|
-
sockets.map(async (socket) => {
|
|
202
|
-
for (let i = 0; i < opsPerSocket; i++) {
|
|
203
|
-
const { elapsed } = await sendQuery(socket, sql)
|
|
204
|
-
allLatencies.push(elapsed)
|
|
205
|
-
}
|
|
206
|
-
})
|
|
207
|
-
)
|
|
208
|
-
const totalMs = performance.now() - t0
|
|
209
|
-
const ops = sockets.length * opsPerSocket
|
|
210
|
-
allLatencies.sort((a, b) => a - b)
|
|
211
|
-
return {
|
|
212
|
-
name,
|
|
213
|
-
ops,
|
|
214
|
-
totalMs,
|
|
215
|
-
opsPerSec: (ops / totalMs) * 1000,
|
|
216
|
-
avgLatencyMs: allLatencies.reduce((s, v) => s + v, 0) / ops,
|
|
217
|
-
p50Ms: percentile(allLatencies, 0.5),
|
|
218
|
-
p99Ms: percentile(allLatencies, 0.99),
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async function run() {
|
|
223
|
-
console.log('\n=== Proxy Throughput Benchmark ===\n')
|
|
224
|
-
|
|
225
|
-
const pgPort = 25000 + Math.floor(Math.random() * 1000)
|
|
226
|
-
const zeroPort = pgPort + 100
|
|
227
|
-
const dataDir = `.orez-bench-proxy-${Date.now()}`
|
|
228
|
-
|
|
229
|
-
console.log(`starting orez (pg:${pgPort}, skipZero)...`)
|
|
230
|
-
const result = await startZeroLite({
|
|
231
|
-
pgPort,
|
|
232
|
-
zeroPort,
|
|
233
|
-
dataDir,
|
|
234
|
-
logLevel: 'error',
|
|
235
|
-
skipZeroCache: true, // pure proxy benchmark, no zero-cache overhead
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
const db = result.db as PGlite
|
|
239
|
-
const user = 'user'
|
|
240
|
-
const password = 'password'
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
// set up test table
|
|
244
|
-
await db.exec(`
|
|
245
|
-
CREATE TABLE bench_rows (
|
|
246
|
-
id SERIAL PRIMARY KEY,
|
|
247
|
-
value TEXT,
|
|
248
|
-
num INTEGER
|
|
249
|
-
);
|
|
250
|
-
INSERT INTO bench_rows (value, num)
|
|
251
|
-
SELECT 'row-' || i, i FROM generate_series(1, 1000) AS i;
|
|
252
|
-
`)
|
|
253
|
-
|
|
254
|
-
// warmup
|
|
255
|
-
const warmupSocket = await connectPg(pgPort, user, password)
|
|
256
|
-
for (let i = 0; i < 50; i++) {
|
|
257
|
-
await sendQuery(warmupSocket, 'SELECT 1')
|
|
258
|
-
}
|
|
259
|
-
warmupSocket.destroy()
|
|
260
|
-
|
|
261
|
-
const results: BenchResult[] = []
|
|
262
|
-
|
|
263
|
-
// --- serial benchmarks (1 connection) ---
|
|
264
|
-
console.log('running serial benchmarks...')
|
|
265
|
-
const s1 = await connectPg(pgPort, user, password)
|
|
266
|
-
|
|
267
|
-
results.push(await benchSerial(s1, 'SELECT 1', 500, 'serial: SELECT 1 (ping)'))
|
|
268
|
-
results.push(
|
|
269
|
-
await benchSerial(
|
|
270
|
-
s1,
|
|
271
|
-
'SELECT * FROM bench_rows LIMIT 10',
|
|
272
|
-
500,
|
|
273
|
-
'serial: SELECT 10 rows'
|
|
274
|
-
)
|
|
275
|
-
)
|
|
276
|
-
results.push(
|
|
277
|
-
await benchSerial(s1, 'SELECT * FROM bench_rows', 200, 'serial: SELECT 1000 rows')
|
|
278
|
-
)
|
|
279
|
-
results.push(
|
|
280
|
-
await benchSerial(
|
|
281
|
-
s1,
|
|
282
|
-
"INSERT INTO bench_rows (value, num) VALUES ('x', 1)",
|
|
283
|
-
200,
|
|
284
|
-
'serial: INSERT'
|
|
285
|
-
)
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
s1.destroy()
|
|
289
|
-
|
|
290
|
-
// --- concurrent benchmarks (multiple connections, same db) ---
|
|
291
|
-
console.log('running concurrent benchmarks...')
|
|
292
|
-
const concSockets: Socket[] = []
|
|
293
|
-
for (let i = 0; i < 4; i++) {
|
|
294
|
-
concSockets.push(await connectPg(pgPort, user, password))
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
results.push(
|
|
298
|
-
await benchConcurrent(concSockets, 'SELECT 1', 200, 'concurrent 4x: SELECT 1')
|
|
299
|
-
)
|
|
300
|
-
results.push(
|
|
301
|
-
await benchConcurrent(
|
|
302
|
-
concSockets,
|
|
303
|
-
'SELECT * FROM bench_rows LIMIT 10',
|
|
304
|
-
200,
|
|
305
|
-
'concurrent 4x: SELECT 10 rows'
|
|
306
|
-
)
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
for (const s of concSockets) s.destroy()
|
|
310
|
-
|
|
311
|
-
// --- report ---
|
|
312
|
-
console.log('\n=== Results ===\n')
|
|
313
|
-
for (const r of results) {
|
|
314
|
-
console.log(formatResult(r))
|
|
315
|
-
console.log()
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// summary: serial vs concurrent throughput ratio
|
|
319
|
-
const serialPing = results.find((r) => r.name.includes('serial: SELECT 1'))!
|
|
320
|
-
const concPing = results.find((r) => r.name.includes('concurrent 4x: SELECT 1'))!
|
|
321
|
-
const serialReal = results.find((r) => r.name.includes('serial: SELECT 10'))!
|
|
322
|
-
const concReal = results.find((r) => r.name.includes('concurrent 4x: SELECT 10'))!
|
|
323
|
-
const pingRatio = concPing.opsPerSec / serialPing.opsPerSec
|
|
324
|
-
const realRatio = concReal.opsPerSec / serialReal.opsPerSec
|
|
325
|
-
console.log(` === scaling analysis (ideal = 4.0x with 4 connections) ===`)
|
|
326
|
-
console.log(
|
|
327
|
-
` ping (no mutex/pglite): ${pingRatio.toFixed(2)}x ← main thread parallelism`
|
|
328
|
-
)
|
|
329
|
-
console.log(` real queries (mutex): ${realRatio.toFixed(2)}x ← bottleneck here`)
|
|
330
|
-
console.log()
|
|
331
|
-
} finally {
|
|
332
|
-
await result.stop()
|
|
333
|
-
const { rmSync } = await import('node:fs')
|
|
334
|
-
try {
|
|
335
|
-
rmSync(dataDir, { recursive: true, force: true })
|
|
336
|
-
} catch {}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
run().catch((err) => {
|
|
341
|
-
console.error('benchmark failed:', err)
|
|
342
|
-
process.exit(1)
|
|
343
|
-
})
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* benchmark: serial mutations with connected client
|
|
3
|
-
*
|
|
4
|
-
* measures the time for N mutations to be fully replicated
|
|
5
|
-
* to a connected websocket client.
|
|
6
|
-
*
|
|
7
|
-
* run: bun src/bench/serial-mutations.bench.ts
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import WebSocket from 'ws'
|
|
11
|
-
|
|
12
|
-
import { startZeroLite } from '../index.js'
|
|
13
|
-
import {
|
|
14
|
-
ensureTablesInPublications,
|
|
15
|
-
installAllowAllPermissions,
|
|
16
|
-
} from '../integration/test-permissions.js'
|
|
17
|
-
import { installChangeTracking } from '../replication/change-tracker.js'
|
|
18
|
-
|
|
19
|
-
import type { PGlite } from '@electric-sql/pglite'
|
|
20
|
-
|
|
21
|
-
const SYNC_PROTOCOL_VERSION = 49
|
|
22
|
-
const NUM_MUTATIONS = 100
|
|
23
|
-
|
|
24
|
-
// test schema
|
|
25
|
-
const CLIENT_SCHEMA = {
|
|
26
|
-
tables: {
|
|
27
|
-
bench_items: {
|
|
28
|
-
columns: {
|
|
29
|
-
id: { type: 'string' },
|
|
30
|
-
value: { type: 'string' },
|
|
31
|
-
num: { type: 'number' },
|
|
32
|
-
},
|
|
33
|
-
primaryKey: ['id'],
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function encodeSecProtocols(
|
|
39
|
-
initConnectionMessage: unknown,
|
|
40
|
-
authToken: string | undefined
|
|
41
|
-
): string {
|
|
42
|
-
const payload = JSON.stringify({ initConnectionMessage, authToken })
|
|
43
|
-
return encodeURIComponent(Buffer.from(payload, 'utf-8').toString('base64'))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
class Queue<T> {
|
|
47
|
-
private items: T[] = []
|
|
48
|
-
private waiters: Array<{
|
|
49
|
-
resolve: (v: T) => void
|
|
50
|
-
timer?: ReturnType<typeof setTimeout>
|
|
51
|
-
}> = []
|
|
52
|
-
|
|
53
|
-
enqueue(item: T) {
|
|
54
|
-
const waiter = this.waiters.shift()
|
|
55
|
-
if (waiter) {
|
|
56
|
-
if (waiter.timer) clearTimeout(waiter.timer)
|
|
57
|
-
waiter.resolve(item)
|
|
58
|
-
} else {
|
|
59
|
-
this.items.push(item)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
dequeue(fallback?: T, timeoutMs = 10000): Promise<T> {
|
|
64
|
-
if (this.items.length > 0) {
|
|
65
|
-
return Promise.resolve(this.items.shift()!)
|
|
66
|
-
}
|
|
67
|
-
return new Promise<T>((resolve) => {
|
|
68
|
-
const waiter: { resolve: (v: T) => void; timer?: ReturnType<typeof setTimeout> } = {
|
|
69
|
-
resolve,
|
|
70
|
-
}
|
|
71
|
-
if (fallback !== undefined) {
|
|
72
|
-
waiter.timer = setTimeout(() => {
|
|
73
|
-
const idx = this.waiters.indexOf(waiter)
|
|
74
|
-
if (idx >= 0) this.waiters.splice(idx, 1)
|
|
75
|
-
resolve(fallback)
|
|
76
|
-
}, timeoutMs)
|
|
77
|
-
}
|
|
78
|
-
this.waiters.push(waiter)
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function waitForZero(port: number, timeoutMs = 30000) {
|
|
84
|
-
const deadline = Date.now() + timeoutMs
|
|
85
|
-
while (Date.now() < deadline) {
|
|
86
|
-
try {
|
|
87
|
-
const res = await fetch(`http://localhost:${port}/`)
|
|
88
|
-
if (res.ok || res.status === 404) return
|
|
89
|
-
} catch {}
|
|
90
|
-
await new Promise((r) => setTimeout(r, 500))
|
|
91
|
-
}
|
|
92
|
-
throw new Error(`zero-cache not ready on port ${port} after ${timeoutMs}ms`)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function runBenchmark() {
|
|
96
|
-
console.log(`\n=== Serial Mutations Benchmark (${NUM_MUTATIONS} mutations) ===\n`)
|
|
97
|
-
|
|
98
|
-
const testPgPort = 24000 + Math.floor(Math.random() * 1000)
|
|
99
|
-
const testZeroPort = testPgPort + 100
|
|
100
|
-
const dataDir = `.orez-bench-${Date.now()}`
|
|
101
|
-
|
|
102
|
-
console.log(`starting orez on pg:${testPgPort} zero:${testZeroPort}`)
|
|
103
|
-
const result = await startZeroLite({
|
|
104
|
-
pgPort: testPgPort,
|
|
105
|
-
zeroPort: testZeroPort,
|
|
106
|
-
dataDir,
|
|
107
|
-
logLevel: 'info',
|
|
108
|
-
skipZeroCache: false,
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
const db = result.db
|
|
112
|
-
const zeroPort = result.zeroPort
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
// create test table
|
|
116
|
-
await db.exec(`
|
|
117
|
-
CREATE TABLE IF NOT EXISTS bench_items (
|
|
118
|
-
id TEXT PRIMARY KEY,
|
|
119
|
-
value TEXT,
|
|
120
|
-
num INTEGER
|
|
121
|
-
);
|
|
122
|
-
`)
|
|
123
|
-
|
|
124
|
-
// add table to publication and install permissions
|
|
125
|
-
await ensureTablesInPublications(db, ['bench_items'])
|
|
126
|
-
await installChangeTracking(db)
|
|
127
|
-
await installAllowAllPermissions(db, ['bench_items'])
|
|
128
|
-
|
|
129
|
-
if (result.resetZeroFull) {
|
|
130
|
-
await result.resetZeroFull()
|
|
131
|
-
} else if (result.restartZero) {
|
|
132
|
-
await result.restartZero()
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
console.log('waiting for zero-cache...')
|
|
136
|
-
await waitForZero(zeroPort, 90000)
|
|
137
|
-
console.log('zero-cache ready')
|
|
138
|
-
|
|
139
|
-
// connect websocket client
|
|
140
|
-
const downstream = new Queue<unknown>()
|
|
141
|
-
const cg = `bench-cg-${Date.now()}`
|
|
142
|
-
const cid = `bench-client-${Date.now()}`
|
|
143
|
-
const secProtocol = encodeSecProtocols(
|
|
144
|
-
[
|
|
145
|
-
'initConnection',
|
|
146
|
-
{
|
|
147
|
-
desiredQueriesPatch: [
|
|
148
|
-
{
|
|
149
|
-
op: 'put',
|
|
150
|
-
hash: 'q1',
|
|
151
|
-
ast: { table: 'bench_items', orderBy: [['id', 'asc']] },
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
clientSchema: CLIENT_SCHEMA,
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
undefined
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
const ws = new WebSocket(
|
|
161
|
-
`ws://localhost:${zeroPort}/sync/v${SYNC_PROTOCOL_VERSION}/connect` +
|
|
162
|
-
`?clientGroupID=${cg}&clientID=${cid}&wsid=ws1&schemaVersion=1&baseCookie=&ts=${Date.now()}&lmid=0`,
|
|
163
|
-
secProtocol
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
ws.on('message', (data) => {
|
|
167
|
-
downstream.enqueue(JSON.parse(data.toString()))
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
// wait for connection
|
|
171
|
-
await new Promise<void>((resolve, reject) => {
|
|
172
|
-
ws.on('open', resolve)
|
|
173
|
-
ws.on('error', reject)
|
|
174
|
-
setTimeout(() => reject(new Error('ws connect timeout')), 5000)
|
|
175
|
-
})
|
|
176
|
-
console.log('websocket connected')
|
|
177
|
-
|
|
178
|
-
// drain initial pokes
|
|
179
|
-
let settled = false
|
|
180
|
-
const timeout = Date.now() + 30000
|
|
181
|
-
while (!settled && Date.now() < timeout) {
|
|
182
|
-
const msg = (await downstream.dequeue('timeout' as any, 3000)) as any
|
|
183
|
-
if (msg === 'timeout') {
|
|
184
|
-
settled = true
|
|
185
|
-
} else if (Array.isArray(msg) && msg[0] === 'pokeEnd') {
|
|
186
|
-
const next = (await downstream.dequeue('timeout' as any, 2000)) as any
|
|
187
|
-
if (next === 'timeout') {
|
|
188
|
-
settled = true
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
console.log('initial sync complete, starting benchmark...\n')
|
|
193
|
-
|
|
194
|
-
// ========== BENCHMARK: Serial Mutations ==========
|
|
195
|
-
const receivedIds = new Set<string>()
|
|
196
|
-
const startTime = performance.now()
|
|
197
|
-
|
|
198
|
-
// insert mutations serially
|
|
199
|
-
for (let i = 0; i < NUM_MUTATIONS; i++) {
|
|
200
|
-
const id = `bench-${i}`
|
|
201
|
-
await db.query(`INSERT INTO bench_items (id, value, num) VALUES ($1, $2, $3)`, [
|
|
202
|
-
id,
|
|
203
|
-
`value-${i}`,
|
|
204
|
-
i,
|
|
205
|
-
])
|
|
206
|
-
}
|
|
207
|
-
const insertEndTime = performance.now()
|
|
208
|
-
console.log(`inserts completed in ${(insertEndTime - startTime).toFixed(1)}ms`)
|
|
209
|
-
|
|
210
|
-
// wait for all mutations to be replicated
|
|
211
|
-
const replicationTimeout = Date.now() + 60000
|
|
212
|
-
while (receivedIds.size < NUM_MUTATIONS && Date.now() < replicationTimeout) {
|
|
213
|
-
const msg = (await downstream.dequeue('timeout' as any, 1000)) as any
|
|
214
|
-
if (
|
|
215
|
-
msg !== 'timeout' &&
|
|
216
|
-
Array.isArray(msg) &&
|
|
217
|
-
msg[0] === 'pokePart' &&
|
|
218
|
-
msg[1]?.rowsPatch
|
|
219
|
-
) {
|
|
220
|
-
for (const row of msg[1].rowsPatch) {
|
|
221
|
-
if (row.op === 'put' && row.tableName === 'bench_items' && row.value?.id) {
|
|
222
|
-
receivedIds.add(row.value.id)
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
const endTime = performance.now()
|
|
228
|
-
|
|
229
|
-
ws.close()
|
|
230
|
-
|
|
231
|
-
// results
|
|
232
|
-
const totalMs = endTime - startTime
|
|
233
|
-
const insertMs = insertEndTime - startTime
|
|
234
|
-
const replicationMs = endTime - insertEndTime
|
|
235
|
-
const perMutation = totalMs / NUM_MUTATIONS
|
|
236
|
-
|
|
237
|
-
console.log(`\n=== Results ===`)
|
|
238
|
-
console.log(`total time: ${totalMs.toFixed(1)}ms`)
|
|
239
|
-
console.log(
|
|
240
|
-
`insert time: ${insertMs.toFixed(1)}ms (${(insertMs / NUM_MUTATIONS).toFixed(1)}ms/op)`
|
|
241
|
-
)
|
|
242
|
-
console.log(`replication time: ${replicationMs.toFixed(1)}ms`)
|
|
243
|
-
console.log(`per mutation (end-to-end): ${perMutation.toFixed(1)}ms`)
|
|
244
|
-
console.log(`mutations received: ${receivedIds.size}/${NUM_MUTATIONS}`)
|
|
245
|
-
console.log(`throughput: ${(1000 / perMutation).toFixed(1)} mutations/sec`)
|
|
246
|
-
|
|
247
|
-
if (receivedIds.size < NUM_MUTATIONS) {
|
|
248
|
-
console.log(`\nWARNING: not all mutations were replicated!`)
|
|
249
|
-
const missing = []
|
|
250
|
-
for (let i = 0; i < NUM_MUTATIONS; i++) {
|
|
251
|
-
if (!receivedIds.has(`bench-${i}`)) missing.push(i)
|
|
252
|
-
}
|
|
253
|
-
console.log(
|
|
254
|
-
`missing: ${missing.slice(0, 10).join(', ')}${missing.length > 10 ? '...' : ''}`
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
|
-
} finally {
|
|
258
|
-
await result.stop()
|
|
259
|
-
// cleanup
|
|
260
|
-
const { rmSync } = await import('node:fs')
|
|
261
|
-
try {
|
|
262
|
-
rmSync(dataDir, { recursive: true, force: true })
|
|
263
|
-
} catch {}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
runBenchmark().catch((err) => {
|
|
268
|
-
console.error('benchmark failed:', err)
|
|
269
|
-
process.exit(1)
|
|
270
|
-
})
|