orez 0.2.27 → 0.2.30

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