orez 0.0.47 → 0.0.48
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/admin/http-proxy.d.ts.map +1 -1
- package/dist/admin/http-proxy.js.map +1 -1
- package/dist/admin/log-store.d.ts.map +1 -1
- package/dist/admin/log-store.js.map +1 -1
- package/dist/admin/server.d.ts +2 -2
- package/dist/admin/server.d.ts.map +1 -1
- package/dist/admin/server.js.map +1 -1
- package/dist/admin/ui.d.ts.map +1 -1
- package/dist/admin/ui.js +2 -2
- package/dist/admin/ui.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -37
- package/dist/index.js.map +1 -1
- package/dist/pg-proxy.d.ts.map +1 -1
- package/dist/pg-proxy.js.map +1 -1
- package/dist/pglite-manager.d.ts.map +1 -1
- package/dist/pglite-manager.js +7 -1
- package/dist/pglite-manager.js.map +1 -1
- package/dist/replication/change-tracker.d.ts.map +1 -1
- package/dist/replication/change-tracker.js +16 -29
- package/dist/replication/change-tracker.js.map +1 -1
- package/dist/replication/handler.d.ts.map +1 -1
- package/dist/replication/handler.js +23 -6
- package/dist/replication/handler.js.map +1 -1
- package/dist/vite-plugin.js +1 -1
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +2 -2
- package/src/admin/http-proxy.ts +5 -1
- package/src/admin/log-store.ts +4 -1
- package/src/admin/server.ts +7 -3
- package/src/admin/ui.ts +682 -680
- package/src/cli.ts +19 -18
- package/src/index.ts +87 -40
- package/src/pg-proxy.ts +3 -1
- package/src/pglite-manager.ts +8 -1
- package/src/replication/change-tracker.ts +20 -30
- package/src/replication/handler.ts +40 -13
- package/src/replication/pgoutput-encoder.test.ts +217 -0
- package/src/replication/zero-compat.test.ts +232 -1
- package/src/shim/hooks.mjs +33 -0
- package/src/vite-plugin.ts +1 -1
package/src/cli.ts
CHANGED
|
@@ -788,23 +788,24 @@ const main = defineCommand({
|
|
|
788
788
|
},
|
|
789
789
|
async run({ args }) {
|
|
790
790
|
const startTime = Date.now()
|
|
791
|
-
const { config, stop, logStore, zeroEnv, actions, httpLogStore } =
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
791
|
+
const { config, stop, logStore, zeroEnv, actions, httpLogStore } =
|
|
792
|
+
await startZeroLite({
|
|
793
|
+
pgPort: Number(args['pg-port']),
|
|
794
|
+
zeroPort: Number(args['zero-port']),
|
|
795
|
+
dataDir: args['data-dir'],
|
|
796
|
+
migrationsDir: args.migrations,
|
|
797
|
+
seedFile: args.seed,
|
|
798
|
+
pgUser: args['pg-user'],
|
|
799
|
+
pgPassword: args['pg-password'],
|
|
800
|
+
skipZeroCache: args['skip-zero-cache'],
|
|
801
|
+
disableWasmSqlite: args['disable-wasm-sqlite'],
|
|
802
|
+
logLevel: (args['log-level'] as 'error' | 'warn' | 'info' | 'debug') || undefined,
|
|
803
|
+
logEnv: args['log-env'],
|
|
804
|
+
onDbReady: args['on-db-ready'],
|
|
805
|
+
admin: args.admin,
|
|
806
|
+
adminPort: Number(args['admin-port']),
|
|
807
|
+
adminLogs: args['admin-logs'],
|
|
808
|
+
})
|
|
808
809
|
|
|
809
810
|
let s3Server: import('node:http').Server | null = null
|
|
810
811
|
if (args.s3) {
|
|
@@ -818,7 +819,7 @@ const main = defineCommand({
|
|
|
818
819
|
let adminServer: import('node:http').Server | null = null
|
|
819
820
|
if (args.admin && logStore) {
|
|
820
821
|
const { findPort } = await import('./port.js')
|
|
821
|
-
const adminPort = Number(args['admin-port']) ||
|
|
822
|
+
const adminPort = Number(args['admin-port']) || config.zeroPort + 2
|
|
822
823
|
const resolvedPort = await findPort(adminPort)
|
|
823
824
|
const { startAdminServer } = await import('./admin/server.js')
|
|
824
825
|
adminServer = await startAdminServer({
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { spawn, type ChildProcess } from 'node:child_process'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
unlinkSync,
|
|
16
|
+
writeFileSync,
|
|
17
|
+
} from 'node:fs'
|
|
11
18
|
import { createRequire } from 'node:module'
|
|
12
19
|
import { totalmem } from 'node:os'
|
|
13
20
|
import { dirname, resolve } from 'node:path'
|
|
@@ -20,10 +27,10 @@ import { createInstance, createPGliteInstances, runMigrations } from './pglite-m
|
|
|
20
27
|
import { findPort } from './port.js'
|
|
21
28
|
import { installChangeTracking } from './replication/change-tracker.js'
|
|
22
29
|
|
|
30
|
+
import type { HttpLogStore } from './admin/http-proxy.js'
|
|
31
|
+
import type { LogStore } from './admin/log-store.js'
|
|
23
32
|
import type { ZeroLiteConfig } from './config.js'
|
|
24
33
|
import type { PGlite } from '@electric-sql/pglite'
|
|
25
|
-
import type { LogStore } from './admin/log-store.js'
|
|
26
|
-
import type { HttpLogStore } from './admin/http-proxy.js'
|
|
27
34
|
|
|
28
35
|
export { getConfig, getConnectionString } from './config.js'
|
|
29
36
|
export type { LogLevel, ZeroLiteConfig } from './config.js'
|
|
@@ -47,8 +54,12 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
|
|
|
47
54
|
|
|
48
55
|
// when admin ui enabled, create log store and capture all log output
|
|
49
56
|
const SOURCE_MAP: Record<string, string> = {
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
orez: 'orez',
|
|
58
|
+
pglite: 'pglite',
|
|
59
|
+
'pg-proxy': 'proxy',
|
|
60
|
+
zero: 'zero',
|
|
61
|
+
'zero-cache': 'zero',
|
|
62
|
+
'orez/s3': 's3',
|
|
52
63
|
}
|
|
53
64
|
let logStore: LogStore | null = null
|
|
54
65
|
let removeLogListener: (() => void) | null = null
|
|
@@ -180,13 +191,17 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
|
|
|
180
191
|
}
|
|
181
192
|
const elapsed = now - cdbResets.lastReset
|
|
182
193
|
if (elapsed < MIN_RESET_INTERVAL_MS) {
|
|
183
|
-
log.zero(
|
|
194
|
+
log.zero(
|
|
195
|
+
`change db reset too soon (${Math.round(elapsed / 1000)}s ago), not retrying`
|
|
196
|
+
)
|
|
184
197
|
return
|
|
185
198
|
}
|
|
186
199
|
|
|
187
200
|
cdbResets.count++
|
|
188
201
|
cdbResets.lastReset = now
|
|
189
|
-
log.zero(
|
|
202
|
+
log.zero(
|
|
203
|
+
`stale change db detected, resetting (${cdbResets.count}/${MAX_CDB_RESETS})`
|
|
204
|
+
)
|
|
190
205
|
|
|
191
206
|
try {
|
|
192
207
|
await instances.cdb.close()
|
|
@@ -224,36 +239,54 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
|
|
|
224
239
|
|
|
225
240
|
// admin action handlers
|
|
226
241
|
const actions = {
|
|
227
|
-
restartZero: config.skipZeroCache
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
242
|
+
restartZero: config.skipZeroCache
|
|
243
|
+
? undefined
|
|
244
|
+
: async () => {
|
|
245
|
+
if (zeroCacheProcess && !zeroCacheProcess.killed) {
|
|
246
|
+
zeroCacheProcess.kill('SIGTERM')
|
|
247
|
+
await new Promise<void>((r) => {
|
|
248
|
+
const t = setTimeout(() => {
|
|
249
|
+
zeroCacheProcess?.kill('SIGKILL')
|
|
250
|
+
r()
|
|
251
|
+
}, 3000)
|
|
252
|
+
zeroCacheProcess!.on('exit', () => {
|
|
253
|
+
clearTimeout(t)
|
|
254
|
+
r()
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
const zc = await startZeroCache(config, zeroInternalPort)
|
|
259
|
+
zeroCacheProcess = zc.child
|
|
260
|
+
await waitForZeroCache(config, undefined, zeroInternalPort)
|
|
261
|
+
log.zero(`restarted ${port(config.zeroPort, 'magenta')}`)
|
|
262
|
+
},
|
|
263
|
+
resetZero: config.skipZeroCache
|
|
264
|
+
? undefined
|
|
265
|
+
: async () => {
|
|
266
|
+
if (zeroCacheProcess && !zeroCacheProcess.killed) {
|
|
267
|
+
zeroCacheProcess.kill('SIGTERM')
|
|
268
|
+
await new Promise<void>((r) => {
|
|
269
|
+
const t = setTimeout(() => {
|
|
270
|
+
zeroCacheProcess?.kill('SIGKILL')
|
|
271
|
+
r()
|
|
272
|
+
}, 3000)
|
|
273
|
+
zeroCacheProcess!.on('exit', () => {
|
|
274
|
+
clearTimeout(t)
|
|
275
|
+
r()
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
const replicaPath = resolve(config.dataDir, 'zero-replica.db')
|
|
280
|
+
for (const suffix of ['', '-wal', '-shm', '-wal2']) {
|
|
281
|
+
try {
|
|
282
|
+
if (existsSync(replicaPath + suffix)) unlinkSync(replicaPath + suffix)
|
|
283
|
+
} catch {}
|
|
284
|
+
}
|
|
285
|
+
const zc = await startZeroCache(config, zeroInternalPort)
|
|
286
|
+
zeroCacheProcess = zc.child
|
|
287
|
+
await waitForZeroCache(config, undefined, zeroInternalPort)
|
|
288
|
+
log.zero(`reset and restarted ${port(config.zeroPort, 'magenta')}`)
|
|
289
|
+
},
|
|
257
290
|
}
|
|
258
291
|
|
|
259
292
|
const stop = async () => {
|
|
@@ -285,7 +318,18 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
|
|
|
285
318
|
log.debug.orez('stopped')
|
|
286
319
|
}
|
|
287
320
|
|
|
288
|
-
return {
|
|
321
|
+
return {
|
|
322
|
+
config,
|
|
323
|
+
stop,
|
|
324
|
+
db,
|
|
325
|
+
instances,
|
|
326
|
+
pgPort: config.pgPort,
|
|
327
|
+
zeroPort: config.zeroPort,
|
|
328
|
+
logStore,
|
|
329
|
+
zeroEnv,
|
|
330
|
+
actions,
|
|
331
|
+
httpLogStore,
|
|
332
|
+
}
|
|
289
333
|
}
|
|
290
334
|
|
|
291
335
|
function cleanupStaleLockFiles(config: ZeroLiteConfig): void {
|
|
@@ -364,7 +408,10 @@ function writeSqliteShim(): string {
|
|
|
364
408
|
return registerPath
|
|
365
409
|
}
|
|
366
410
|
|
|
367
|
-
async function startZeroCache(
|
|
411
|
+
async function startZeroCache(
|
|
412
|
+
config: ZeroLiteConfig,
|
|
413
|
+
portOverride?: number
|
|
414
|
+
): Promise<{ child: ChildProcess; env: Record<string, string>; stderrBuf: string }> {
|
|
368
415
|
// resolve @rocicorp/zero entry for finding zero-cache modules
|
|
369
416
|
const zeroEntry = resolvePackage('@rocicorp/zero')
|
|
370
417
|
|
|
@@ -533,7 +580,7 @@ async function startZeroCache(config: ZeroLiteConfig, portOverride?: number): Pr
|
|
|
533
580
|
async function waitForZeroCache(
|
|
534
581
|
config: ZeroLiteConfig,
|
|
535
582
|
timeoutMs = 120000,
|
|
536
|
-
portOverride?: number
|
|
583
|
+
portOverride?: number
|
|
537
584
|
): Promise<void> {
|
|
538
585
|
const start = Date.now()
|
|
539
586
|
const url = `http://127.0.0.1:${portOverride || config.zeroPort}/`
|
package/src/pg-proxy.ts
CHANGED
|
@@ -694,7 +694,9 @@ export async function startPgProxy(
|
|
|
694
694
|
// track active connections per database
|
|
695
695
|
activeConns[dbName] = (activeConns[dbName] || 0) + 1
|
|
696
696
|
|
|
697
|
-
console.info(
|
|
697
|
+
console.info(
|
|
698
|
+
`[orez-proxy#${connId}] connect db=${dbName} repl=${params.replication || 'none'}`
|
|
699
|
+
)
|
|
698
700
|
|
|
699
701
|
const { db } = getDbContext(dbName)
|
|
700
702
|
await db.waitReady
|
package/src/pglite-manager.ts
CHANGED
|
@@ -133,8 +133,15 @@ export async function runMigrations(db: PGlite, config: ZeroLiteConfig): Promise
|
|
|
133
133
|
continue
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
const filePath = join(migrationsDir, file)
|
|
137
|
+
if (!existsSync(filePath)) {
|
|
138
|
+
// .ts-only custom migrations are handled by the app's own migration runner
|
|
139
|
+
log.debug.orez(`skipping migration (no .sql file): ${name}`)
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
|
|
136
143
|
log.debug.orez(`applying migration: ${name}`)
|
|
137
|
-
const sql = readFileSync(
|
|
144
|
+
const sql = readFileSync(filePath, 'utf-8')
|
|
138
145
|
|
|
139
146
|
// split by drizzle's statement-breakpoint marker
|
|
140
147
|
const statements = sql
|
|
@@ -67,35 +67,6 @@ export async function installChangeTracking(db: PGlite): Promise<void> {
|
|
|
67
67
|
$$ LANGUAGE plpgsql;
|
|
68
68
|
`)
|
|
69
69
|
|
|
70
|
-
// auto-install change tracking on tables created after startup (e.g. via restore
|
|
71
|
-
// or wire protocol). uses a DDL event trigger that fires on CREATE TABLE.
|
|
72
|
-
await db.exec(`
|
|
73
|
-
CREATE OR REPLACE FUNCTION public._zero_auto_track() RETURNS event_trigger AS $$
|
|
74
|
-
DECLARE
|
|
75
|
-
obj record;
|
|
76
|
-
BEGIN
|
|
77
|
-
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
|
|
78
|
-
WHERE command_tag = 'CREATE TABLE'
|
|
79
|
-
LOOP
|
|
80
|
-
IF obj.schema_name = 'public'
|
|
81
|
-
AND obj.object_identity NOT LIKE '%._zero_%'
|
|
82
|
-
AND obj.object_identity NOT LIKE '%.migrations'
|
|
83
|
-
THEN
|
|
84
|
-
EXECUTE format(
|
|
85
|
-
'CREATE TRIGGER _zero_change_trigger AFTER INSERT OR UPDATE OR DELETE ON %s FOR EACH ROW EXECUTE FUNCTION public._zero_track_change()',
|
|
86
|
-
obj.object_identity
|
|
87
|
-
);
|
|
88
|
-
END IF;
|
|
89
|
-
END LOOP;
|
|
90
|
-
END;
|
|
91
|
-
$$ LANGUAGE plpgsql;
|
|
92
|
-
|
|
93
|
-
DROP EVENT TRIGGER IF EXISTS _zero_auto_track_trigger;
|
|
94
|
-
CREATE EVENT TRIGGER _zero_auto_track_trigger ON ddl_command_end
|
|
95
|
-
WHEN TAG IN ('CREATE TABLE')
|
|
96
|
-
EXECUTE FUNCTION public._zero_auto_track();
|
|
97
|
-
`)
|
|
98
|
-
|
|
99
70
|
// install triggers on all public tables
|
|
100
71
|
await installTriggersOnAllTables(db)
|
|
101
72
|
}
|
|
@@ -239,10 +210,29 @@ export async function installTriggersOnShardTables(db: PGlite): Promise<void> {
|
|
|
239
210
|
|
|
240
211
|
if (result.rows.length === 0) return
|
|
241
212
|
|
|
213
|
+
// only track `clients` — that's the table zero-cache expects in the
|
|
214
|
+
// replication stream (needed for .server promise resolution). other shard
|
|
215
|
+
// tables like `replicas` are zero-cache internal state and streaming them
|
|
216
|
+
// back causes "Unknown table" crashes in zero-cache's change-processor.
|
|
242
217
|
let count = 0
|
|
243
218
|
for (const { nspname } of result.rows) {
|
|
219
|
+
// remove stale triggers from non-clients tables (from previous versions)
|
|
220
|
+
const stale = await db.query<{ event_object_table: string }>(
|
|
221
|
+
`SELECT DISTINCT event_object_table FROM information_schema.triggers
|
|
222
|
+
WHERE trigger_name = '_zero_change_trigger'
|
|
223
|
+
AND event_object_schema = $1
|
|
224
|
+
AND event_object_table != 'clients'`,
|
|
225
|
+
[nspname]
|
|
226
|
+
)
|
|
227
|
+
for (const { event_object_table } of stale.rows) {
|
|
228
|
+
const qs = quoteIdent(nspname)
|
|
229
|
+
const qt = quoteIdent(event_object_table)
|
|
230
|
+
await db.exec(`DROP TRIGGER IF EXISTS _zero_change_trigger ON ${qs}.${qt}`)
|
|
231
|
+
log.debug.pglite(`removed stale shard trigger from ${nspname}.${event_object_table}`)
|
|
232
|
+
}
|
|
233
|
+
|
|
244
234
|
const tables = await db.query<{ tablename: string }>(
|
|
245
|
-
`SELECT tablename FROM pg_tables WHERE schemaname = $1`,
|
|
235
|
+
`SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = 'clients'`,
|
|
246
236
|
[nspname]
|
|
247
237
|
)
|
|
248
238
|
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
getCurrentWatermark,
|
|
13
13
|
purgeConsumedChanges,
|
|
14
14
|
installTriggersOnShardTables,
|
|
15
|
-
ensureChangeTrackingOnAllTables,
|
|
16
15
|
type ChangeRecord,
|
|
17
16
|
} from './change-tracker.js'
|
|
18
17
|
import {
|
|
@@ -268,7 +267,9 @@ export async function handleStartReplication(
|
|
|
268
267
|
): Promise<void> {
|
|
269
268
|
activeHandlerCount++
|
|
270
269
|
const handlerId = activeHandlerCount
|
|
271
|
-
console.info(
|
|
270
|
+
console.info(
|
|
271
|
+
`[orez-repl#${handlerId}] START_REPLICATION (active handlers: ${activeHandlerCount})`
|
|
272
|
+
)
|
|
272
273
|
log.debug.proxy('replication: entering streaming mode')
|
|
273
274
|
|
|
274
275
|
// send CopyBothResponse to enter streaming mode
|
|
@@ -292,9 +293,6 @@ export async function handleStartReplication(
|
|
|
292
293
|
// "already in transaction" errors when they interleave.
|
|
293
294
|
await mutex.acquire()
|
|
294
295
|
try {
|
|
295
|
-
// install change tracking triggers on any tables created after startup
|
|
296
|
-
await ensureChangeTrackingOnAllTables(db)
|
|
297
|
-
|
|
298
296
|
// install change tracking triggers on shard schema tables (e.g. chat_0.clients)
|
|
299
297
|
// these track zero-cache's lastMutationID for .server promise resolution
|
|
300
298
|
await installTriggersOnShardTables(db)
|
|
@@ -353,7 +351,7 @@ export async function handleStartReplication(
|
|
|
353
351
|
for (const schema of relevantSchemas) {
|
|
354
352
|
if (schema === 'public') continue
|
|
355
353
|
const shardTables = await db.query<{ tablename: string }>(
|
|
356
|
-
`SELECT tablename FROM pg_tables WHERE schemaname = $1`,
|
|
354
|
+
`SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = 'clients'`,
|
|
357
355
|
[schema]
|
|
358
356
|
)
|
|
359
357
|
for (const { tablename } of shardTables.rows) {
|
|
@@ -463,7 +461,9 @@ export async function handleStartReplication(
|
|
|
463
461
|
mutex.release()
|
|
464
462
|
}
|
|
465
463
|
|
|
466
|
-
console.info(
|
|
464
|
+
console.info(
|
|
465
|
+
`[orez-repl#${handlerId}] setup complete, starting poll (lastWatermark=${lastWatermark})`
|
|
466
|
+
)
|
|
467
467
|
|
|
468
468
|
// track which tables we've sent RELATION messages for
|
|
469
469
|
const sentRelations = new Set<string>()
|
|
@@ -492,8 +492,29 @@ export async function handleStartReplication(
|
|
|
492
492
|
}
|
|
493
493
|
|
|
494
494
|
if (changes.length > 0) {
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
// filter out shard tables that zero-cache doesn't expect.
|
|
496
|
+
// only `clients` is needed (for .server promise resolution).
|
|
497
|
+
// other shard tables (replicas, mutations) crash zero-cache
|
|
498
|
+
// with "Unknown table" in change-processor.
|
|
499
|
+
const batchEnd = changes[changes.length - 1].watermark
|
|
500
|
+
changes = changes.filter((c) => {
|
|
501
|
+
const dot = c.table_name.indexOf('.')
|
|
502
|
+
if (dot === -1) return true
|
|
503
|
+
const schema = c.table_name.substring(0, dot)
|
|
504
|
+
if (schema === 'public') return true
|
|
505
|
+
const table = c.table_name.substring(dot + 1)
|
|
506
|
+
return table === 'clients'
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
if (changes.length === 0) {
|
|
510
|
+
lastWatermark = batchEnd
|
|
511
|
+
continue
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const tables = [...new Set(changes.map((c) => c.table_name))].join(',')
|
|
515
|
+
console.info(
|
|
516
|
+
`[orez-repl#${handlerId}] found ${changes.length} changes [${tables}] (wm ${lastWatermark}→${changes[changes.length - 1].watermark}, type=${typeof changes[0].watermark})`
|
|
517
|
+
)
|
|
497
518
|
await streamChanges(
|
|
498
519
|
changes,
|
|
499
520
|
writer,
|
|
@@ -503,7 +524,7 @@ export async function handleStartReplication(
|
|
|
503
524
|
excludedColumns,
|
|
504
525
|
columnTypeOids
|
|
505
526
|
)
|
|
506
|
-
lastWatermark =
|
|
527
|
+
lastWatermark = batchEnd
|
|
507
528
|
|
|
508
529
|
// purge consumed changes periodically to free wasm memory
|
|
509
530
|
pollsSincePurge++
|
|
@@ -513,7 +534,9 @@ export async function handleStartReplication(
|
|
|
513
534
|
try {
|
|
514
535
|
const purged = await purgeConsumedChanges(db, lastWatermark)
|
|
515
536
|
if (purged > 0) {
|
|
516
|
-
console.info(
|
|
537
|
+
console.info(
|
|
538
|
+
`[orez-repl#${handlerId}] purged ${purged} changes (wm<=${lastWatermark})`
|
|
539
|
+
)
|
|
517
540
|
}
|
|
518
541
|
} finally {
|
|
519
542
|
mutex.release()
|
|
@@ -524,7 +547,9 @@ export async function handleStartReplication(
|
|
|
524
547
|
const now = Date.now()
|
|
525
548
|
if (now - lastIdleLog > 10000) {
|
|
526
549
|
lastIdleLog = now
|
|
527
|
-
console.info(
|
|
550
|
+
console.info(
|
|
551
|
+
`[orez-repl#${handlerId}] idle (lastWatermark=${lastWatermark}, type=${typeof lastWatermark})`
|
|
552
|
+
)
|
|
528
553
|
}
|
|
529
554
|
}
|
|
530
555
|
|
|
@@ -550,7 +575,9 @@ export async function handleStartReplication(
|
|
|
550
575
|
log.debug.proxy('replication: starting poll loop')
|
|
551
576
|
await poll()
|
|
552
577
|
activeHandlerCount--
|
|
553
|
-
console.info(
|
|
578
|
+
console.info(
|
|
579
|
+
`[orez-repl#${handlerId}] poll loop exited (remaining handlers: ${activeHandlerCount})`
|
|
580
|
+
)
|
|
554
581
|
}
|
|
555
582
|
|
|
556
583
|
async function streamChanges(
|