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.
Files changed (172) hide show
  1. package/dist/cf-do/worker.d.ts.map +1 -1
  2. package/dist/cf-do/worker.js +9 -1
  3. package/dist/cf-do/worker.js.map +1 -1
  4. package/dist/pg-proxy-do-backend.d.ts +2 -0
  5. package/dist/pg-proxy-do-backend.d.ts.map +1 -1
  6. package/dist/pg-proxy-do-backend.js +49 -7
  7. package/dist/pg-proxy-do-backend.js.map +1 -1
  8. package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
  9. package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
  10. package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
  11. package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
  12. package/dist/pg-sqlite-compiler/index.d.ts +12 -0
  13. package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
  14. package/dist/pg-sqlite-compiler/index.js +59 -0
  15. package/dist/pg-sqlite-compiler/index.js.map +1 -0
  16. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
  17. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
  18. package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
  19. package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
  20. package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
  21. package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
  22. package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
  23. package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
  24. package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
  25. package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
  26. package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
  27. package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
  28. package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
  29. package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
  30. package/dist/pg-sqlite-compiler/passes/index.js +39 -0
  31. package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
  32. package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
  33. package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
  34. package/dist/pg-sqlite-compiler/passes/types.js +103 -0
  35. package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
  36. package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
  37. package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
  38. package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
  39. package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
  40. package/dist/pg-sqlite-compiler/types.d.ts +55 -0
  41. package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
  42. package/dist/pg-sqlite-compiler/types.js +2 -0
  43. package/dist/pg-sqlite-compiler/types.js.map +1 -0
  44. package/package.json +8 -4
  45. package/src/admin/admin-data.test.ts +0 -348
  46. package/src/admin/http-proxy.ts +0 -252
  47. package/src/admin/log-store.ts +0 -192
  48. package/src/admin/server.ts +0 -471
  49. package/src/admin/ui.ts +0 -1322
  50. package/src/bench/proxy-throughput.bench.ts +0 -343
  51. package/src/bench/serial-mutations.bench.ts +0 -270
  52. package/src/browser.ts +0 -203
  53. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  54. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  55. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  56. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  57. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
  58. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
  59. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
  60. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
  61. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
  62. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
  63. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
  64. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
  65. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
  66. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
  67. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
  68. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
  69. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
  70. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
  71. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
  72. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  73. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  74. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  75. package/src/cf-do/ARCHITECTURE.md +0 -83
  76. package/src/cf-do/watermark.test.ts +0 -103
  77. package/src/cf-do/watermark.ts +0 -118
  78. package/src/cf-do/worker.ts +0 -1033
  79. package/src/cf-do/wrangler.toml +0 -11
  80. package/src/cf-pglite/README.md +0 -19
  81. package/src/change-tracking.ts +0 -25
  82. package/src/child-process.test.ts +0 -147
  83. package/src/child-process.ts +0 -90
  84. package/src/cli-entry.ts +0 -72
  85. package/src/cli.test.ts +0 -38
  86. package/src/cli.ts +0 -1214
  87. package/src/config.ts +0 -150
  88. package/src/do-sql-tracking.test.ts +0 -19
  89. package/src/do-sql-tracking.ts +0 -19
  90. package/src/index.ts +0 -1215
  91. package/src/integration/integration.test.ts +0 -517
  92. package/src/integration/native-binary.guard.test.ts +0 -13
  93. package/src/integration/native-startup.test.ts +0 -44
  94. package/src/integration/replication-latency.test.ts +0 -428
  95. package/src/integration/restore-live-stress.test.ts +0 -433
  96. package/src/integration/restore-reset.test.ts +0 -400
  97. package/src/integration/restore.test.ts +0 -274
  98. package/src/integration/test-permissions.ts +0 -147
  99. package/src/load-config.ts +0 -46
  100. package/src/log.ts +0 -96
  101. package/src/mutex.ts +0 -47
  102. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  103. package/src/pg-proxy-browser.ts +0 -2022
  104. package/src/pg-proxy-do-backend.test.ts +0 -3890
  105. package/src/pg-proxy-do-backend.ts +0 -7157
  106. package/src/pg-proxy.ts +0 -1087
  107. package/src/pglite-ipc.test.ts +0 -116
  108. package/src/pglite-ipc.ts +0 -266
  109. package/src/pglite-manager.ts +0 -557
  110. package/src/pglite-web-proxy.test.ts +0 -57
  111. package/src/pglite-web-proxy.ts +0 -221
  112. package/src/pglite-web-worker.ts +0 -152
  113. package/src/pglite-worker-thread.ts +0 -253
  114. package/src/port.ts +0 -25
  115. package/src/process-title.ts +0 -9
  116. package/src/recovery.ts +0 -155
  117. package/src/replication/change-tracker.test.ts +0 -357
  118. package/src/replication/change-tracker.ts +0 -279
  119. package/src/replication/handler.test.ts +0 -511
  120. package/src/replication/handler.ts +0 -1190
  121. package/src/replication/pgoutput-encoder.test.ts +0 -697
  122. package/src/replication/pgoutput-encoder.ts +0 -373
  123. package/src/replication/tcp-replication.test.ts +0 -876
  124. package/src/replication/zero-compat.test.ts +0 -1150
  125. package/src/restore-stress.test.ts +0 -188
  126. package/src/s3-local.ts +0 -203
  127. package/src/shim/hooks.mjs +0 -120
  128. package/src/shim/register.mjs +0 -4
  129. package/src/sqlite-mode/apply-mode.ts +0 -224
  130. package/src/sqlite-mode/index.ts +0 -15
  131. package/src/sqlite-mode/native-binary.ts +0 -89
  132. package/src/sqlite-mode/package-resolve.ts +0 -17
  133. package/src/sqlite-mode/resolve-mode.ts +0 -80
  134. package/src/sqlite-mode/shim-template.ts +0 -159
  135. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  136. package/src/sqlite-mode/types.ts +0 -30
  137. package/src/vite-plugin.ts +0 -67
  138. package/src/wasm-sqlite.test.ts +0 -537
  139. package/src/worker/browser-admin.ts +0 -52
  140. package/src/worker/browser-build-config.test.ts +0 -71
  141. package/src/worker/browser-build-config.ts +0 -109
  142. package/src/worker/browser-embed-admin.test.ts +0 -75
  143. package/src/worker/browser-embed.ts +0 -345
  144. package/src/worker/cf-patches.ts +0 -384
  145. package/src/worker/embed-integration.test.ts +0 -321
  146. package/src/worker/index.ts +0 -138
  147. package/src/worker/shims/fastify.test.ts +0 -255
  148. package/src/worker/shims/fastify.ts +0 -306
  149. package/src/worker/shims/http-service.test.ts +0 -355
  150. package/src/worker/shims/http-service.ts +0 -293
  151. package/src/worker/shims/node-stub.ts +0 -290
  152. package/src/worker/shims/oxfmt.ts +0 -3
  153. package/src/worker/shims/postgres-browser.ts +0 -59
  154. package/src/worker/shims/postgres-socket.test.ts +0 -576
  155. package/src/worker/shims/postgres-socket.ts +0 -310
  156. package/src/worker/shims/postgres.test.ts +0 -364
  157. package/src/worker/shims/postgres.ts +0 -1454
  158. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  159. package/src/worker/shims/sqlite-browser.ts +0 -175
  160. package/src/worker/shims/sqlite.test.ts +0 -786
  161. package/src/worker/shims/sqlite.ts +0 -978
  162. package/src/worker/shims/stream-browser.ts +0 -15
  163. package/src/worker/shims/ws-browser.test.ts +0 -205
  164. package/src/worker/shims/ws-browser.ts +0 -248
  165. package/src/worker/shims/ws.test.ts +0 -288
  166. package/src/worker/shims/ws.ts +0 -467
  167. package/src/worker/shims/zero-process-env.ts +0 -11
  168. package/src/worker/types.ts +0 -75
  169. package/src/worker/worker-integration.test.ts +0 -223
  170. package/src/worker/worker.test.ts +0 -136
  171. package/src/worker/zero-cache-embed-cf.ts +0 -463
  172. 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
- })