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