@rocicorp/zero 1.11.0-canary.30 → 1.11.0-canary.32

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 (89) hide show
  1. package/out/zero/package.js +1 -1
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  4. package/out/zero-cache/src/server/change-streamer.js +2 -1
  5. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  6. package/out/zero-cache/src/server/slot-keeper.d.ts +26 -0
  7. package/out/zero-cache/src/server/slot-keeper.d.ts.map +1 -0
  8. package/out/zero-cache/src/server/slot-keeper.js +175 -0
  9. package/out/zero-cache/src/server/slot-keeper.js.map +1 -0
  10. package/out/zero-cache/src/server/worker-urls.d.ts +1 -0
  11. package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
  12. package/out/zero-cache/src/server/worker-urls.js +2 -1
  13. package/out/zero-cache/src/server/worker-urls.js.map +1 -1
  14. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts +15 -5
  15. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -1
  16. package/out/zero-cache/src/services/change-source/pg/change-source-init.js +21 -13
  17. package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
  18. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  19. package/out/zero-cache/src/services/change-source/pg/change-source.js +2 -1
  20. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  21. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +7 -1
  22. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  23. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +7 -4
  24. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  25. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +35 -1
  26. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  27. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +106 -22
  28. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  29. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +1 -1
  30. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  31. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +31 -9
  32. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  33. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  34. package/out/zero-cache/src/services/change-source/pg/schema/init.js +15 -1
  35. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  36. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +20 -2
  37. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +17 -2
  39. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  40. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts +3 -2
  41. package/out/zero-cache/src/services/view-syncer/connection-context-manager.d.ts.map +1 -1
  42. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js +1 -1
  43. package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
  44. package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
  45. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  46. package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
  47. package/out/zero-cache/src/types/pg.js +1 -1
  48. package/out/zero-cache/src/types/processes.d.ts +3 -0
  49. package/out/zero-cache/src/types/processes.d.ts.map +1 -1
  50. package/out/zero-cache/src/types/processes.js.map +1 -1
  51. package/out/zero-cache/src/workers/connect-params.d.ts +6 -15
  52. package/out/zero-cache/src/workers/connect-params.d.ts.map +1 -1
  53. package/out/zero-cache/src/workers/connect-params.js +14 -15
  54. package/out/zero-cache/src/workers/connect-params.js.map +1 -1
  55. package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
  56. package/out/zero-cache/src/workers/connection.js +1 -1
  57. package/out/zero-cache/src/workers/connection.js.map +1 -1
  58. package/out/zero-client/src/client/version.js +1 -1
  59. package/out/zero-client/src/client/zero.js +2 -2
  60. package/out/zero-client/src/client/zero.js.map +1 -1
  61. package/out/zero-protocol/src/analyze-query-result.d.ts +1 -1
  62. package/out/zero-protocol/src/analyze-query-result.js.map +1 -1
  63. package/out/zero-protocol/src/connect.d.ts +9 -12
  64. package/out/zero-protocol/src/connect.d.ts.map +1 -1
  65. package/out/zero-protocol/src/connect.js +19 -16
  66. package/out/zero-protocol/src/connect.js.map +1 -1
  67. package/out/zero-protocol/src/poke.d.ts +3 -3
  68. package/out/zero-protocol/src/poke.js +1 -1
  69. package/out/zero-protocol/src/poke.js.map +1 -1
  70. package/out/zero-protocol/src/{feature-flag-enum.d.ts → protocol-flag-enum.d.ts} +1 -1
  71. package/out/zero-protocol/src/protocol-flag-enum.d.ts.map +1 -0
  72. package/out/zero-protocol/src/protocol-flag-enum.js +10 -0
  73. package/out/zero-protocol/src/protocol-flag-enum.js.map +1 -0
  74. package/out/zero-protocol/src/protocol-flag.d.ts +14 -0
  75. package/out/zero-protocol/src/protocol-flag.d.ts.map +1 -0
  76. package/out/zero-protocol/src/protocol-flag.js +8 -0
  77. package/out/zero-protocol/src/protocol-flag.js.map +1 -0
  78. package/out/zero-protocol/src/protocol-version.d.ts +3 -3
  79. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  80. package/package.json +1 -1
  81. package/out/shared/src/packed-boolean-map.d.ts +0 -7
  82. package/out/shared/src/packed-boolean-map.d.ts.map +0 -1
  83. package/out/shared/src/packed-boolean-map.js +0 -36
  84. package/out/shared/src/packed-boolean-map.js.map +0 -1
  85. package/out/zero-protocol/src/feature-flag-enum.d.ts.map +0 -1
  86. package/out/zero-protocol/src/feature-flag-enum.js +0 -10
  87. package/out/zero-protocol/src/feature-flag-enum.js.map +0 -1
  88. package/out/zero-protocol/src/feature-flag.d.ts +0 -5
  89. package/out/zero-protocol/src/feature-flag.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"change-source.js","names":["#lc","#db","#upstreamUri","#shard","#replica","#slotCleanupMonitor","#backupOptions","#context","#lagReporter","#textCopy","#streamInboundTimeoutMs","#minStartWatermark","#subscribe","#streamBackfill","#stopped","#takeoverReplicationSlot","#checkConfirmedFlushLSN","#logCurrentReplicaInfo","#acks","#waitingForDownstreamAck","#expectDownstreamAck","#ackIfDownstreamIsCaughtUp","#sendAck","#reportIntervalMs","#reportIntervalJitterMs","#lagReportRetries","#pgVersion","#expectingLagReport","#scheduleMissingReportRetry","#processLagReport","#timer","#lastAcceptedSendTimeMs","#scheduleNextReportWithJitter","#shardPrefix","#shardConfig","#skippedIndexWarnings","#error","#logError","#makeChanges","#invalidateReplica","#replicaInvalidated","#handleDdlMessage","#lastReplicationEvent","#handleRelation","#replicaIdentityTimer","#makeSchemaChanges","#getTableChanges"],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/change-source.ts"],"sourcesContent":["import {\n PG_ADMIN_SHUTDOWN,\n PG_INSUFFICIENT_PRIVILEGE,\n} from '@drdgvhbh/postgres-error-codes';\nimport type {LogContext} from '@rocicorp/logger';\nimport {nanoid} from 'nanoid';\nimport postgres from 'postgres';\nimport {AbortError} from '../../../../../shared/src/abort-error.ts';\nimport {assert} from '../../../../../shared/src/asserts.ts';\nimport {stringify} from '../../../../../shared/src/bigint-json.ts';\nimport {deepEqual} from '../../../../../shared/src/json.ts';\nimport {must} from '../../../../../shared/src/must.ts';\nimport {mapValues} from '../../../../../shared/src/objects.ts';\nimport {\n equals,\n intersection,\n symmetricDifferences,\n} from '../../../../../shared/src/set-utils.ts';\nimport * as v from '../../../../../shared/src/valita.ts';\nimport {mapIndexPredicateColumns} from '../../../db/index-predicate.ts';\nimport {\n mapPostgresToLiteColumn,\n UnsupportedColumnDefaultError,\n} from '../../../db/pg-to-lite.ts';\nimport type {\n ColumnSpec,\n PublishedIndexSpec,\n PublishedTableSpec,\n} from '../../../db/specs.ts';\nimport {getOrCreateCounter} from '../../../observability/metrics.ts';\nimport {type LexiVersion} from '../../../types/lexi-version.ts';\nimport {PG_17} from '../../../types/pg-versions.ts';\nimport {isPostgresError, pgClient, type PostgresDB} from '../../../types/pg.ts';\nimport {upstreamSchema, type ShardID} from '../../../types/shards.ts';\nimport {\n majorVersionFromString,\n majorVersionToString,\n} from '../../../types/state-version.ts';\nimport type {Sink} from '../../../types/streams.ts';\nimport {UnrecoverableError} from '../../running-state.ts';\nimport type {ChangeSource, ChangeStream} from '../change-source.ts';\nimport {BackfillManager} from '../common/backfill-manager.ts';\nimport {\n ChangeStreamMultiplexer,\n type Listener,\n} from '../common/change-stream-multiplexer.ts';\nimport type {\n BackfillRequest,\n DownstreamStatusMessage,\n JSONObject,\n} from '../protocol/current.ts';\nimport type {\n ColumnAdd,\n Identifier,\n MessageRelation,\n SchemaChange,\n TableCreate,\n} from '../protocol/current/data.ts';\nimport type {\n ChangeStreamData,\n ChangeStreamMessage,\n Data,\n} from '../protocol/current/downstream.ts';\nimport type {ColumnMetadata, TableMetadata} from './backfill-metadata.ts';\nimport {streamBackfill} from './backfill-stream.ts';\nimport {type ServerContext} from './initial-sync.ts';\nimport type {\n Message,\n MessageMessage,\n MessageRelation as PostgresRelation,\n} from './logical-replication/pgoutput.types.ts';\nimport {subscribe, type StreamMessage} from './logical-replication/stream.ts';\nimport {fromBigInt, toBigInt, toStateVersionString, type LSN} from './lsn.ts';\nimport {ReplicationSlotCleanupMonitor} from './replication-slot-cleanup-monitor.ts';\nimport {registerReplicationSlotHealthMetrics} from './replication-slot-health.ts';\nimport {replicationEventSchema, type ReplicationEvent} from './schema/ddl.ts';\nimport {\n getPublicationInfo,\n warnForSkippedIndexes,\n type PublishedSchema,\n type PublishedTableWithReplicaIdentity,\n} from './schema/published.ts';\nimport {Replicate} from './schema/replica-stage-enum.ts';\nimport {\n getInternalShardConfig,\n getReplicaAtVersion,\n replicaIdentitiesForTablesWithoutPrimaryKeys,\n replicationSlotPrefix,\n type BackupOptions,\n type InternalShardConfig,\n type Replica,\n} from './schema/shard.ts';\nimport {validate} from './schema/validation.ts';\n\n// Parameterize this if necessary. In practice starvation may never happen.\nconst MAX_LOW_PRIORITY_DELAY_MS = 1000;\n\ntype ReservationState = {\n lastWatermark?: string;\n};\n\n/**\n * Postgres implementation of a {@link ChangeSource} backed by a logical\n * replication stream.\n */\nexport class PostgresChangeSource implements ChangeSource {\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #upstreamUri: string;\n readonly #shard: ShardID;\n readonly #replica: Replica;\n readonly #slotCleanupMonitor: ReplicationSlotCleanupMonitor;\n readonly #backupOptions: BackupOptions;\n readonly #context: ServerContext;\n readonly #lagReporter: LagReporter | null;\n readonly #textCopy: boolean;\n readonly #streamInboundTimeoutMs: number | undefined;\n readonly #minStartWatermark: string | undefined;\n readonly #subscribe: typeof subscribe;\n readonly #streamBackfill: typeof streamBackfill;\n #stopped = false;\n\n constructor(\n lc: LogContext,\n upstreamUri: string,\n shard: ShardID,\n replica: Replica,\n pgVersion: number,\n backupOptions: BackupOptions,\n context: ServerContext,\n lagReportIntervalMs: number,\n textCopy?: boolean | undefined,\n streamInboundTimeoutMs?: number | undefined,\n /**\n * The watermark below which a stream cannot be started without skipping\n * transactions, as Postgres would silently start from the slot's position\n * instead (see `minStartWatermark()` in change-source-init.ts).\n */\n minStartWatermark?: string | undefined,\n // Injectable dependencies, overridable in tests. Default to the production\n // implementations.\n deps: {\n subscribe?: typeof subscribe;\n streamBackfill?: typeof streamBackfill;\n } = {},\n ) {\n this.#lc = lc.withContext('component', 'change-source');\n this.#subscribe = deps.subscribe ?? subscribe;\n this.#streamBackfill = deps.streamBackfill ?? streamBackfill;\n // used for schema changes, lag reporting, and slot cleanup\n this.#db = pgClient(lc, upstreamUri, 'replication-monitor', {\n max: 3,\n idle_timeout: 60,\n });\n this.#upstreamUri = upstreamUri;\n this.#shard = shard;\n this.#replica = replica;\n this.#slotCleanupMonitor = new ReplicationSlotCleanupMonitor(\n lc.withContext('component', 'replication-slot-monitor'),\n shard,\n this.#db,\n pgVersion,\n replica.slot,\n );\n this.#backupOptions = backupOptions;\n this.#context = context;\n this.#textCopy = textCopy ?? false;\n this.#streamInboundTimeoutMs = streamInboundTimeoutMs;\n this.#minStartWatermark = minStartWatermark;\n this.#lagReporter =\n lagReportIntervalMs > 0\n ? new LagReporter(\n lc.withContext('component', 'lag-reporter'),\n shard,\n this.#db,\n pgVersion,\n lagReportIntervalMs,\n )\n : null;\n registerReplicationSlotHealthMetrics(\n this.#lc,\n this.#db,\n replica.slot,\n () => this.#stopped,\n );\n }\n\n async stop(): Promise<void> {\n this.#stopped = true;\n this.#lagReporter?.stop();\n await this.#db.end();\n }\n\n async startLagReporter() {\n if (this.#lagReporter) {\n try {\n return await this.#lagReporter.initiateLagReport(true);\n } catch (e) {\n if (isPostgresError(e, PG_INSUFFICIENT_PRIVILEGE)) {\n const functionName =\n (this.#lagReporter.pgVersion ?? 0) >= PG_17\n ? 'pg_logical_emit_message(boolean, text, text, boolean)'\n : 'pg_logical_emit_message(boolean, text, text)';\n this.#lc.warn?.(\n `\\n\\nUnable to initiate replication lag reports due to insufficient privileges.` +\n `\\nTo enable replication lag reporting, run:`,\n `\\n\\tGRANT EXECUTE ON FUNCTION ${functionName} TO <your_db_user>;\\n\\n`,\n e,\n );\n } else {\n this.#lc.error?.(\n `Unexpected error while initiating lag reports. Lag reports will be disabled.`,\n e,\n );\n }\n }\n }\n return null;\n }\n\n async startStream(\n clientWatermark: string,\n backfillRequests: BackfillRequest[] = [],\n ): Promise<ChangeStream> {\n await this.#takeoverReplicationSlot();\n const config = await getInternalShardConfig(this.#db, this.#shard);\n const {slot} = this.#replica;\n this.#lc.info?.(`starting replication stream@${slot}`);\n await this.#checkConfirmedFlushLSN(slot, clientWatermark);\n const changeStream = await this.startStreamInternal(\n slot,\n clientWatermark,\n config,\n backfillRequests,\n );\n const {signal} = changeStream.changes;\n if (!signal.aborted) {\n // The slot cleanup monitor runs when a replication slot is active.\n // If no slots are active, they are all available for claiming by a\n // replication-manager; inactive slots are only cleaned up when at\n // least one stream is being processed.\n this.#slotCleanupMonitor.start();\n signal.addEventListener('abort', () => this.#slotCleanupMonitor.stop());\n }\n return changeStream;\n }\n\n // Exported for testing.\n async startStreamInternal(\n slot: string,\n clientWatermark: string,\n shardConfig: InternalShardConfig,\n backfillRequests: BackfillRequest[],\n ): Promise<ChangeStream> {\n if (\n this.#minStartWatermark !== undefined &&\n majorVersionFromString(clientWatermark) <\n majorVersionFromString(this.#minStartWatermark)\n ) {\n // Retrying would resume from the same watermark, so fail the service\n // (rather than backing off). Nothing has been acked, so the slot is\n // left intact.\n throw new UnrecoverableError(\n `cannot start replication stream@${slot} from ${clientWatermark}, ` +\n `which is before its minimum start watermark ` +\n `${this.#minStartWatermark}: transactions in between would be ` +\n `silently skipped`,\n );\n }\n const clientStart = majorVersionFromString(clientWatermark) + 1n;\n const {messages, acks} = await this.#subscribe(\n this.#lc,\n this.#db,\n slot,\n [...shardConfig.publications],\n clientStart,\n undefined,\n undefined,\n this.#streamInboundTimeoutMs,\n );\n const acker = new Acker(acks, clientWatermark);\n\n // The ChangeStreamMultiplexer facilitates cooperative streaming from\n // the main replication stream and backfill streams initiated by the\n // BackfillManager.\n const changes = new ChangeStreamMultiplexer(this.#lc, clientWatermark);\n const backfillManager = new BackfillManager(this.#lc, changes, req =>\n this.#streamBackfill(this.#lc, this.#upstreamUri, this.#replica, req, {\n textCopy: this.#textCopy,\n }),\n );\n changes\n .addProducers(messages, backfillManager)\n .addListeners(backfillManager, acker);\n backfillManager.run(clientWatermark, backfillRequests);\n\n const changeMaker = new ChangeMaker(\n this.#shard,\n shardConfig,\n this.#db,\n this.#replica,\n );\n\n /**\n * Determines if the incoming message is transactional, otherwise handling\n * non-transactional messages with a downstream status message.\n */\n const isTransactionalMessage = (\n lsn: bigint,\n msg: StreamMessage[1],\n ): msg is Message => {\n if (\n msg.tag === 'message' &&\n msg.prefix === this.#lagReporter?.messagePrefix\n ) {\n changes.pushStatus(this.#lagReporter.processLagReportMessage(msg, lsn));\n return false;\n }\n // Checks if we are passed the LSN of the expected lag report, in which\n // case a new one is initiated.\n const status = this.#lagReporter?.checkCurrentLSN(lsn);\n if (status) {\n changes.pushStatus(status);\n }\n\n if (msg.tag === 'keepalive') {\n changes.pushStatus([\n 'status',\n {ack: msg.shouldRespond},\n {watermark: majorVersionToString(lsn)},\n ]);\n return false;\n }\n\n // The only non-transaction \"message\" we process is our own lagReporter's\n // message (handled above). Non-transactional messages from other shards\n // (e.g. lag reports), should be ignored but properly classified as\n // non-transactional, so as not to incorrectly request a stream\n // reservation.\n if (msg.tag === 'message' && !msg.transactional) {\n this.#lc.debug?.(\n 'ignoring non-transactional message for different shard',\n msg.prefix,\n );\n return false;\n }\n\n return true;\n };\n\n void (async () => {\n try {\n let reservation: ReservationState | null = null;\n let inTransaction = false;\n\n for await (const [lsn, msg] of messages) {\n if (!isTransactionalMessage(lsn, msg)) {\n // If we're not in a transaction but the last reservation was kept\n // because of pending keepalives or lag reports in the queue,\n // release the reservation.\n if (!inTransaction && reservation?.lastWatermark) {\n changes.release(reservation.lastWatermark);\n reservation = null;\n }\n continue;\n }\n\n if (!reservation) {\n const res = changes.reserve('replication');\n const lastWatermark = typeof res === 'string' ? res : await res;\n reservation = {lastWatermark};\n }\n\n let lastChange: ChangeStreamMessage | undefined;\n for (const change of await changeMaker.makeChanges(\n this.#lc.withContext('lsn', fromBigInt(lsn)),\n lsn,\n msg,\n )) {\n await changes.push(change); // Allow the change-streamer to push back.\n lastChange = change;\n }\n\n switch (lastChange?.[0]) {\n case 'begin':\n inTransaction = true;\n break;\n case 'commit':\n inTransaction = false;\n reservation.lastWatermark = lastChange[2].watermark;\n if (\n messages.queued === 0 ||\n changes.waiterDelay() > MAX_LOW_PRIORITY_DELAY_MS\n ) {\n // After each transaction, release the reservation:\n // - if there are no pending upstream messages\n // - or if a low priority request has been waiting for longer\n // than MAX_LOW_PRIORITY_DELAY_MS. This is to prevent\n // (backfill) starvation on very active upstreams.\n changes.release(reservation.lastWatermark);\n reservation = null;\n }\n break;\n }\n }\n } catch (e) {\n // Note: no need to worry about reservations here since downstream\n // is being completely canceled.\n const err = translateError(e);\n if (err instanceof ShutdownSignal) {\n // Log the new state of the replica to surface information about the\n // server that sent the shutdown signal, if any.\n await this.#logCurrentReplicaInfo();\n }\n changes.fail(err);\n }\n })();\n\n this.#lc.info?.(\n `started replication stream@${slot} from ${clientWatermark} (replicaVersion: ${\n this.#replica.generation\n })`,\n );\n\n return {\n changes: changes.asSource(),\n acks: {push: status => acker.ack(status[2].watermark)},\n };\n }\n\n /**\n * Warns if the stream is about to start from before the slot's\n * `confirmed_flush_lsn`, from which Postgres starts streaming instead.\n *\n * This is benign if the slot was only acked past `clientWatermark` for\n * keepalives (i.e. with no transactions in between), which the\n * {@link Acker} does on a caught-up stream. It is therefore not an error\n * (`minStartWatermark` is the bound that is), but it is the evidence needed\n * to diagnose a skipped interval.\n */\n async #checkConfirmedFlushLSN(slot: string, clientWatermark: string) {\n try {\n const [row] = await this.#db<{confirmedFlushLSN: LSN | null}[]>`\n SELECT confirmed_flush_lsn AS \"confirmedFlushLSN\"\n FROM pg_replication_slots WHERE slot_name = ${slot}`;\n const confirmed = row?.confirmedFlushLSN;\n if (!confirmed) {\n return;\n }\n const start = majorVersionFromString(clientWatermark);\n const confirmedLSN = toBigInt(confirmed);\n if (start < confirmedLSN) {\n this.#lc.warn?.(\n `starting replication stream@${slot} from ${clientWatermark}, ` +\n `which is ${confirmedLSN - start} bytes before the slot's ` +\n `confirmed_flush_lsn ${confirmed} ` +\n `(${toStateVersionString(confirmed)}). Postgres starts from the ` +\n `latter, which skips nothing only if the slot was acked past ` +\n `${clientWatermark} for keepalives.`,\n );\n } else {\n this.#lc.info?.(\n `replication slot ${slot} confirmed_flush_lsn: ${confirmed} ` +\n `(${toStateVersionString(confirmed)})`,\n );\n }\n } catch (e) {\n this.#lc.warn?.(`unable to read the position of slot ${slot}`, e);\n }\n }\n\n async #logCurrentReplicaInfo() {\n try {\n const replica = await getReplicaAtVersion(\n this.#lc,\n this.#db,\n this.#shard,\n this.#replica.generation,\n this.#replica.id,\n );\n if (replica) {\n this.#lc.info?.(\n `Shutdown signal from replica@${this.#replica.generation}: ${stringify(replica.subscriberContext)}`,\n );\n }\n } catch (e) {\n this.#lc.warn?.(`error logging replica info`, e);\n }\n }\n\n /**\n * In RMv1, a single replication slot is taken over by the next\n * replication-manager, signaling the old one to shut down.\n *\n * With litestream v5, the two replication-managers must replicate to unique\n * backupPaths, as is required by the LTX backup schema.\n *\n * Thus, in addition to taking over the replication slot, the\n * replication-manager also updates the `replicas` table with its\n * `backupPath` so that future RM's restore from that path instead\n * of the vestigial path of the previous replication-manager.\n *\n * In RMv2, each replication-manager will have its own slot and\n * row in the `replicas` table, so this \"takeover\" will be unnecessary\n * (but a harmless no-op).\n */\n async #takeoverReplicationSlot() {\n const sql = this.#db;\n const {id: replicaID, slot} = this.#replica;\n const replicasTable = `${upstreamSchema(this.#shard)}.replicas`;\n\n // Before terminating (and taking over) the replication slot, transition\n // the stage from InitialSync to Replicate, so that a replica in the\n // InitialSync stage is never intentionally inactive. This allows the\n // restore logic to safely exclude (actually) orphaned initial syncs.\n await sql`\n UPDATE ${sql(replicasTable)} \n SET \"stage\" = ${Replicate},\n \"subscriberContext\" = ${this.#context},\n \"backupPath\" = ${this.#backupOptions.backupPath},\n \"backupV5\" = ${this.#backupOptions.backupV5}\n WHERE id = ${replicaID}`;\n\n const result = await sql`\n SELECT pg_terminate_backend(active_pid) as terminated, active_pid as pid\n FROM pg_replication_slots\n WHERE slot_name = ${slot}\n `;\n if (result.length === 0) {\n const slotExpression = replicationSlotPrefix(this.#shard);\n const replicas = await sql`\n SELECT id, rank, slot, generation, \"initialSyncContext\", \"subscriberContext\" \n FROM ${sql(replicasTable)} ORDER BY rank DESC`;\n const slots = await sql`\n SELECT slot_name as slot, active, active_pid as pid\n FROM pg_replication_slots\n WHERE slot_name LIKE ${slotExpression}\n ORDER BY slot_name`;\n this.#lc.warn?.(`slot ${slot} not found while cleaning subscribers`, {\n slots,\n replicas,\n });\n throw new AbortError(\n `replication slot ${slot} is missing. A different ` +\n `replication-manager should now be running on a new ` +\n `replication slot.`,\n );\n }\n this.#lc.info?.(`terminated replication slots: ${JSON.stringify(result)}`);\n }\n}\n\n// Exported for testing.\nexport class Acker implements Listener {\n #acks: Sink<bigint>;\n #waitingForDownstreamAck: string | null;\n\n /**\n * @param resumeWatermark the watermark the stream resumes after. What came\n * before it was received on an earlier connection, and only the\n * change-streamer knows whether it has persisted it, so keepalives are\n * not acked until the change-streamer has acked it. A change-streamer\n * whose SQLite change log is ahead of its backup would otherwise have\n * the slot moved past transactions that only that log holds.\n */\n constructor(acks: Sink<bigint>, resumeWatermark: string | null) {\n this.#acks = acks;\n this.#waitingForDownstreamAck = resumeWatermark;\n }\n\n onChange(change: ChangeStreamMessage): void {\n switch (change[0]) {\n case 'status':\n const {watermark} = change[2];\n if (change[1].ack) {\n this.#expectDownstreamAck(watermark);\n } else {\n // Keepalives with shouldRespond = false are not acked by the\n // change-streamer. Ack them here if the change-streamer is caught\n // up. This updates the replication slot's `confirmed_flush_lsn`\n // more quickly (rather than waiting for the periodic shouldRespond),\n // which is useful for monitoring replication slot lag.\n this.#ackIfDownstreamIsCaughtUp(watermark);\n }\n break;\n case 'begin':\n // Mark the commit watermark as being expected so that any intermediate\n // shouldRespond=false watermarks, which will be at the\n // commitWatermark, are *not* acked, as the ack must come from\n // change-streamer after it commits the transaction.\n if (!change[1].skipAck) {\n this.#expectDownstreamAck(change[2].commitWatermark);\n }\n break;\n }\n }\n\n #expectDownstreamAck(watermark: string) {\n this.#waitingForDownstreamAck = watermark;\n }\n\n ack(watermark: LexiVersion) {\n if (\n this.#waitingForDownstreamAck &&\n this.#waitingForDownstreamAck <= watermark\n ) {\n this.#waitingForDownstreamAck = null;\n }\n this.#sendAck(watermark);\n }\n\n #ackIfDownstreamIsCaughtUp(watermark: string) {\n if (this.#waitingForDownstreamAck === null) {\n this.#sendAck(watermark);\n }\n }\n\n #sendAck(watermark: LexiVersion) {\n const lsn = majorVersionFromString(watermark);\n this.#acks.push(lsn);\n }\n}\n\nconst lagReportSchema = v.object({\n id: v.string(),\n sendTimeMs: v.number(),\n commitTimeMs: v.number(),\n});\n\nexport type LagReport = v.Infer<typeof lagReportSchema>;\n\ntype InitiatedLagReport = LagReport & {lsn: bigint};\n\nexport class LagReporter {\n static readonly MESSAGE_SUFFIX = '/lag-report/v1';\n\n readonly #lc: LogContext;\n readonly messagePrefix: string;\n\n // oxlint-disable-next-line no-unused-private-class-members\n readonly #db: PostgresDB;\n readonly #reportIntervalMs: number;\n readonly #reportIntervalJitterMs: number;\n readonly #lagReportRetries = getOrCreateCounter(\n 'replication',\n 'lag_report_retries',\n {\n description:\n 'Number of replication lag reports retried because the expected ' +\n 'report was not received before the next report interval.',\n unit: '{report}',\n },\n );\n\n readonly #pgVersion: number;\n #expectingLagReport: InitiatedLagReport | null = null;\n // The sendTimeMs of the latest report from which the next report was\n // scheduled.\n #lastAcceptedSendTimeMs = 0;\n #timer: NodeJS.Timeout | undefined;\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n db: PostgresDB,\n pgVersion: number,\n reportIntervalMs: number,\n reportIntervalJitterMs: number = 2000, // +/- 1 second\n ) {\n this.#lc = lc;\n this.messagePrefix = `${shard.appID}/${shard.shardNum}${LagReporter.MESSAGE_SUFFIX}`;\n this.#db = db;\n this.#pgVersion = pgVersion;\n this.#reportIntervalMs = reportIntervalMs;\n this.#reportIntervalJitterMs = reportIntervalJitterMs;\n }\n\n get pgVersion() {\n return this.#pgVersion;\n }\n\n async initiateLagReport(log = false) {\n const now = Date.now();\n const id = nanoid();\n\n // lsn is filled in after the db call.\n const lagReport = {id, sendTimeMs: now, commitTimeMs: now, lsn: 0n};\n this.#expectingLagReport = lagReport;\n\n let commitTimeMs: number;\n let lsn: string;\n\n try {\n if (this.#pgVersion >= PG_17) {\n [{commitTimeMs, lsn}] = await this.#db /*sql*/ `\n WITH CTE AS (SELECT extract(epoch from now()) * 1000 AS \"commitTimeMs\")\n SELECT \"commitTimeMs\", pg_logical_emit_message(\n false,\n ${this.messagePrefix},\n json_build_object(\n 'id', ${id}::text,\n 'sendTimeMs', ${now}::int8,\n 'commitTimeMs', \"commitTimeMs\"\n )::text,\n true\n ) as lsn FROM CTE;\n `;\n } else {\n // Versions before PG 17 do not support the final `flush` option of\n // pg_logical_emit_message(). This results in an extra 50~100ms latency\n // for replication reports when the db is idle, which is still\n // acceptable for the purpose for alerting on pathological lag, for\n // which the threshold is much higher (e.g. many seconds).\n [{commitTimeMs, lsn}] = await this.#db /*sql*/ `\n WITH CTE AS (SELECT extract(epoch from now()) * 1000 as \"commitTimeMs\")\n SELECT \"commitTimeMs\", pg_logical_emit_message(\n false,\n ${this.messagePrefix},\n json_build_object(\n 'id', ${id}::text,\n 'sendTimeMs', ${now}::int8,\n 'commitTimeMs', \"commitTimeMs\"\n )::text\n ) as lsn FROM CTE;\n `;\n // The LSN returned by pg_logical_emit_message() is not necessarily\n // comparable to the LSNs of the replication stream (e.g. on Aurora,\n // it is in the cluster volume's separate logical WAL stream). The\n // WAL insert position, read after the message is emitted, is in the\n // stream's LSN space and is at or beyond the end of the message,\n // which is what checkCurrentLSN() requires.\n [{lsn}] = await this.#db /*sql*/ `\n SELECT pg_current_wal_insert_lsn() AS lsn`;\n }\n } catch (e) {\n if (this.#expectingLagReport?.id === id) {\n this.#expectingLagReport = null;\n }\n throw e;\n }\n\n // Note: We don't know the lsn until after pg_logical_emit_message()\n // returns, at which point it is possible that the report has\n // already been sent through the replication stream, but this\n // is okay since this.#expectingLagReport will have be updated.\n lagReport.lsn = toBigInt(lsn);\n lagReport.commitTimeMs = commitTimeMs;\n if (this.#expectingLagReport?.id === id) {\n this.#scheduleMissingReportRetry(id);\n }\n\n if (log) {\n this.#lc.info?.(`initiated lag report at lsn ${lsn}`, {\n id,\n lsn,\n sendTimeMs: now,\n commitTimeMs,\n });\n }\n return {firstCommitTimeMs: commitTimeMs, nextSendTimeMs: now};\n }\n\n /**\n * In Postgres < 17, the pg_logical_emit_message lacks an immediate \"flush\"\n * option, which can cause messages to be missed when the replication stream\n * starts up:\n *\n * ```\n * * emit message → WAL write (buffered, not flushed)\n * * walsender reads up to current flush LSN\n * * emitted message's LSN is beyond flush LSN → not yet visible\n * * stream feedback/acknowledgment advances slot\n * * WAL eventually flushes → but slot has already moved past it\n * ```\n *\n * This has been seen to happen for the initial `wal_writer_delay` interval\n * of a replication session.\n *\n * To account for this, the last emitted lag report is considered \"received\"\n * if the stream has advanced beyond the WAL insert position read after the\n * report was emitted (see initiateLagReport()).\n *\n * This is unnecessary in PG 17+, where reports are emitted with `flush`.\n */\n checkCurrentLSN(lsn: bigint): DownstreamStatusMessage | undefined {\n if (this.#pgVersion >= PG_17) {\n return undefined;\n }\n if (this.#expectingLagReport?.lsn && lsn > this.#expectingLagReport.lsn) {\n this.#lc.info?.(\n `LSN ${fromBigInt(lsn)} is passed expected lag report ` +\n `${fromBigInt(this.#expectingLagReport.lsn)}. Processing it as received.`,\n );\n return this.#processLagReport(\n this.#expectingLagReport,\n majorVersionToString(lsn),\n );\n }\n return undefined;\n }\n\n stop() {\n clearTimeout(this.#timer);\n this.#timer = undefined;\n this.#expectingLagReport = null;\n this.#lastAcceptedSendTimeMs = 0;\n }\n\n #scheduleNextReportWithJitter(delayMs: number) {\n const jitter =\n Math.floor(Math.random() * this.#reportIntervalJitterMs) -\n this.#reportIntervalJitterMs / 2;\n delayMs = Math.max(0, delayMs + jitter);\n\n this.#expectingLagReport = null;\n clearTimeout(this.#timer);\n this.#timer = setTimeout(async () => {\n this.#timer = undefined;\n try {\n await this.initiateLagReport();\n } catch (e) {\n this.#lc.warn?.(`error initiating lag report`, e);\n this.#scheduleNextReportWithJitter(this.#reportIntervalMs);\n }\n }, delayMs);\n }\n\n #scheduleMissingReportRetry(reportID: string) {\n clearTimeout(this.#timer);\n this.#timer = setTimeout(async () => {\n this.#timer = undefined;\n const missingReport = this.#expectingLagReport;\n if (missingReport?.id !== reportID) {\n return;\n }\n\n this.#lagReportRetries.add(1);\n this.#lc.warn?.(`retrying missing lag report`, {\n id: missingReport.id,\n lsn: fromBigInt(missingReport.lsn),\n sendTimeMs: missingReport.sendTimeMs,\n });\n try {\n await this.initiateLagReport();\n } catch (e) {\n this.#lc.warn?.(`error retrying lag report`, e);\n this.#scheduleNextReportWithJitter(this.#reportIntervalMs);\n }\n }, this.#reportIntervalMs);\n }\n\n /**\n * @param lsn The LSN of the message in the replication stream (i.e. from the\n * XLogData header), rather than the `messageLsn` of the message itself,\n * which is not guaranteed to be comparable to other stream LSNs (e.g. on\n * Aurora). See {@link initiateLagReport}.\n */\n processLagReportMessage(\n msg: MessageMessage,\n lsn: bigint,\n ): DownstreamStatusMessage {\n assert(\n msg.prefix === this.messagePrefix,\n `unexpected message prefix: ${msg.prefix}`,\n );\n const report = parseLogicalMessageContent(this.#lc, msg, lagReportSchema);\n return this.#processLagReport(report, majorVersionToString(lsn));\n }\n\n #processLagReport(\n report: LagReport,\n watermark: string,\n ): DownstreamStatusMessage {\n const now = Date.now();\n const expectedReport = this.#expectingLagReport;\n let nextSendTimeMs: number;\n if (\n expectedReport\n ? // Accept any report whose send time is within the reporting\n // interval of the report that is expected.\n Math.abs(report.sendTimeMs - expectedReport.sendTimeMs) <\n this.#reportIntervalMs\n : // When waiting to send the next report, accept a newer report\n // (i.e. from another replication-manager) as a substitute, and\n // postpone the next report accordingly. This, in addition to\n // scheduling each report with jitter, allows multiple\n // replication-managers to coalesce on a single stream of reports.\n //\n // Note that a report is never initiated from here if one was not\n // already scheduled (e.g. after stop()).\n this.#timer !== undefined &&\n report.sendTimeMs > this.#lastAcceptedSendTimeMs\n ) {\n this.#lastAcceptedSendTimeMs = report.sendTimeMs;\n nextSendTimeMs = Math.max(\n now,\n report.sendTimeMs + this.#reportIntervalMs,\n );\n this.#scheduleNextReportWithJitter(nextSendTimeMs - now);\n } else {\n // Other reports, such as historic reports in the WAL, are still\n // sent downstream, but the next report is not actually scheduled.\n nextSendTimeMs =\n expectedReport?.sendTimeMs ??\n Math.max(now, report.sendTimeMs + this.#reportIntervalMs);\n }\n const {sendTimeMs, commitTimeMs} = report;\n return [\n 'status',\n {\n ack: false,\n lagReport: {\n lastTimings: {\n sendTimeMs,\n commitTimeMs,\n receiveTimeMs: now,\n },\n nextSendTimeMs,\n },\n },\n {watermark},\n ];\n }\n}\n\ntype ReplicationError = {\n lsn: bigint;\n msg: Message;\n err: unknown;\n lastLogTime: number;\n};\n\nconst SET_REPLICA_IDENTITY_DELAY_MS = 50;\n\nclass ChangeMaker {\n readonly #shard: ShardID;\n readonly #shardPrefix: string;\n readonly #shardConfig: InternalShardConfig;\n readonly #replica: Replica;\n readonly #db: PostgresDB;\n\n #replicaIdentityTimer: NodeJS.Timeout | undefined;\n #error: ReplicationError | undefined;\n readonly #skippedIndexWarnings = new Set<string>();\n\n constructor(\n shard: ShardID,\n shardConfig: InternalShardConfig,\n db: PostgresDB,\n replica: Replica,\n ) {\n this.#shard = shard;\n // Note: This matches the prefix used in pg_logical_emit_message() in pg/schema/ddl.ts.\n this.#shardPrefix = `${shard.appID}/${shard.shardNum}`;\n this.#shardConfig = shardConfig;\n this.#replica = replica;\n this.#db = db;\n }\n\n async makeChanges(\n lc: LogContext,\n lsn: bigint,\n msg: Message,\n ): Promise<ChangeStreamMessage[]> {\n if (this.#error) {\n this.#logError(lc, this.#error);\n return [];\n }\n try {\n return await this.#makeChanges(lc, lsn, msg);\n } catch (err) {\n this.#error = {lsn, msg, err, lastLogTime: 0};\n this.#logError(lc, this.#error);\n\n await this.#invalidateReplica(lc, err);\n\n const message = `Unable to continue replication from LSN ${fromBigInt(lsn)}`;\n const errorDetails: JSONObject = {error: message};\n if (err instanceof UnsupportedSchemaChangeError) {\n errorDetails.reason = err.description;\n errorDetails.context = err.event.context;\n } else {\n errorDetails.reason = String(err);\n }\n\n // Rollback the current transaction to avoid dangling transactions in\n // downstream processors (i.e. changeLog, replicator).\n return [\n ['rollback', {tag: 'rollback'}],\n ['control', {tag: 'reset-required', message, errorDetails}],\n ];\n }\n }\n\n #replicaInvalidated = false;\n\n // Deletes this replica's row in the `replicas` table so that it, and its\n // slot, are removed from candidacy for restore. This is a poison pill to\n // initiate an auto-reset (i.e. resync) without relying on a separate\n // global store (e.g. no PG change-log required).\n //\n // Note that there is no need to delete the slot, as this will be taken care\n // of in `dropUnclaimedSlots()` when a new replica is synced, or by the\n // replication-slot-cleanup-monitor of a healthy replica.\n async #invalidateReplica(lc: LogContext, err: unknown) {\n if (this.#replicaInvalidated) {\n return;\n }\n const sql = this.#db;\n const {id: replicaID} = this.#replica;\n const replicasTable = `${upstreamSchema(this.#shard)}.replicas`;\n\n lc.warn?.(`deleting invalid replica: ${this.#replica.id}`, err);\n await sql`\n DELETE FROM ${sql(replicasTable)} WHERE \"id\" = ${replicaID}\n `;\n this.#replicaInvalidated = true;\n }\n\n #logError(lc: LogContext, error: ReplicationError) {\n const {lsn, msg, err, lastLogTime} = error;\n const now = Date.now();\n\n // Output an error to logs as replication messages continue to be dropped,\n // at most once a minute.\n if (now - lastLogTime > 60_000) {\n lc.error?.(\n `Unable to continue replication from LSN ${fromBigInt(lsn)}: ${String(\n err,\n )}`,\n {\n // 'content' can be a large byte Buffer. Exclude it from logging output.\n ...{change: {...msg, content: undefined}},\n ...(err instanceof UnsupportedSchemaChangeError && {\n context: err.event.context,\n }),\n ...(err instanceof Error && {\n errorMsg: err.message,\n name: err.name,\n stack: err.stack,\n }),\n },\n );\n error.lastLogTime = now;\n }\n }\n\n async #makeChanges(\n lc: LogContext,\n lsn: bigint,\n msg: Message,\n ): Promise<ChangeStreamData[]> {\n switch (msg.tag) {\n case 'begin':\n return [\n [\n 'begin',\n {...msg, json: 's'},\n {commitWatermark: toStateVersionString(must(msg.commitLsn))},\n ],\n ];\n\n case 'delete': {\n if (!(msg.key ?? msg.old)) {\n throw new Error(\n `Invalid DELETE msg (missing key): ${stringify(msg)}`,\n );\n }\n return [\n [\n 'data',\n {\n ...msg,\n relation: makeRelation(msg.relation),\n // https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-DELETE\n key: must(msg.old ?? msg.key),\n },\n ],\n ];\n }\n\n case 'update': {\n return [\n [\n 'data',\n {\n ...msg,\n relation: makeRelation(msg.relation),\n // https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-UPDATE\n key: msg.old ?? msg.key,\n },\n ],\n ];\n }\n\n case 'insert':\n return [['data', {...msg, relation: makeRelation(msg.relation)}]];\n case 'truncate':\n return [['data', {...msg, relations: msg.relations.map(makeRelation)}]];\n\n case 'message':\n if (!msg.prefix.startsWith(this.#shardPrefix)) {\n lc.debug?.('ignoring message for different shard', msg.prefix);\n return [];\n }\n switch (msg.prefix.substring(this.#shardPrefix.length)) {\n case '': // Legacy prefix\n case '/ddl':\n return this.#handleDdlMessage(lc, lsn, msg);\n default:\n lc.debug?.('ignoring unknown message type', msg.prefix);\n return [];\n }\n\n case 'commit':\n // The DDL event that provides command-tag context for a subsequent\n // event (see #handleDdlMessage) is only meaningful within a single\n // (upstream) transaction, since related ddl events (e.g. the nested\n // start->start->end->end sequence) are always emitted together. Clear\n // it at the transaction boundary so that a lingering event (e.g. a\n // `CREATE TABLE` ddlStart) is not misattributed as the context for an\n // unrelated event in a later transaction. Note that this is safe for\n // the one type of DDL event that spans multiple transactions,\n // `CREATE INDEX CONCURRENTLY`, because that change is self declaring\n // in the `ddlUpdate` and does not depend on the value of the preceding\n // event.\n this.#lastReplicationEvent = undefined;\n return [\n [\n 'commit',\n // `commitTime` is microseconds since the unix epoch. Carrying it\n // as milliseconds gives the ViewSyncer the origin timestamp for\n // the end-to-end serving lag histogram.\n {...msg, commitTimeMs: Number(msg.commitTime / 1000n)},\n {watermark: toStateVersionString(must(msg.commitLsn))},\n ],\n ];\n\n case 'relation':\n return await this.#handleRelation(msg);\n case 'type':\n return []; // Nothing need be done for custom types.\n case 'origin':\n // No need to detect replication loops since we are not a\n // PG replication source.\n return [];\n default:\n msg satisfies never;\n throw new Error(`Unexpected message type ${stringify(msg)}`);\n }\n }\n\n // The lastReplicationEvent is stored to understand the context\n // of the next one.\n #lastReplicationEvent: ReplicationEvent | undefined;\n\n #handleDdlMessage(\n lc: LogContext,\n lsn: bigint,\n msg: MessageMessage,\n ): ChangeStreamData[] {\n const event = parseLogicalMessageContent(lc, msg, replicationEventSchema);\n lc = lc\n .withContext('lsn', fromBigInt(lsn))\n .withContext('tag', event.event.tag)\n .withContext('query', event.context.query);\n\n if (event.previousSchema) {\n warnForSkippedIndexes(\n lc,\n event.previousSchema,\n this.#skippedIndexWarnings,\n );\n }\n if (event.schema) {\n warnForSkippedIndexes(lc, event.schema, this.#skippedIndexWarnings);\n }\n\n // Cancel manual schema adjustment timeouts when an upstream schema change\n // is about to happen, so as to avoid interfering / redundant work.\n clearTimeout(this.#replicaIdentityTimer);\n\n const {type} = event;\n switch (type) {\n case 'ddlStart':\n case 'schemaSnapshot':\n case 'ddlUpdate':\n break;\n default: // Ignore unknown types for forwards compatibility\n lc.info?.(`ignoring unknown ddl message type: ${type}`);\n return [];\n }\n\n const prevEvent = this.#lastReplicationEvent;\n // Store the new event to understand the context of the next event.\n this.#lastReplicationEvent = event;\n\n const {schema} = event;\n if (schema === undefined) {\n // A schema-less (protocol v2) ddlStart event signifies that there was\n // no schema change; it is stored (above) only to provide the command\n // tag context for a subsequent event with a schema change.\n lc.debug?.(`received context-only ${msg.prefix}/${type} event`, {\n event: summarizeReplicationEventForLog(event),\n });\n return [];\n }\n\n const prevSchema =\n event.previousSchema === undefined // pre-v21 event => use prevEvent\n ? prevEvent?.schema\n : event.previousSchema;\n if (!prevSchema) {\n lc.info?.(`received ${msg.prefix}/${type} event`, {\n event: summarizeReplicationEventForLog(event),\n });\n return [];\n }\n\n // The tag (i.e. command) is used as an optimization to determine whether\n // backfill is necessary (CREATE TABLE vs ALTER TABLE vs\n // ALTER PUBLICATION). If the context is not available (rare), the tag\n // falls back to 'UNKNOWN', which conservatively initiates a backfill.\n //\n // Because ddl events may be nested, e.g.\n //\n // ```\n // [1] [2] [3] [4] [5]\n // ddl_start => ddl_start => ddl_end => ddl_start => ddl_end => ddl_end\n // ```\n //\n // The effective tag is determined from the previous event if it is\n // ddl_start (e.g. cases 1, 2, and 4), and from the current event\n // if it is a ddl_end (case 5), and 'UNKNOWN' otherwise (case 3 and\n // 'schemaSnapshot' workarounds).\n //\n // A 'schemaSnapshot' is special: it is a standalone hook (emitted by the\n // COMMENT ON PUBLICATION workaround, or a MANUAL update_schemas() call)\n // that substitutes for a missing ALTER PUBLICATION event on databases\n // (e.g. supabase) that do not fire event triggers for it. It must never\n // adopt the command tag of a preceding ddlStart (e.g. a `CREATE TABLE`\n // in the same transaction), as that would cause a newly *published*\n // table to be misclassified as a freshly *created* one and skip the\n // backfill of its pre-existing rows. It therefore always falls back to\n // 'UNKNOWN', which conservatively initiates a backfill.\n const effectiveTag =\n event.type === 'schemaSnapshot'\n ? 'UNKNOWN'\n : prevEvent?.type === 'ddlStart'\n ? prevEvent.event.tag\n : event.type === 'ddlUpdate'\n ? event.event.tag\n : 'UNKNOWN';\n lc.info?.(`processing ${effectiveTag} command from ${msg.prefix}/${type}`, {\n event: summarizeReplicationEventForLog(event),\n });\n const changes = this.#makeSchemaChanges(\n lc,\n prevSchema,\n schema,\n event,\n effectiveTag,\n ).map(change => ['data', change] satisfies Data);\n\n lc.info?.(`${changes.length} schema change(s)`, {changes});\n\n const replicaIdentities =\n replicaIdentitiesForTablesWithoutPrimaryKeys(schema);\n if (replicaIdentities) {\n this.#replicaIdentityTimer = setTimeout(async () => {\n try {\n await replicaIdentities.apply(lc, this.#db);\n } catch (err) {\n lc.warn?.(`error setting replica identities`, err);\n }\n }, SET_REPLICA_IDENTITY_DELAY_MS);\n }\n\n return changes;\n }\n\n /**\n * A note on operation order:\n *\n * Postgres will drop related indexes when columns are dropped,\n * but SQLite will error instead (https://sqlite.org/forum/forumpost/2e62dba69f?t=c&hist).\n * The current workaround is to drop indexes first.\n *\n * Also note that although it should not be possible to both rename and\n * add/drop tables/columns in a single statement, the operations are\n * ordered to handle that possibility, by always dropping old entities,\n * then modifying kept entities, and then adding new entities.\n *\n * Thus, the order of replicating DDL updates is:\n * - drop indexes\n * - drop tables\n * - alter tables\n * - drop columns\n * - alter columns\n * - add columns\n * - create tables\n * - create indexes\n *\n * In the future the replication logic should be improved to handle this\n * behavior in SQLite by dropping dependent indexes manually before dropping\n * columns. This, for example, would be needed to properly support changing\n * the type of a column that's indexed.\n */\n #makeSchemaChanges(\n lc: LogContext,\n preSchema: PublishedSchema,\n nextSchema: PublishedSchema,\n event: ReplicationEvent,\n tag: string,\n ): SchemaChange[] {\n try {\n const [prevTbl, prevIdx] = specsByID(preSchema);\n const [nextTbl, nextIdx] = specsByID(nextSchema);\n const changes: SchemaChange[] = [];\n\n // Validate the new table schemas\n for (const table of nextTbl.values()) {\n validate(lc, table);\n }\n\n const [droppedIdx, createdIdx] = symmetricDifferences(prevIdx, nextIdx);\n\n // Detect modified indexes (same name, different definition).\n // This happens when a constraint is dropped and recreated with the\n // same name in a single ALTER TABLE statement.\n // Note: We compare using stable column attnums rather than names,\n // because table/column renames change the index spec cosmetically\n // (tableName, column keys) without the index actually being recreated.\n const keptIdx = intersection(prevIdx, nextIdx);\n for (const id of keptIdx) {\n if (\n isIndexStructurallyChanged(\n must(prevIdx.get(id)),\n must(nextIdx.get(id)),\n prevTbl,\n nextTbl,\n )\n ) {\n droppedIdx.add(id);\n createdIdx.add(id);\n }\n }\n\n for (const id of droppedIdx) {\n const {schema, name} = must(prevIdx.get(id));\n changes.push({tag: 'drop-index', id: {schema, name}});\n }\n\n // DROP\n const [droppedTbl, createdTbl] = symmetricDifferences(prevTbl, nextTbl);\n for (const id of droppedTbl) {\n const {schema, name} = must(prevTbl.get(id));\n changes.push({tag: 'drop-table', id: {schema, name}});\n }\n // ALTER TABLE | ALTER PUBLICATION\n const tables = intersection(prevTbl, nextTbl);\n for (const id of tables) {\n changes.push(\n ...this.#getTableChanges(\n lc,\n must(prevTbl.get(id)),\n must(nextTbl.get(id)),\n tag,\n ),\n );\n }\n // CREATE\n for (const id of createdTbl) {\n const spec = must(nextTbl.get(id));\n const createTable: TableCreate = {\n tag: 'create-table',\n spec,\n metadata: getMetadata(spec),\n };\n // Only tables introduced by a `CREATE` statement can skip backfill.\n // All other scenarios in which tables are introduced into the\n // schema, e.g.\n // * ALTER PUBLICATION statements\n // * COMMENT statements\n // * MANUAL snapshots\n // * UNKNOWN command tags\n // must be backfilled.\n if (!tag.startsWith('CREATE')) {\n createTable.backfill = mapValues(spec.columns, ({pos: attNum}) => ({\n attNum,\n })) satisfies Record<string, ColumnMetadata>;\n }\n changes.push(createTable);\n }\n\n // Add indexes last since they may reference tables / columns that need\n // to be created first.\n for (const id of createdIdx) {\n const spec = must(nextIdx.get(id));\n changes.push({tag: 'create-index', spec});\n }\n return changes;\n } catch (e) {\n throw new UnsupportedSchemaChangeError(String(e), event, {cause: e});\n }\n }\n\n #getTableChanges(\n lc: LogContext,\n oldTable: PublishedTableWithReplicaIdentity,\n newTable: PublishedTableWithReplicaIdentity,\n ddlTag: string,\n ): SchemaChange[] {\n const changes: SchemaChange[] = [];\n if (\n oldTable.schema !== newTable.schema ||\n oldTable.name !== newTable.name\n ) {\n changes.push({\n tag: 'rename-table',\n old: {schema: oldTable.schema, name: oldTable.name},\n new: {schema: newTable.schema, name: newTable.name},\n });\n }\n const oldMetadata = getMetadata(oldTable);\n const newMetadata = getMetadata(newTable);\n if (!deepEqual(oldMetadata, newMetadata)) {\n changes.push({\n tag: 'update-table-metadata',\n table: {schema: newTable.schema, name: newTable.name},\n old: oldMetadata,\n new: newMetadata,\n });\n }\n const table = {schema: newTable.schema, name: newTable.name};\n const oldColumns = columnsByID(oldTable.columns);\n const newColumns = columnsByID(newTable.columns);\n\n // DROP\n const [dropped, added] = symmetricDifferences(oldColumns, newColumns);\n for (const id of dropped) {\n const {name: column} = must(oldColumns.get(id));\n changes.push({tag: 'drop-column', table, column});\n }\n\n // ALTER\n const both = intersection(oldColumns, newColumns);\n for (const id of both) {\n const {name: oldName, ...oldSpec} = must(oldColumns.get(id));\n const {name: newName, ...newSpec} = must(newColumns.get(id));\n // The three things that we care about are:\n // 1. name\n // 2. type\n // 3. not-null\n if (\n oldName !== newName ||\n oldSpec.dataType !== newSpec.dataType ||\n oldSpec.notNull !== newSpec.notNull\n ) {\n changes.push({\n tag: 'update-column',\n table,\n old: {name: oldName, spec: oldSpec},\n new: {name: newName, spec: newSpec},\n });\n }\n }\n\n // Only columns introduced by `ALTER TABLE` statements can potentially\n // skip backfill if they have non-constant defaults. All other scenarios\n // in which columns are introduced, e.g.\n // * ALTER PUBLICATION\n // * COMMENT\n // * MANUAL\n // * UNKNOWN\n // must be backfilled.\n const alwaysBackfill = ddlTag !== 'ALTER TABLE';\n\n // ADD\n for (const id of added) {\n const {name, ...spec} = must(newColumns.get(id));\n const column = {name, spec};\n const addColumn: ColumnAdd = {\n tag: 'add-column',\n table,\n column,\n tableMetadata: getMetadata(newTable),\n };\n if (alwaysBackfill) {\n addColumn.column.spec.dflt = null;\n addColumn.backfill = {attNum: spec.pos} satisfies ColumnMetadata;\n } else {\n // Determine if the ChangeProcessor will accept the column add as is.\n try {\n mapPostgresToLiteColumn(table.name, column);\n } catch (e) {\n if (!(e instanceof UnsupportedColumnDefaultError)) {\n // Note: mapPostgresToLiteColumn is not expected to throw any other\n // types of errors.\n throw e;\n }\n // If the column has an unsupported default (e.g. an expression or a\n // generated value), create the column as initially hidden with a\n // `null` default, and publish it after backfilling the values from\n // upstream. Note that this does require that the table have a valid\n // REPLICA IDENTITY, since backfill relies on merging new data with\n // an existing row.\n lc.info?.(`Backfilling column ${table.name}.${name}: ${String(e)}`);\n addColumn.column.spec.dflt = null;\n addColumn.backfill = {attNum: spec.pos} satisfies ColumnMetadata;\n }\n }\n changes.push(addColumn);\n }\n return changes;\n }\n\n /**\n * If `ddlDetection === true`, relation messages are irrelevant,\n * as schema changes are detected by event triggers that\n * emit custom messages.\n *\n * For degraded-mode replication (`ddlDetection === false`):\n * 1. query the current published schemas on upstream\n * 2. compare that with the InternalShardConfig.initialSchema\n * 3. compare that with the incoming MessageRelation\n * 4. On any discrepancy, throw an UnsupportedSchemaChangeError\n * to halt replication.\n *\n * Note that schemas queried in step [1] will be *post-transaction*\n * schemas, which are not necessarily suitable for actually processing\n * the statements in the transaction being replicated. In other words,\n * this mechanism cannot be used to reliably *replicate* schema changes.\n * However, they serve the purpose determining if schemas have changed.\n */\n async #handleRelation(rel: PostgresRelation): Promise<ChangeStreamData[]> {\n const {publications, ddlDetection} = this.#shardConfig;\n if (ddlDetection) {\n return [];\n }\n const currentSchema = await getPublicationInfo(this.#db, publications);\n const difference = getSchemaDifference(\n this.#replica.initialSchema,\n currentSchema,\n );\n if (difference !== null) {\n throw new MissingEventTriggerSupport(difference);\n }\n // Even if the currentSchema is equal to the initialSchema, the\n // MessageRelation itself must be checked to detect transient\n // schema changes within the transaction (e.g. adding and dropping\n // a table, or renaming a column and then renaming it back).\n const orel = this.#replica.initialSchema.tables.find(\n t => t.oid === rel.relationOid,\n );\n if (!orel) {\n // Can happen if a table is created and then dropped in the same transaction.\n throw new MissingEventTriggerSupport(\n `relation not in initialSchema: ${stringify(rel)}`,\n );\n }\n if (relationDifferent(orel, rel)) {\n throw new MissingEventTriggerSupport(\n `relation has changed within the transaction: ${stringify(orel)} vs ${stringify(rel)}`,\n );\n }\n return [];\n }\n}\n\nfunction getSchemaDifference(\n a: PublishedSchema,\n b: PublishedSchema,\n): string | null {\n // Note: ignore indexes since changes need not to halt replication\n if (a.tables.length !== b.tables.length) {\n return `tables created or dropped`;\n }\n for (let i = 0; i < a.tables.length; i++) {\n const at = a.tables[i];\n const bt = b.tables[i];\n const difference = getTableDifference(at, bt);\n if (difference) {\n return difference;\n }\n }\n return null;\n}\n\n// ColumnSpec comparator\nconst byColumnPos = (a: [string, ColumnSpec], b: [string, ColumnSpec]) =>\n a[1].pos < b[1].pos ? -1 : a[1].pos > b[1].pos ? 1 : 0;\n\nfunction getTableDifference(\n a: PublishedTableSpec,\n b: PublishedTableSpec,\n): string | null {\n if (a.oid !== b.oid || a.schema !== b.schema || a.name !== b.name) {\n return `Table \"${a.name}\" differs from table \"${b.name}\"`;\n }\n if (!deepEqual(a.primaryKey, b.primaryKey)) {\n return `Primary key of table \"${a.name}\" has changed`;\n }\n const acols = Object.entries(a.columns).sort(byColumnPos);\n const bcols = Object.entries(b.columns).sort(byColumnPos);\n if (\n acols.length !== bcols.length ||\n acols.some(([aname, acol], i) => {\n const [bname, bcol] = bcols[i];\n return (\n aname !== bname ||\n acol.pos !== bcol.pos ||\n acol.typeOID !== bcol.typeOID ||\n acol.notNull !== bcol.notNull\n );\n })\n ) {\n return `Columns of table \"${a.name}\" have changed`;\n }\n return null;\n}\n\nexport function relationDifferent(a: PublishedTableSpec, b: PostgresRelation) {\n if (a.oid !== b.relationOid || a.schema !== b.schema || a.name !== b.name) {\n return true;\n }\n if (\n // The MessageRelation's `keyColumns` field contains the columns in column\n // declaration order, whereas the PublishedTableSpec's `primaryKey`\n // contains the columns in primary key (i.e. index) order. Do an\n // order-agnostic compare here since it is not possible to detect\n // key-order changes from the MessageRelation message alone.\n b.replicaIdentity === 'default' &&\n !equals(new Set(a.primaryKey), new Set(b.keyColumns))\n ) {\n return true;\n }\n const acols = Object.entries(a.columns).sort(byColumnPos);\n const bcols = b.columns;\n return (\n acols.length !== bcols.length ||\n acols.some(([aname, acol], i) => {\n const bcol = bcols[i];\n return aname !== bcol.name || acol.typeOID !== bcol.typeOid;\n })\n );\n}\n\nfunction translateError(e: unknown): Error {\n if (!(e instanceof Error)) {\n return new Error(String(e));\n }\n if (e instanceof postgres.PostgresError && e.code === PG_ADMIN_SHUTDOWN) {\n return new ShutdownSignal(e);\n }\n return e;\n}\nconst idString = (id: Identifier) => `${id.schema}.${id.name}`;\n\nfunction specsByID(published: PublishedSchema) {\n return [\n // It would have been nice to use a CustomKeyMap here, but we rely on set-utils\n // operations which use plain Sets.\n new Map(published.tables.map(t => [t.oid, t])),\n new Map(published.indexes.map(i => [idString(i), i])),\n ] as const;\n}\n\n/**\n * Determines if an index was structurally changed (e.g. constraint dropped\n * and recreated with different columns) vs cosmetically changed (e.g. the\n * index spec changed because the table or a column was renamed).\n *\n * Compares boolean properties directly and resolves column names to their\n * stable attnums (pg_attribute `attnum`) for the column comparison.\n */\nexport function isIndexStructurallyChanged(\n prev: PublishedIndexSpec,\n next: PublishedIndexSpec,\n prevTables: Map<number, PublishedTableWithReplicaIdentity>,\n nextTables: Map<number, PublishedTableWithReplicaIdentity>,\n): boolean {\n if (\n prev.unique !== next.unique ||\n prev.isPrimaryKey !== next.isPrimaryKey ||\n prev.isReplicaIdentity !== next.isReplicaIdentity ||\n prev.isImmediate !== next.isImmediate\n ) {\n return true;\n }\n\n const prevTable = findTableBySchemaAndName(\n prevTables,\n prev.schema,\n prev.tableName,\n );\n const nextTable = findTableBySchemaAndName(\n nextTables,\n next.schema,\n next.tableName,\n );\n if (!prevTable || !nextTable) {\n // Can't resolve tables; conservatively treat as changed.\n return true;\n }\n\n if ((prev.predicate === undefined) !== (next.predicate === undefined)) {\n return true;\n }\n if (prev.predicate && next.predicate) {\n let unresolved = false;\n const prevPredicate = mapIndexPredicateColumns(prev.predicate, name => {\n const pos = prevTable.columns[name]?.pos;\n if (pos === undefined) unresolved = true;\n return String(pos);\n });\n const nextPredicate = mapIndexPredicateColumns(next.predicate, name => {\n const pos = nextTable.columns[name]?.pos;\n if (pos === undefined) unresolved = true;\n return String(pos);\n });\n if (unresolved || !deepEqual(prevPredicate, nextPredicate)) {\n return true;\n }\n }\n\n const prevEntries = Object.entries(prev.columns);\n const nextEntries = Object.entries(next.columns);\n if (prevEntries.length !== nextEntries.length) {\n return true;\n }\n\n // Resolve column names → attnums and compare.\n const prevByAttnum = new Map<number | undefined, string>(\n prevEntries.map(([name, dir]) => [prevTable.columns[name]?.pos, dir]),\n );\n const nextByAttnum = new Map<number | undefined, string>(\n nextEntries.map(([name, dir]) => [nextTable.columns[name]?.pos, dir]),\n );\n\n if (prevByAttnum.has(undefined) || nextByAttnum.has(undefined)) {\n // Column not found in table spec; conservatively treat as changed.\n return true;\n }\n if (prevByAttnum.size !== nextByAttnum.size) {\n return true;\n }\n for (const [attnum, dir] of prevByAttnum) {\n if (nextByAttnum.get(attnum) !== dir) {\n return true;\n }\n }\n return false;\n}\n\nfunction findTableBySchemaAndName(\n tables: Map<number, PublishedTableWithReplicaIdentity>,\n schema: string,\n name: string,\n): PublishedTableWithReplicaIdentity | undefined {\n for (const table of tables.values()) {\n if (table.schema === schema && table.name === name) {\n return table;\n }\n }\n return undefined;\n}\n\nfunction columnsByID(\n columns: Record<string, ColumnSpec>,\n): Map<number, ColumnSpec & {name: string}> {\n const colsByID = new Map<number, ColumnSpec & {name: string}>();\n for (const [name, spec] of Object.entries(columns)) {\n // The `pos` field is the `attnum` in `pg_attribute`, which is a stable\n // identifier for the column in this table (i.e. never reused).\n colsByID.set(spec.pos, {...spec, name});\n }\n return colsByID;\n}\n\nfunction getMetadata(table: PublishedTableWithReplicaIdentity): TableMetadata {\n return {\n schemaOID: must(table.schemaOID),\n relationOID: table.oid,\n rowKey: Object.fromEntries(\n table.replicaIdentityColumns.map(k => [\n k,\n {attNum: table.columns[k].pos},\n ]),\n ),\n };\n}\n\n// Avoid sending the `columns` from the Postgres MessageRelation message.\n// They are not used downstream and the message can be large.\nfunction makeRelation(relation: PostgresRelation): MessageRelation {\n // Avoid sending the `columns` from the Postgres MessageRelation message.\n // They are not used downstream and the message can be large.\n const {columns: _, keyColumns, replicaIdentity, ...rest} = relation;\n return {\n ...rest,\n rowKey: {\n columns: keyColumns,\n type: replicaIdentity,\n },\n // For now, deprecated columns are sent for backwards compatibility.\n // These can be removed when bumping the MIN_PROTOCOL_VERSION to 5.\n keyColumns,\n replicaIdentity,\n };\n}\n\nfunction summarizeSchemaForLog(schema: PublishedSchema) {\n return {\n tables: schema.tables.length,\n indexes: schema.indexes.length,\n };\n}\n\nfunction summarizeReplicationEventForLog(event: ReplicationEvent): JSONObject {\n const {schema, ...rest} = event;\n const {previousSchema, ...eventWithoutSchemas} = rest;\n return {\n ...eventWithoutSchemas,\n ...(schema !== undefined && {schema: summarizeSchemaForLog(schema)}),\n ...(previousSchema !== undefined && {\n previousSchema:\n previousSchema === null ? null : summarizeSchemaForLog(previousSchema),\n }),\n };\n}\n\nclass UnsupportedSchemaChangeError extends Error {\n readonly name = 'UnsupportedSchemaChangeError';\n readonly description: string;\n readonly event: ReplicationEvent;\n\n constructor(\n description: string,\n event: ReplicationEvent,\n options?: ErrorOptions,\n ) {\n super(\n `Replication halted. Resync the replica to recover: ${description}`,\n options,\n );\n this.description = description;\n this.event = event;\n }\n}\n\nclass MissingEventTriggerSupport extends Error {\n readonly name = 'MissingEventTriggerSupport';\n\n constructor(msg: string) {\n super(\n `${msg}. Schema changes cannot be reliably replicated without event trigger support.`,\n );\n }\n}\n\n// TODO(0xcadams): should this be a ProtocolError?\nclass ShutdownSignal extends AbortError {\n readonly name = 'ShutdownSignal';\n\n constructor(cause: unknown) {\n super(\n 'shutdown signal received (e.g. another zero-cache taking over the replication stream)',\n {\n cause,\n },\n );\n }\n}\n\nfunction parseLogicalMessageContent<T>(\n lc: LogContext,\n msg: MessageMessage,\n schema: v.Type<T>,\n) {\n const {content} = msg;\n const str =\n content instanceof Buffer\n ? content.toString('utf-8')\n : new TextDecoder().decode(content);\n try {\n const json = JSON.parse(str);\n return v.parse(json, schema, 'passthrough');\n } catch (e) {\n lc.error?.(`unable to parse logical message content: ${String(e)}`, {\n message: {...msg, content: str},\n });\n throw e;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"change-source.js","names":["#lc","#db","#upstreamUri","#shard","#replica","#slotCleanupMonitor","#backupOptions","#context","#lagReporter","#textCopy","#streamInboundTimeoutMs","#minStartWatermark","#subscribe","#streamBackfill","#stopped","#takeoverReplicationSlot","#checkConfirmedFlushLSN","#logCurrentReplicaInfo","#acks","#waitingForDownstreamAck","#expectDownstreamAck","#ackIfDownstreamIsCaughtUp","#sendAck","#reportIntervalMs","#reportIntervalJitterMs","#lagReportRetries","#pgVersion","#expectingLagReport","#scheduleMissingReportRetry","#processLagReport","#timer","#lastAcceptedSendTimeMs","#scheduleNextReportWithJitter","#shardPrefix","#shardConfig","#skippedIndexWarnings","#error","#logError","#makeChanges","#invalidateReplica","#replicaInvalidated","#handleDdlMessage","#lastReplicationEvent","#handleRelation","#replicaIdentityTimer","#makeSchemaChanges","#getTableChanges"],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/change-source.ts"],"sourcesContent":["import {\n PG_ADMIN_SHUTDOWN,\n PG_INSUFFICIENT_PRIVILEGE,\n} from '@drdgvhbh/postgres-error-codes';\nimport type {LogContext} from '@rocicorp/logger';\nimport {nanoid} from 'nanoid';\nimport postgres from 'postgres';\nimport {AbortError} from '../../../../../shared/src/abort-error.ts';\nimport {assert} from '../../../../../shared/src/asserts.ts';\nimport {stringify} from '../../../../../shared/src/bigint-json.ts';\nimport {deepEqual} from '../../../../../shared/src/json.ts';\nimport {must} from '../../../../../shared/src/must.ts';\nimport {mapValues} from '../../../../../shared/src/objects.ts';\nimport {\n equals,\n intersection,\n symmetricDifferences,\n} from '../../../../../shared/src/set-utils.ts';\nimport * as v from '../../../../../shared/src/valita.ts';\nimport {mapIndexPredicateColumns} from '../../../db/index-predicate.ts';\nimport {\n mapPostgresToLiteColumn,\n UnsupportedColumnDefaultError,\n} from '../../../db/pg-to-lite.ts';\nimport type {\n ColumnSpec,\n PublishedIndexSpec,\n PublishedTableSpec,\n} from '../../../db/specs.ts';\nimport {getOrCreateCounter} from '../../../observability/metrics.ts';\nimport {type LexiVersion} from '../../../types/lexi-version.ts';\nimport {PG_17} from '../../../types/pg-versions.ts';\nimport {isPostgresError, pgClient, type PostgresDB} from '../../../types/pg.ts';\nimport {upstreamSchema, type ShardID} from '../../../types/shards.ts';\nimport {\n majorVersionFromString,\n majorVersionToString,\n} from '../../../types/state-version.ts';\nimport type {Sink} from '../../../types/streams.ts';\nimport {UnrecoverableError} from '../../running-state.ts';\nimport type {ChangeSource, ChangeStream} from '../change-source.ts';\nimport {BackfillManager} from '../common/backfill-manager.ts';\nimport {\n ChangeStreamMultiplexer,\n type Listener,\n} from '../common/change-stream-multiplexer.ts';\nimport type {\n BackfillRequest,\n DownstreamStatusMessage,\n JSONObject,\n} from '../protocol/current.ts';\nimport type {\n ColumnAdd,\n Identifier,\n MessageRelation,\n SchemaChange,\n TableCreate,\n} from '../protocol/current/data.ts';\nimport type {\n ChangeStreamData,\n ChangeStreamMessage,\n Data,\n} from '../protocol/current/downstream.ts';\nimport type {ColumnMetadata, TableMetadata} from './backfill-metadata.ts';\nimport {streamBackfill} from './backfill-stream.ts';\nimport {type ServerContext} from './initial-sync.ts';\nimport type {\n Message,\n MessageMessage,\n MessageRelation as PostgresRelation,\n} from './logical-replication/pgoutput.types.ts';\nimport {subscribe, type StreamMessage} from './logical-replication/stream.ts';\nimport {fromBigInt, toBigInt, toStateVersionString, type LSN} from './lsn.ts';\nimport {ReplicationSlotCleanupMonitor} from './replication-slot-cleanup-monitor.ts';\nimport {registerReplicationSlotHealthMetrics} from './replication-slot-health.ts';\nimport {replicationEventSchema, type ReplicationEvent} from './schema/ddl.ts';\nimport {\n getPublicationInfo,\n warnForSkippedIndexes,\n type PublishedSchema,\n type PublishedTableWithReplicaIdentity,\n} from './schema/published.ts';\nimport {Replicate} from './schema/replica-stage-enum.ts';\nimport {\n getInternalShardConfig,\n getReplicaAtVersion,\n replicaIdentitiesForTablesWithoutPrimaryKeys,\n replicationSlotPrefix,\n type BackupOptions,\n type InternalShardConfig,\n type Replica,\n} from './schema/shard.ts';\nimport {validate} from './schema/validation.ts';\n\n// Parameterize this if necessary. In practice starvation may never happen.\nconst MAX_LOW_PRIORITY_DELAY_MS = 1000;\n\ntype ReservationState = {\n lastWatermark?: string;\n};\n\n/**\n * Postgres implementation of a {@link ChangeSource} backed by a logical\n * replication stream.\n */\nexport class PostgresChangeSource implements ChangeSource {\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #upstreamUri: string;\n readonly #shard: ShardID;\n readonly #replica: Replica;\n readonly #slotCleanupMonitor: ReplicationSlotCleanupMonitor;\n readonly #backupOptions: BackupOptions;\n readonly #context: ServerContext;\n readonly #lagReporter: LagReporter | null;\n readonly #textCopy: boolean;\n readonly #streamInboundTimeoutMs: number | undefined;\n readonly #minStartWatermark: string | undefined;\n readonly #subscribe: typeof subscribe;\n readonly #streamBackfill: typeof streamBackfill;\n #stopped = false;\n\n constructor(\n lc: LogContext,\n upstreamUri: string,\n shard: ShardID,\n replica: Replica,\n pgVersion: number,\n backupOptions: BackupOptions,\n context: ServerContext,\n lagReportIntervalMs: number,\n textCopy?: boolean | undefined,\n streamInboundTimeoutMs?: number | undefined,\n /**\n * The watermark below which a stream cannot be started without skipping\n * transactions, as Postgres would silently start from the slot's position\n * instead (see `minStartWatermark()` in change-source-init.ts).\n */\n minStartWatermark?: string | undefined,\n // Injectable dependencies, overridable in tests. Default to the production\n // implementations.\n deps: {\n subscribe?: typeof subscribe;\n streamBackfill?: typeof streamBackfill;\n } = {},\n ) {\n this.#lc = lc.withContext('component', 'change-source');\n this.#subscribe = deps.subscribe ?? subscribe;\n this.#streamBackfill = deps.streamBackfill ?? streamBackfill;\n // used for schema changes, lag reporting, and slot cleanup\n this.#db = pgClient(lc, upstreamUri, 'replication-monitor', {\n max: 3,\n idle_timeout: 60,\n });\n this.#upstreamUri = upstreamUri;\n this.#shard = shard;\n this.#replica = replica;\n this.#slotCleanupMonitor = new ReplicationSlotCleanupMonitor(\n lc.withContext('component', 'replication-slot-monitor'),\n shard,\n this.#db,\n pgVersion,\n replica.slot,\n );\n this.#backupOptions = backupOptions;\n this.#context = context;\n this.#textCopy = textCopy ?? false;\n this.#streamInboundTimeoutMs = streamInboundTimeoutMs;\n this.#minStartWatermark = minStartWatermark;\n this.#lagReporter =\n lagReportIntervalMs > 0\n ? new LagReporter(\n lc.withContext('component', 'lag-reporter'),\n shard,\n this.#db,\n pgVersion,\n lagReportIntervalMs,\n )\n : null;\n registerReplicationSlotHealthMetrics(\n this.#lc,\n this.#db,\n replica.slot,\n () => this.#stopped,\n );\n }\n\n async stop(): Promise<void> {\n this.#stopped = true;\n this.#lagReporter?.stop();\n await this.#db.end();\n }\n\n async startLagReporter() {\n if (this.#lagReporter) {\n try {\n return await this.#lagReporter.initiateLagReport(true);\n } catch (e) {\n if (isPostgresError(e, PG_INSUFFICIENT_PRIVILEGE)) {\n const functionName =\n (this.#lagReporter.pgVersion ?? 0) >= PG_17\n ? 'pg_logical_emit_message(boolean, text, text, boolean)'\n : 'pg_logical_emit_message(boolean, text, text)';\n this.#lc.warn?.(\n `\\n\\nUnable to initiate replication lag reports due to insufficient privileges.` +\n `\\nTo enable replication lag reporting, run:`,\n `\\n\\tGRANT EXECUTE ON FUNCTION ${functionName} TO <your_db_user>;\\n\\n`,\n e,\n );\n } else {\n this.#lc.error?.(\n `Unexpected error while initiating lag reports. Lag reports will be disabled.`,\n e,\n );\n }\n }\n }\n return null;\n }\n\n async startStream(\n clientWatermark: string,\n backfillRequests: BackfillRequest[] = [],\n ): Promise<ChangeStream> {\n await this.#takeoverReplicationSlot();\n const config = await getInternalShardConfig(this.#db, this.#shard);\n const {slot} = this.#replica;\n this.#lc.info?.(`starting replication stream@${slot}`);\n await this.#checkConfirmedFlushLSN(slot, clientWatermark);\n const changeStream = await this.startStreamInternal(\n slot,\n clientWatermark,\n config,\n backfillRequests,\n );\n const {signal} = changeStream.changes;\n if (!signal.aborted) {\n // The slot cleanup monitor runs when a replication slot is active.\n // If no slots are active, they are all available for claiming by a\n // replication-manager; inactive slots are only cleaned up when at\n // least one stream is being processed.\n this.#slotCleanupMonitor.start();\n signal.addEventListener('abort', () => this.#slotCleanupMonitor.stop());\n }\n return changeStream;\n }\n\n // Exported for testing.\n async startStreamInternal(\n slot: string,\n clientWatermark: string,\n shardConfig: InternalShardConfig,\n backfillRequests: BackfillRequest[],\n ): Promise<ChangeStream> {\n if (\n this.#minStartWatermark !== undefined &&\n majorVersionFromString(clientWatermark) <\n majorVersionFromString(this.#minStartWatermark)\n ) {\n // Retrying would resume from the same watermark, so fail the service\n // (rather than backing off). Nothing has been acked, so the slot is\n // left intact.\n throw new UnrecoverableError(\n `cannot start replication stream@${slot} from ${clientWatermark}, ` +\n `which is before its minimum start watermark ` +\n `${this.#minStartWatermark}: transactions in between would be ` +\n `silently skipped`,\n );\n }\n const clientStart = majorVersionFromString(clientWatermark) + 1n;\n const {messages, acks} = await this.#subscribe(\n this.#lc,\n this.#db,\n slot,\n [...shardConfig.publications],\n clientStart,\n undefined,\n undefined,\n this.#streamInboundTimeoutMs,\n );\n const acker = new Acker(acks, clientWatermark);\n\n // The ChangeStreamMultiplexer facilitates cooperative streaming from\n // the main replication stream and backfill streams initiated by the\n // BackfillManager.\n const changes = new ChangeStreamMultiplexer(this.#lc, clientWatermark);\n const backfillManager = new BackfillManager(this.#lc, changes, req =>\n this.#streamBackfill(this.#lc, this.#upstreamUri, this.#replica, req, {\n textCopy: this.#textCopy,\n }),\n );\n changes\n .addProducers(messages, backfillManager)\n .addListeners(backfillManager, acker);\n backfillManager.run(clientWatermark, backfillRequests);\n\n const changeMaker = new ChangeMaker(\n this.#shard,\n shardConfig,\n this.#db,\n this.#replica,\n );\n\n /**\n * Determines if the incoming message is transactional, otherwise handling\n * non-transactional messages with a downstream status message.\n */\n const isTransactionalMessage = (\n lsn: bigint,\n msg: StreamMessage[1],\n ): msg is Message => {\n if (\n msg.tag === 'message' &&\n msg.prefix === this.#lagReporter?.messagePrefix\n ) {\n changes.pushStatus(this.#lagReporter.processLagReportMessage(msg, lsn));\n return false;\n }\n // Checks if we are passed the LSN of the expected lag report, in which\n // case a new one is initiated.\n const status = this.#lagReporter?.checkCurrentLSN(lsn);\n if (status) {\n changes.pushStatus(status);\n }\n\n if (msg.tag === 'keepalive') {\n changes.pushStatus([\n 'status',\n {ack: msg.shouldRespond},\n {watermark: majorVersionToString(lsn)},\n ]);\n return false;\n }\n\n // The only non-transaction \"message\" we process is our own lagReporter's\n // message (handled above). Non-transactional messages from other shards\n // (e.g. lag reports), should be ignored but properly classified as\n // non-transactional, so as not to incorrectly request a stream\n // reservation.\n if (msg.tag === 'message' && !msg.transactional) {\n this.#lc.debug?.(\n 'ignoring non-transactional message for different shard',\n msg.prefix,\n );\n return false;\n }\n\n return true;\n };\n\n void (async () => {\n try {\n let reservation: ReservationState | null = null;\n let inTransaction = false;\n\n for await (const [lsn, msg] of messages) {\n if (!isTransactionalMessage(lsn, msg)) {\n // If we're not in a transaction but the last reservation was kept\n // because of pending keepalives or lag reports in the queue,\n // release the reservation.\n if (!inTransaction && reservation?.lastWatermark) {\n changes.release(reservation.lastWatermark);\n reservation = null;\n }\n continue;\n }\n\n if (!reservation) {\n const res = changes.reserve('replication');\n const lastWatermark = typeof res === 'string' ? res : await res;\n reservation = {lastWatermark};\n }\n\n let lastChange: ChangeStreamMessage | undefined;\n for (const change of await changeMaker.makeChanges(\n this.#lc.withContext('lsn', fromBigInt(lsn)),\n lsn,\n msg,\n )) {\n await changes.push(change); // Allow the change-streamer to push back.\n lastChange = change;\n }\n\n switch (lastChange?.[0]) {\n case 'begin':\n inTransaction = true;\n break;\n case 'commit':\n inTransaction = false;\n reservation.lastWatermark = lastChange[2].watermark;\n if (\n messages.queued === 0 ||\n changes.waiterDelay() > MAX_LOW_PRIORITY_DELAY_MS\n ) {\n // After each transaction, release the reservation:\n // - if there are no pending upstream messages\n // - or if a low priority request has been waiting for longer\n // than MAX_LOW_PRIORITY_DELAY_MS. This is to prevent\n // (backfill) starvation on very active upstreams.\n changes.release(reservation.lastWatermark);\n reservation = null;\n }\n break;\n }\n }\n } catch (e) {\n // Note: no need to worry about reservations here since downstream\n // is being completely canceled.\n const err = translateError(e);\n if (err instanceof ShutdownSignal) {\n // Log the new state of the replica to surface information about the\n // server that sent the shutdown signal, if any.\n await this.#logCurrentReplicaInfo();\n }\n changes.fail(err);\n }\n })();\n\n this.#lc.info?.(\n `started replication stream@${slot} from ${clientWatermark} (replicaVersion: ${\n this.#replica.generation\n })`,\n );\n\n return {\n changes: changes.asSource(),\n acks: {push: status => acker.ack(status[2].watermark)},\n };\n }\n\n /**\n * Warns if the stream is about to start from before the slot's\n * `confirmed_flush_lsn`, from which Postgres starts streaming instead.\n *\n * This is benign if the slot was only acked past `clientWatermark` for\n * keepalives (i.e. with no transactions in between), which the\n * {@link Acker} does on a caught-up stream. It is therefore not an error\n * (`minStartWatermark` is the bound that is), but it is the evidence needed\n * to diagnose a skipped interval.\n */\n async #checkConfirmedFlushLSN(slot: string, clientWatermark: string) {\n try {\n const [row] = await this.#db<{confirmedFlushLSN: LSN | null}[]>`\n SELECT confirmed_flush_lsn AS \"confirmedFlushLSN\"\n FROM pg_replication_slots WHERE slot_name = ${slot}`;\n const confirmed = row?.confirmedFlushLSN;\n if (!confirmed) {\n return;\n }\n const start = majorVersionFromString(clientWatermark);\n const confirmedLSN = toBigInt(confirmed);\n if (start < confirmedLSN) {\n this.#lc.warn?.(\n `starting replication stream@${slot} from ${clientWatermark}, ` +\n `which is ${confirmedLSN - start} bytes before the slot's ` +\n `confirmed_flush_lsn ${confirmed} ` +\n `(${toStateVersionString(confirmed)}). Postgres starts from the ` +\n `latter, which skips nothing only if the slot was acked past ` +\n `${clientWatermark} for keepalives.`,\n );\n } else {\n this.#lc.info?.(\n `replication slot ${slot} confirmed_flush_lsn: ${confirmed} ` +\n `(${toStateVersionString(confirmed)})`,\n );\n }\n } catch (e) {\n this.#lc.warn?.(`unable to read the position of slot ${slot}`, e);\n }\n }\n\n async #logCurrentReplicaInfo() {\n try {\n const replica = await getReplicaAtVersion(\n this.#lc,\n this.#db,\n this.#shard,\n this.#replica.generation,\n this.#replica.id,\n );\n if (replica) {\n this.#lc.info?.(\n `Shutdown signal from replica@${this.#replica.generation}: ${stringify(replica.subscriberContext)}`,\n );\n }\n } catch (e) {\n this.#lc.warn?.(`error logging replica info`, e);\n }\n }\n\n /**\n * In RMv1, a single replication slot is taken over by the next\n * replication-manager, signaling the old one to shut down.\n *\n * With litestream v5, the two replication-managers must replicate to unique\n * backupPaths, as is required by the LTX backup schema.\n *\n * Thus, in addition to taking over the replication slot, the\n * replication-manager also updates the `replicas` table with its\n * `backupPath` so that future RM's restore from that path instead\n * of the vestigial path of the previous replication-manager.\n *\n * In RMv2, each replication-manager will have its own slot and\n * row in the `replicas` table, so this \"takeover\" will be unnecessary\n * (but a harmless no-op).\n */\n async #takeoverReplicationSlot() {\n const sql = this.#db;\n const {id: replicaID, slot} = this.#replica;\n const replicasTable = `${upstreamSchema(this.#shard)}.replicas`;\n\n // Before terminating (and taking over) the replication slot, transition\n // the stage from InitialSync to Replicate, so that a replica in the\n // InitialSync stage is never intentionally inactive. This allows the\n // restore logic to safely exclude (actually) orphaned initial syncs.\n await sql`\n UPDATE ${sql(replicasTable)} \n SET \"stage\" = ${Replicate},\n \"subscriberContext\" = ${this.#context},\n \"backupPath\" = ${this.#backupOptions.backupPath},\n \"backupV5\" = ${this.#backupOptions.backupV5},\n \"pgChangeLog\" = ${this.#backupOptions.pgChangeLog}\n WHERE id = ${replicaID}`;\n\n const result = await sql`\n SELECT pg_terminate_backend(active_pid) as terminated, active_pid as pid\n FROM pg_replication_slots\n WHERE slot_name = ${slot}\n `;\n if (result.length === 0) {\n const slotExpression = replicationSlotPrefix(this.#shard);\n const replicas = await sql`\n SELECT id, rank, slot, generation, \"initialSyncContext\", \"subscriberContext\" \n FROM ${sql(replicasTable)} ORDER BY rank DESC`;\n const slots = await sql`\n SELECT slot_name as slot, active, active_pid as pid\n FROM pg_replication_slots\n WHERE slot_name LIKE ${slotExpression}\n ORDER BY slot_name`;\n this.#lc.warn?.(`slot ${slot} not found while cleaning subscribers`, {\n slots,\n replicas,\n });\n throw new AbortError(\n `replication slot ${slot} is missing. A different ` +\n `replication-manager should now be running on a new ` +\n `replication slot.`,\n );\n }\n this.#lc.info?.(`terminated replication slots: ${JSON.stringify(result)}`);\n }\n}\n\n// Exported for testing.\nexport class Acker implements Listener {\n #acks: Sink<bigint>;\n #waitingForDownstreamAck: string | null;\n\n /**\n * @param resumeWatermark the watermark the stream resumes after. What came\n * before it was received on an earlier connection, and only the\n * change-streamer knows whether it has persisted it, so keepalives are\n * not acked until the change-streamer has acked it. A change-streamer\n * whose SQLite change log is ahead of its backup would otherwise have\n * the slot moved past transactions that only that log holds.\n */\n constructor(acks: Sink<bigint>, resumeWatermark: string | null) {\n this.#acks = acks;\n this.#waitingForDownstreamAck = resumeWatermark;\n }\n\n onChange(change: ChangeStreamMessage): void {\n switch (change[0]) {\n case 'status':\n const {watermark} = change[2];\n if (change[1].ack) {\n this.#expectDownstreamAck(watermark);\n } else {\n // Keepalives with shouldRespond = false are not acked by the\n // change-streamer. Ack them here if the change-streamer is caught\n // up. This updates the replication slot's `confirmed_flush_lsn`\n // more quickly (rather than waiting for the periodic shouldRespond),\n // which is useful for monitoring replication slot lag.\n this.#ackIfDownstreamIsCaughtUp(watermark);\n }\n break;\n case 'begin':\n // Mark the commit watermark as being expected so that any intermediate\n // shouldRespond=false watermarks, which will be at the\n // commitWatermark, are *not* acked, as the ack must come from\n // change-streamer after it commits the transaction.\n if (!change[1].skipAck) {\n this.#expectDownstreamAck(change[2].commitWatermark);\n }\n break;\n }\n }\n\n #expectDownstreamAck(watermark: string) {\n this.#waitingForDownstreamAck = watermark;\n }\n\n ack(watermark: LexiVersion) {\n if (\n this.#waitingForDownstreamAck &&\n this.#waitingForDownstreamAck <= watermark\n ) {\n this.#waitingForDownstreamAck = null;\n }\n this.#sendAck(watermark);\n }\n\n #ackIfDownstreamIsCaughtUp(watermark: string) {\n if (this.#waitingForDownstreamAck === null) {\n this.#sendAck(watermark);\n }\n }\n\n #sendAck(watermark: LexiVersion) {\n const lsn = majorVersionFromString(watermark);\n this.#acks.push(lsn);\n }\n}\n\nconst lagReportSchema = v.object({\n id: v.string(),\n sendTimeMs: v.number(),\n commitTimeMs: v.number(),\n});\n\nexport type LagReport = v.Infer<typeof lagReportSchema>;\n\ntype InitiatedLagReport = LagReport & {lsn: bigint};\n\nexport class LagReporter {\n static readonly MESSAGE_SUFFIX = '/lag-report/v1';\n\n readonly #lc: LogContext;\n readonly messagePrefix: string;\n\n // oxlint-disable-next-line no-unused-private-class-members\n readonly #db: PostgresDB;\n readonly #reportIntervalMs: number;\n readonly #reportIntervalJitterMs: number;\n readonly #lagReportRetries = getOrCreateCounter(\n 'replication',\n 'lag_report_retries',\n {\n description:\n 'Number of replication lag reports retried because the expected ' +\n 'report was not received before the next report interval.',\n unit: '{report}',\n },\n );\n\n readonly #pgVersion: number;\n #expectingLagReport: InitiatedLagReport | null = null;\n // The sendTimeMs of the latest report from which the next report was\n // scheduled.\n #lastAcceptedSendTimeMs = 0;\n #timer: NodeJS.Timeout | undefined;\n\n constructor(\n lc: LogContext,\n shard: ShardID,\n db: PostgresDB,\n pgVersion: number,\n reportIntervalMs: number,\n reportIntervalJitterMs: number = 2000, // +/- 1 second\n ) {\n this.#lc = lc;\n this.messagePrefix = `${shard.appID}/${shard.shardNum}${LagReporter.MESSAGE_SUFFIX}`;\n this.#db = db;\n this.#pgVersion = pgVersion;\n this.#reportIntervalMs = reportIntervalMs;\n this.#reportIntervalJitterMs = reportIntervalJitterMs;\n }\n\n get pgVersion() {\n return this.#pgVersion;\n }\n\n async initiateLagReport(log = false) {\n const now = Date.now();\n const id = nanoid();\n\n // lsn is filled in after the db call.\n const lagReport = {id, sendTimeMs: now, commitTimeMs: now, lsn: 0n};\n this.#expectingLagReport = lagReport;\n\n let commitTimeMs: number;\n let lsn: string;\n\n try {\n if (this.#pgVersion >= PG_17) {\n [{commitTimeMs, lsn}] = await this.#db /*sql*/ `\n WITH CTE AS (SELECT extract(epoch from now()) * 1000 AS \"commitTimeMs\")\n SELECT \"commitTimeMs\", pg_logical_emit_message(\n false,\n ${this.messagePrefix},\n json_build_object(\n 'id', ${id}::text,\n 'sendTimeMs', ${now}::int8,\n 'commitTimeMs', \"commitTimeMs\"\n )::text,\n true\n ) as lsn FROM CTE;\n `;\n } else {\n // Versions before PG 17 do not support the final `flush` option of\n // pg_logical_emit_message(). This results in an extra 50~100ms latency\n // for replication reports when the db is idle, which is still\n // acceptable for the purpose for alerting on pathological lag, for\n // which the threshold is much higher (e.g. many seconds).\n [{commitTimeMs, lsn}] = await this.#db /*sql*/ `\n WITH CTE AS (SELECT extract(epoch from now()) * 1000 as \"commitTimeMs\")\n SELECT \"commitTimeMs\", pg_logical_emit_message(\n false,\n ${this.messagePrefix},\n json_build_object(\n 'id', ${id}::text,\n 'sendTimeMs', ${now}::int8,\n 'commitTimeMs', \"commitTimeMs\"\n )::text\n ) as lsn FROM CTE;\n `;\n // The LSN returned by pg_logical_emit_message() is not necessarily\n // comparable to the LSNs of the replication stream (e.g. on Aurora,\n // it is in the cluster volume's separate logical WAL stream). The\n // WAL insert position, read after the message is emitted, is in the\n // stream's LSN space and is at or beyond the end of the message,\n // which is what checkCurrentLSN() requires.\n [{lsn}] = await this.#db /*sql*/ `\n SELECT pg_current_wal_insert_lsn() AS lsn`;\n }\n } catch (e) {\n if (this.#expectingLagReport?.id === id) {\n this.#expectingLagReport = null;\n }\n throw e;\n }\n\n // Note: We don't know the lsn until after pg_logical_emit_message()\n // returns, at which point it is possible that the report has\n // already been sent through the replication stream, but this\n // is okay since this.#expectingLagReport will have be updated.\n lagReport.lsn = toBigInt(lsn);\n lagReport.commitTimeMs = commitTimeMs;\n if (this.#expectingLagReport?.id === id) {\n this.#scheduleMissingReportRetry(id);\n }\n\n if (log) {\n this.#lc.info?.(`initiated lag report at lsn ${lsn}`, {\n id,\n lsn,\n sendTimeMs: now,\n commitTimeMs,\n });\n }\n return {firstCommitTimeMs: commitTimeMs, nextSendTimeMs: now};\n }\n\n /**\n * In Postgres < 17, the pg_logical_emit_message lacks an immediate \"flush\"\n * option, which can cause messages to be missed when the replication stream\n * starts up:\n *\n * ```\n * * emit message → WAL write (buffered, not flushed)\n * * walsender reads up to current flush LSN\n * * emitted message's LSN is beyond flush LSN → not yet visible\n * * stream feedback/acknowledgment advances slot\n * * WAL eventually flushes → but slot has already moved past it\n * ```\n *\n * This has been seen to happen for the initial `wal_writer_delay` interval\n * of a replication session.\n *\n * To account for this, the last emitted lag report is considered \"received\"\n * if the stream has advanced beyond the WAL insert position read after the\n * report was emitted (see initiateLagReport()).\n *\n * This is unnecessary in PG 17+, where reports are emitted with `flush`.\n */\n checkCurrentLSN(lsn: bigint): DownstreamStatusMessage | undefined {\n if (this.#pgVersion >= PG_17) {\n return undefined;\n }\n if (this.#expectingLagReport?.lsn && lsn > this.#expectingLagReport.lsn) {\n this.#lc.info?.(\n `LSN ${fromBigInt(lsn)} is passed expected lag report ` +\n `${fromBigInt(this.#expectingLagReport.lsn)}. Processing it as received.`,\n );\n return this.#processLagReport(\n this.#expectingLagReport,\n majorVersionToString(lsn),\n );\n }\n return undefined;\n }\n\n stop() {\n clearTimeout(this.#timer);\n this.#timer = undefined;\n this.#expectingLagReport = null;\n this.#lastAcceptedSendTimeMs = 0;\n }\n\n #scheduleNextReportWithJitter(delayMs: number) {\n const jitter =\n Math.floor(Math.random() * this.#reportIntervalJitterMs) -\n this.#reportIntervalJitterMs / 2;\n delayMs = Math.max(0, delayMs + jitter);\n\n this.#expectingLagReport = null;\n clearTimeout(this.#timer);\n this.#timer = setTimeout(async () => {\n this.#timer = undefined;\n try {\n await this.initiateLagReport();\n } catch (e) {\n this.#lc.warn?.(`error initiating lag report`, e);\n this.#scheduleNextReportWithJitter(this.#reportIntervalMs);\n }\n }, delayMs);\n }\n\n #scheduleMissingReportRetry(reportID: string) {\n clearTimeout(this.#timer);\n this.#timer = setTimeout(async () => {\n this.#timer = undefined;\n const missingReport = this.#expectingLagReport;\n if (missingReport?.id !== reportID) {\n return;\n }\n\n this.#lagReportRetries.add(1);\n this.#lc.warn?.(`retrying missing lag report`, {\n id: missingReport.id,\n lsn: fromBigInt(missingReport.lsn),\n sendTimeMs: missingReport.sendTimeMs,\n });\n try {\n await this.initiateLagReport();\n } catch (e) {\n this.#lc.warn?.(`error retrying lag report`, e);\n this.#scheduleNextReportWithJitter(this.#reportIntervalMs);\n }\n }, this.#reportIntervalMs);\n }\n\n /**\n * @param lsn The LSN of the message in the replication stream (i.e. from the\n * XLogData header), rather than the `messageLsn` of the message itself,\n * which is not guaranteed to be comparable to other stream LSNs (e.g. on\n * Aurora). See {@link initiateLagReport}.\n */\n processLagReportMessage(\n msg: MessageMessage,\n lsn: bigint,\n ): DownstreamStatusMessage {\n assert(\n msg.prefix === this.messagePrefix,\n `unexpected message prefix: ${msg.prefix}`,\n );\n const report = parseLogicalMessageContent(this.#lc, msg, lagReportSchema);\n return this.#processLagReport(report, majorVersionToString(lsn));\n }\n\n #processLagReport(\n report: LagReport,\n watermark: string,\n ): DownstreamStatusMessage {\n const now = Date.now();\n const expectedReport = this.#expectingLagReport;\n let nextSendTimeMs: number;\n if (\n expectedReport\n ? // Accept any report whose send time is within the reporting\n // interval of the report that is expected.\n Math.abs(report.sendTimeMs - expectedReport.sendTimeMs) <\n this.#reportIntervalMs\n : // When waiting to send the next report, accept a newer report\n // (i.e. from another replication-manager) as a substitute, and\n // postpone the next report accordingly. This, in addition to\n // scheduling each report with jitter, allows multiple\n // replication-managers to coalesce on a single stream of reports.\n //\n // Note that a report is never initiated from here if one was not\n // already scheduled (e.g. after stop()).\n this.#timer !== undefined &&\n report.sendTimeMs > this.#lastAcceptedSendTimeMs\n ) {\n this.#lastAcceptedSendTimeMs = report.sendTimeMs;\n nextSendTimeMs = Math.max(\n now,\n report.sendTimeMs + this.#reportIntervalMs,\n );\n this.#scheduleNextReportWithJitter(nextSendTimeMs - now);\n } else {\n // Other reports, such as historic reports in the WAL, are still\n // sent downstream, but the next report is not actually scheduled.\n nextSendTimeMs =\n expectedReport?.sendTimeMs ??\n Math.max(now, report.sendTimeMs + this.#reportIntervalMs);\n }\n const {sendTimeMs, commitTimeMs} = report;\n return [\n 'status',\n {\n ack: false,\n lagReport: {\n lastTimings: {\n sendTimeMs,\n commitTimeMs,\n receiveTimeMs: now,\n },\n nextSendTimeMs,\n },\n },\n {watermark},\n ];\n }\n}\n\ntype ReplicationError = {\n lsn: bigint;\n msg: Message;\n err: unknown;\n lastLogTime: number;\n};\n\nconst SET_REPLICA_IDENTITY_DELAY_MS = 50;\n\nclass ChangeMaker {\n readonly #shard: ShardID;\n readonly #shardPrefix: string;\n readonly #shardConfig: InternalShardConfig;\n readonly #replica: Replica;\n readonly #db: PostgresDB;\n\n #replicaIdentityTimer: NodeJS.Timeout | undefined;\n #error: ReplicationError | undefined;\n readonly #skippedIndexWarnings = new Set<string>();\n\n constructor(\n shard: ShardID,\n shardConfig: InternalShardConfig,\n db: PostgresDB,\n replica: Replica,\n ) {\n this.#shard = shard;\n // Note: This matches the prefix used in pg_logical_emit_message() in pg/schema/ddl.ts.\n this.#shardPrefix = `${shard.appID}/${shard.shardNum}`;\n this.#shardConfig = shardConfig;\n this.#replica = replica;\n this.#db = db;\n }\n\n async makeChanges(\n lc: LogContext,\n lsn: bigint,\n msg: Message,\n ): Promise<ChangeStreamMessage[]> {\n if (this.#error) {\n this.#logError(lc, this.#error);\n return [];\n }\n try {\n return await this.#makeChanges(lc, lsn, msg);\n } catch (err) {\n this.#error = {lsn, msg, err, lastLogTime: 0};\n this.#logError(lc, this.#error);\n\n await this.#invalidateReplica(lc, err);\n\n const message = `Unable to continue replication from LSN ${fromBigInt(lsn)}`;\n const errorDetails: JSONObject = {error: message};\n if (err instanceof UnsupportedSchemaChangeError) {\n errorDetails.reason = err.description;\n errorDetails.context = err.event.context;\n } else {\n errorDetails.reason = String(err);\n }\n\n // Rollback the current transaction to avoid dangling transactions in\n // downstream processors (i.e. changeLog, replicator).\n return [\n ['rollback', {tag: 'rollback'}],\n ['control', {tag: 'reset-required', message, errorDetails}],\n ];\n }\n }\n\n #replicaInvalidated = false;\n\n // Deletes this replica's row in the `replicas` table so that it, and its\n // slot, are removed from candidacy for restore. This is a poison pill to\n // initiate an auto-reset (i.e. resync) without relying on a separate\n // global store (e.g. no PG change-log required).\n //\n // Note that there is no need to delete the slot, as this will be taken care\n // of in `dropUnclaimedSlots()` when a new replica is synced, or by the\n // replication-slot-cleanup-monitor of a healthy replica.\n async #invalidateReplica(lc: LogContext, err: unknown) {\n if (this.#replicaInvalidated) {\n return;\n }\n const sql = this.#db;\n const {id: replicaID} = this.#replica;\n const replicasTable = `${upstreamSchema(this.#shard)}.replicas`;\n\n lc.warn?.(`deleting invalid replica: ${this.#replica.id}`, err);\n await sql`\n DELETE FROM ${sql(replicasTable)} WHERE \"id\" = ${replicaID}\n `;\n this.#replicaInvalidated = true;\n }\n\n #logError(lc: LogContext, error: ReplicationError) {\n const {lsn, msg, err, lastLogTime} = error;\n const now = Date.now();\n\n // Output an error to logs as replication messages continue to be dropped,\n // at most once a minute.\n if (now - lastLogTime > 60_000) {\n lc.error?.(\n `Unable to continue replication from LSN ${fromBigInt(lsn)}: ${String(\n err,\n )}`,\n {\n // 'content' can be a large byte Buffer. Exclude it from logging output.\n ...{change: {...msg, content: undefined}},\n ...(err instanceof UnsupportedSchemaChangeError && {\n context: err.event.context,\n }),\n ...(err instanceof Error && {\n errorMsg: err.message,\n name: err.name,\n stack: err.stack,\n }),\n },\n );\n error.lastLogTime = now;\n }\n }\n\n async #makeChanges(\n lc: LogContext,\n lsn: bigint,\n msg: Message,\n ): Promise<ChangeStreamData[]> {\n switch (msg.tag) {\n case 'begin':\n return [\n [\n 'begin',\n {...msg, json: 's'},\n {commitWatermark: toStateVersionString(must(msg.commitLsn))},\n ],\n ];\n\n case 'delete': {\n if (!(msg.key ?? msg.old)) {\n throw new Error(\n `Invalid DELETE msg (missing key): ${stringify(msg)}`,\n );\n }\n return [\n [\n 'data',\n {\n ...msg,\n relation: makeRelation(msg.relation),\n // https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-DELETE\n key: must(msg.old ?? msg.key),\n },\n ],\n ];\n }\n\n case 'update': {\n return [\n [\n 'data',\n {\n ...msg,\n relation: makeRelation(msg.relation),\n // https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-UPDATE\n key: msg.old ?? msg.key,\n },\n ],\n ];\n }\n\n case 'insert':\n return [['data', {...msg, relation: makeRelation(msg.relation)}]];\n case 'truncate':\n return [['data', {...msg, relations: msg.relations.map(makeRelation)}]];\n\n case 'message':\n if (!msg.prefix.startsWith(this.#shardPrefix)) {\n lc.debug?.('ignoring message for different shard', msg.prefix);\n return [];\n }\n switch (msg.prefix.substring(this.#shardPrefix.length)) {\n case '': // Legacy prefix\n case '/ddl':\n return this.#handleDdlMessage(lc, lsn, msg);\n default:\n lc.debug?.('ignoring unknown message type', msg.prefix);\n return [];\n }\n\n case 'commit':\n // The DDL event that provides command-tag context for a subsequent\n // event (see #handleDdlMessage) is only meaningful within a single\n // (upstream) transaction, since related ddl events (e.g. the nested\n // start->start->end->end sequence) are always emitted together. Clear\n // it at the transaction boundary so that a lingering event (e.g. a\n // `CREATE TABLE` ddlStart) is not misattributed as the context for an\n // unrelated event in a later transaction. Note that this is safe for\n // the one type of DDL event that spans multiple transactions,\n // `CREATE INDEX CONCURRENTLY`, because that change is self declaring\n // in the `ddlUpdate` and does not depend on the value of the preceding\n // event.\n this.#lastReplicationEvent = undefined;\n return [\n [\n 'commit',\n // `commitTime` is microseconds since the unix epoch. Carrying it\n // as milliseconds gives the ViewSyncer the origin timestamp for\n // the end-to-end serving lag histogram.\n {...msg, commitTimeMs: Number(msg.commitTime / 1000n)},\n {watermark: toStateVersionString(must(msg.commitLsn))},\n ],\n ];\n\n case 'relation':\n return await this.#handleRelation(msg);\n case 'type':\n return []; // Nothing need be done for custom types.\n case 'origin':\n // No need to detect replication loops since we are not a\n // PG replication source.\n return [];\n default:\n msg satisfies never;\n throw new Error(`Unexpected message type ${stringify(msg)}`);\n }\n }\n\n // The lastReplicationEvent is stored to understand the context\n // of the next one.\n #lastReplicationEvent: ReplicationEvent | undefined;\n\n #handleDdlMessage(\n lc: LogContext,\n lsn: bigint,\n msg: MessageMessage,\n ): ChangeStreamData[] {\n const event = parseLogicalMessageContent(lc, msg, replicationEventSchema);\n lc = lc\n .withContext('lsn', fromBigInt(lsn))\n .withContext('tag', event.event.tag)\n .withContext('query', event.context.query);\n\n if (event.previousSchema) {\n warnForSkippedIndexes(\n lc,\n event.previousSchema,\n this.#skippedIndexWarnings,\n );\n }\n if (event.schema) {\n warnForSkippedIndexes(lc, event.schema, this.#skippedIndexWarnings);\n }\n\n // Cancel manual schema adjustment timeouts when an upstream schema change\n // is about to happen, so as to avoid interfering / redundant work.\n clearTimeout(this.#replicaIdentityTimer);\n\n const {type} = event;\n switch (type) {\n case 'ddlStart':\n case 'schemaSnapshot':\n case 'ddlUpdate':\n break;\n default: // Ignore unknown types for forwards compatibility\n lc.info?.(`ignoring unknown ddl message type: ${type}`);\n return [];\n }\n\n const prevEvent = this.#lastReplicationEvent;\n // Store the new event to understand the context of the next event.\n this.#lastReplicationEvent = event;\n\n const {schema} = event;\n if (schema === undefined) {\n // A schema-less (protocol v2) ddlStart event signifies that there was\n // no schema change; it is stored (above) only to provide the command\n // tag context for a subsequent event with a schema change.\n lc.debug?.(`received context-only ${msg.prefix}/${type} event`, {\n event: summarizeReplicationEventForLog(event),\n });\n return [];\n }\n\n const prevSchema =\n event.previousSchema === undefined // pre-v21 event => use prevEvent\n ? prevEvent?.schema\n : event.previousSchema;\n if (!prevSchema) {\n lc.info?.(`received ${msg.prefix}/${type} event`, {\n event: summarizeReplicationEventForLog(event),\n });\n return [];\n }\n\n // The tag (i.e. command) is used as an optimization to determine whether\n // backfill is necessary (CREATE TABLE vs ALTER TABLE vs\n // ALTER PUBLICATION). If the context is not available (rare), the tag\n // falls back to 'UNKNOWN', which conservatively initiates a backfill.\n //\n // Because ddl events may be nested, e.g.\n //\n // ```\n // [1] [2] [3] [4] [5]\n // ddl_start => ddl_start => ddl_end => ddl_start => ddl_end => ddl_end\n // ```\n //\n // The effective tag is determined from the previous event if it is\n // ddl_start (e.g. cases 1, 2, and 4), and from the current event\n // if it is a ddl_end (case 5), and 'UNKNOWN' otherwise (case 3 and\n // 'schemaSnapshot' workarounds).\n //\n // A 'schemaSnapshot' is special: it is a standalone hook (emitted by the\n // COMMENT ON PUBLICATION workaround, or a MANUAL update_schemas() call)\n // that substitutes for a missing ALTER PUBLICATION event on databases\n // (e.g. supabase) that do not fire event triggers for it. It must never\n // adopt the command tag of a preceding ddlStart (e.g. a `CREATE TABLE`\n // in the same transaction), as that would cause a newly *published*\n // table to be misclassified as a freshly *created* one and skip the\n // backfill of its pre-existing rows. It therefore always falls back to\n // 'UNKNOWN', which conservatively initiates a backfill.\n const effectiveTag =\n event.type === 'schemaSnapshot'\n ? 'UNKNOWN'\n : prevEvent?.type === 'ddlStart'\n ? prevEvent.event.tag\n : event.type === 'ddlUpdate'\n ? event.event.tag\n : 'UNKNOWN';\n lc.info?.(`processing ${effectiveTag} command from ${msg.prefix}/${type}`, {\n event: summarizeReplicationEventForLog(event),\n });\n const changes = this.#makeSchemaChanges(\n lc,\n prevSchema,\n schema,\n event,\n effectiveTag,\n ).map(change => ['data', change] satisfies Data);\n\n lc.info?.(`${changes.length} schema change(s)`, {changes});\n\n const replicaIdentities =\n replicaIdentitiesForTablesWithoutPrimaryKeys(schema);\n if (replicaIdentities) {\n this.#replicaIdentityTimer = setTimeout(async () => {\n try {\n await replicaIdentities.apply(lc, this.#db);\n } catch (err) {\n lc.warn?.(`error setting replica identities`, err);\n }\n }, SET_REPLICA_IDENTITY_DELAY_MS);\n }\n\n return changes;\n }\n\n /**\n * A note on operation order:\n *\n * Postgres will drop related indexes when columns are dropped,\n * but SQLite will error instead (https://sqlite.org/forum/forumpost/2e62dba69f?t=c&hist).\n * The current workaround is to drop indexes first.\n *\n * Also note that although it should not be possible to both rename and\n * add/drop tables/columns in a single statement, the operations are\n * ordered to handle that possibility, by always dropping old entities,\n * then modifying kept entities, and then adding new entities.\n *\n * Thus, the order of replicating DDL updates is:\n * - drop indexes\n * - drop tables\n * - alter tables\n * - drop columns\n * - alter columns\n * - add columns\n * - create tables\n * - create indexes\n *\n * In the future the replication logic should be improved to handle this\n * behavior in SQLite by dropping dependent indexes manually before dropping\n * columns. This, for example, would be needed to properly support changing\n * the type of a column that's indexed.\n */\n #makeSchemaChanges(\n lc: LogContext,\n preSchema: PublishedSchema,\n nextSchema: PublishedSchema,\n event: ReplicationEvent,\n tag: string,\n ): SchemaChange[] {\n try {\n const [prevTbl, prevIdx] = specsByID(preSchema);\n const [nextTbl, nextIdx] = specsByID(nextSchema);\n const changes: SchemaChange[] = [];\n\n // Validate the new table schemas\n for (const table of nextTbl.values()) {\n validate(lc, table);\n }\n\n const [droppedIdx, createdIdx] = symmetricDifferences(prevIdx, nextIdx);\n\n // Detect modified indexes (same name, different definition).\n // This happens when a constraint is dropped and recreated with the\n // same name in a single ALTER TABLE statement.\n // Note: We compare using stable column attnums rather than names,\n // because table/column renames change the index spec cosmetically\n // (tableName, column keys) without the index actually being recreated.\n const keptIdx = intersection(prevIdx, nextIdx);\n for (const id of keptIdx) {\n if (\n isIndexStructurallyChanged(\n must(prevIdx.get(id)),\n must(nextIdx.get(id)),\n prevTbl,\n nextTbl,\n )\n ) {\n droppedIdx.add(id);\n createdIdx.add(id);\n }\n }\n\n for (const id of droppedIdx) {\n const {schema, name} = must(prevIdx.get(id));\n changes.push({tag: 'drop-index', id: {schema, name}});\n }\n\n // DROP\n const [droppedTbl, createdTbl] = symmetricDifferences(prevTbl, nextTbl);\n for (const id of droppedTbl) {\n const {schema, name} = must(prevTbl.get(id));\n changes.push({tag: 'drop-table', id: {schema, name}});\n }\n // ALTER TABLE | ALTER PUBLICATION\n const tables = intersection(prevTbl, nextTbl);\n for (const id of tables) {\n changes.push(\n ...this.#getTableChanges(\n lc,\n must(prevTbl.get(id)),\n must(nextTbl.get(id)),\n tag,\n ),\n );\n }\n // CREATE\n for (const id of createdTbl) {\n const spec = must(nextTbl.get(id));\n const createTable: TableCreate = {\n tag: 'create-table',\n spec,\n metadata: getMetadata(spec),\n };\n // Only tables introduced by a `CREATE` statement can skip backfill.\n // All other scenarios in which tables are introduced into the\n // schema, e.g.\n // * ALTER PUBLICATION statements\n // * COMMENT statements\n // * MANUAL snapshots\n // * UNKNOWN command tags\n // must be backfilled.\n if (!tag.startsWith('CREATE')) {\n createTable.backfill = mapValues(spec.columns, ({pos: attNum}) => ({\n attNum,\n })) satisfies Record<string, ColumnMetadata>;\n }\n changes.push(createTable);\n }\n\n // Add indexes last since they may reference tables / columns that need\n // to be created first.\n for (const id of createdIdx) {\n const spec = must(nextIdx.get(id));\n changes.push({tag: 'create-index', spec});\n }\n return changes;\n } catch (e) {\n throw new UnsupportedSchemaChangeError(String(e), event, {cause: e});\n }\n }\n\n #getTableChanges(\n lc: LogContext,\n oldTable: PublishedTableWithReplicaIdentity,\n newTable: PublishedTableWithReplicaIdentity,\n ddlTag: string,\n ): SchemaChange[] {\n const changes: SchemaChange[] = [];\n if (\n oldTable.schema !== newTable.schema ||\n oldTable.name !== newTable.name\n ) {\n changes.push({\n tag: 'rename-table',\n old: {schema: oldTable.schema, name: oldTable.name},\n new: {schema: newTable.schema, name: newTable.name},\n });\n }\n const oldMetadata = getMetadata(oldTable);\n const newMetadata = getMetadata(newTable);\n if (!deepEqual(oldMetadata, newMetadata)) {\n changes.push({\n tag: 'update-table-metadata',\n table: {schema: newTable.schema, name: newTable.name},\n old: oldMetadata,\n new: newMetadata,\n });\n }\n const table = {schema: newTable.schema, name: newTable.name};\n const oldColumns = columnsByID(oldTable.columns);\n const newColumns = columnsByID(newTable.columns);\n\n // DROP\n const [dropped, added] = symmetricDifferences(oldColumns, newColumns);\n for (const id of dropped) {\n const {name: column} = must(oldColumns.get(id));\n changes.push({tag: 'drop-column', table, column});\n }\n\n // ALTER\n const both = intersection(oldColumns, newColumns);\n for (const id of both) {\n const {name: oldName, ...oldSpec} = must(oldColumns.get(id));\n const {name: newName, ...newSpec} = must(newColumns.get(id));\n // The three things that we care about are:\n // 1. name\n // 2. type\n // 3. not-null\n if (\n oldName !== newName ||\n oldSpec.dataType !== newSpec.dataType ||\n oldSpec.notNull !== newSpec.notNull\n ) {\n changes.push({\n tag: 'update-column',\n table,\n old: {name: oldName, spec: oldSpec},\n new: {name: newName, spec: newSpec},\n });\n }\n }\n\n // Only columns introduced by `ALTER TABLE` statements can potentially\n // skip backfill if they have non-constant defaults. All other scenarios\n // in which columns are introduced, e.g.\n // * ALTER PUBLICATION\n // * COMMENT\n // * MANUAL\n // * UNKNOWN\n // must be backfilled.\n const alwaysBackfill = ddlTag !== 'ALTER TABLE';\n\n // ADD\n for (const id of added) {\n const {name, ...spec} = must(newColumns.get(id));\n const column = {name, spec};\n const addColumn: ColumnAdd = {\n tag: 'add-column',\n table,\n column,\n tableMetadata: getMetadata(newTable),\n };\n if (alwaysBackfill) {\n addColumn.column.spec.dflt = null;\n addColumn.backfill = {attNum: spec.pos} satisfies ColumnMetadata;\n } else {\n // Determine if the ChangeProcessor will accept the column add as is.\n try {\n mapPostgresToLiteColumn(table.name, column);\n } catch (e) {\n if (!(e instanceof UnsupportedColumnDefaultError)) {\n // Note: mapPostgresToLiteColumn is not expected to throw any other\n // types of errors.\n throw e;\n }\n // If the column has an unsupported default (e.g. an expression or a\n // generated value), create the column as initially hidden with a\n // `null` default, and publish it after backfilling the values from\n // upstream. Note that this does require that the table have a valid\n // REPLICA IDENTITY, since backfill relies on merging new data with\n // an existing row.\n lc.info?.(`Backfilling column ${table.name}.${name}: ${String(e)}`);\n addColumn.column.spec.dflt = null;\n addColumn.backfill = {attNum: spec.pos} satisfies ColumnMetadata;\n }\n }\n changes.push(addColumn);\n }\n return changes;\n }\n\n /**\n * If `ddlDetection === true`, relation messages are irrelevant,\n * as schema changes are detected by event triggers that\n * emit custom messages.\n *\n * For degraded-mode replication (`ddlDetection === false`):\n * 1. query the current published schemas on upstream\n * 2. compare that with the InternalShardConfig.initialSchema\n * 3. compare that with the incoming MessageRelation\n * 4. On any discrepancy, throw an UnsupportedSchemaChangeError\n * to halt replication.\n *\n * Note that schemas queried in step [1] will be *post-transaction*\n * schemas, which are not necessarily suitable for actually processing\n * the statements in the transaction being replicated. In other words,\n * this mechanism cannot be used to reliably *replicate* schema changes.\n * However, they serve the purpose determining if schemas have changed.\n */\n async #handleRelation(rel: PostgresRelation): Promise<ChangeStreamData[]> {\n const {publications, ddlDetection} = this.#shardConfig;\n if (ddlDetection) {\n return [];\n }\n const currentSchema = await getPublicationInfo(this.#db, publications);\n const difference = getSchemaDifference(\n this.#replica.initialSchema,\n currentSchema,\n );\n if (difference !== null) {\n throw new MissingEventTriggerSupport(difference);\n }\n // Even if the currentSchema is equal to the initialSchema, the\n // MessageRelation itself must be checked to detect transient\n // schema changes within the transaction (e.g. adding and dropping\n // a table, or renaming a column and then renaming it back).\n const orel = this.#replica.initialSchema.tables.find(\n t => t.oid === rel.relationOid,\n );\n if (!orel) {\n // Can happen if a table is created and then dropped in the same transaction.\n throw new MissingEventTriggerSupport(\n `relation not in initialSchema: ${stringify(rel)}`,\n );\n }\n if (relationDifferent(orel, rel)) {\n throw new MissingEventTriggerSupport(\n `relation has changed within the transaction: ${stringify(orel)} vs ${stringify(rel)}`,\n );\n }\n return [];\n }\n}\n\nfunction getSchemaDifference(\n a: PublishedSchema,\n b: PublishedSchema,\n): string | null {\n // Note: ignore indexes since changes need not to halt replication\n if (a.tables.length !== b.tables.length) {\n return `tables created or dropped`;\n }\n for (let i = 0; i < a.tables.length; i++) {\n const at = a.tables[i];\n const bt = b.tables[i];\n const difference = getTableDifference(at, bt);\n if (difference) {\n return difference;\n }\n }\n return null;\n}\n\n// ColumnSpec comparator\nconst byColumnPos = (a: [string, ColumnSpec], b: [string, ColumnSpec]) =>\n a[1].pos < b[1].pos ? -1 : a[1].pos > b[1].pos ? 1 : 0;\n\nfunction getTableDifference(\n a: PublishedTableSpec,\n b: PublishedTableSpec,\n): string | null {\n if (a.oid !== b.oid || a.schema !== b.schema || a.name !== b.name) {\n return `Table \"${a.name}\" differs from table \"${b.name}\"`;\n }\n if (!deepEqual(a.primaryKey, b.primaryKey)) {\n return `Primary key of table \"${a.name}\" has changed`;\n }\n const acols = Object.entries(a.columns).sort(byColumnPos);\n const bcols = Object.entries(b.columns).sort(byColumnPos);\n if (\n acols.length !== bcols.length ||\n acols.some(([aname, acol], i) => {\n const [bname, bcol] = bcols[i];\n return (\n aname !== bname ||\n acol.pos !== bcol.pos ||\n acol.typeOID !== bcol.typeOID ||\n acol.notNull !== bcol.notNull\n );\n })\n ) {\n return `Columns of table \"${a.name}\" have changed`;\n }\n return null;\n}\n\nexport function relationDifferent(a: PublishedTableSpec, b: PostgresRelation) {\n if (a.oid !== b.relationOid || a.schema !== b.schema || a.name !== b.name) {\n return true;\n }\n if (\n // The MessageRelation's `keyColumns` field contains the columns in column\n // declaration order, whereas the PublishedTableSpec's `primaryKey`\n // contains the columns in primary key (i.e. index) order. Do an\n // order-agnostic compare here since it is not possible to detect\n // key-order changes from the MessageRelation message alone.\n b.replicaIdentity === 'default' &&\n !equals(new Set(a.primaryKey), new Set(b.keyColumns))\n ) {\n return true;\n }\n const acols = Object.entries(a.columns).sort(byColumnPos);\n const bcols = b.columns;\n return (\n acols.length !== bcols.length ||\n acols.some(([aname, acol], i) => {\n const bcol = bcols[i];\n return aname !== bcol.name || acol.typeOID !== bcol.typeOid;\n })\n );\n}\n\nfunction translateError(e: unknown): Error {\n if (!(e instanceof Error)) {\n return new Error(String(e));\n }\n if (e instanceof postgres.PostgresError && e.code === PG_ADMIN_SHUTDOWN) {\n return new ShutdownSignal(e);\n }\n return e;\n}\nconst idString = (id: Identifier) => `${id.schema}.${id.name}`;\n\nfunction specsByID(published: PublishedSchema) {\n return [\n // It would have been nice to use a CustomKeyMap here, but we rely on set-utils\n // operations which use plain Sets.\n new Map(published.tables.map(t => [t.oid, t])),\n new Map(published.indexes.map(i => [idString(i), i])),\n ] as const;\n}\n\n/**\n * Determines if an index was structurally changed (e.g. constraint dropped\n * and recreated with different columns) vs cosmetically changed (e.g. the\n * index spec changed because the table or a column was renamed).\n *\n * Compares boolean properties directly and resolves column names to their\n * stable attnums (pg_attribute `attnum`) for the column comparison.\n */\nexport function isIndexStructurallyChanged(\n prev: PublishedIndexSpec,\n next: PublishedIndexSpec,\n prevTables: Map<number, PublishedTableWithReplicaIdentity>,\n nextTables: Map<number, PublishedTableWithReplicaIdentity>,\n): boolean {\n if (\n prev.unique !== next.unique ||\n prev.isPrimaryKey !== next.isPrimaryKey ||\n prev.isReplicaIdentity !== next.isReplicaIdentity ||\n prev.isImmediate !== next.isImmediate\n ) {\n return true;\n }\n\n const prevTable = findTableBySchemaAndName(\n prevTables,\n prev.schema,\n prev.tableName,\n );\n const nextTable = findTableBySchemaAndName(\n nextTables,\n next.schema,\n next.tableName,\n );\n if (!prevTable || !nextTable) {\n // Can't resolve tables; conservatively treat as changed.\n return true;\n }\n\n if ((prev.predicate === undefined) !== (next.predicate === undefined)) {\n return true;\n }\n if (prev.predicate && next.predicate) {\n let unresolved = false;\n const prevPredicate = mapIndexPredicateColumns(prev.predicate, name => {\n const pos = prevTable.columns[name]?.pos;\n if (pos === undefined) unresolved = true;\n return String(pos);\n });\n const nextPredicate = mapIndexPredicateColumns(next.predicate, name => {\n const pos = nextTable.columns[name]?.pos;\n if (pos === undefined) unresolved = true;\n return String(pos);\n });\n if (unresolved || !deepEqual(prevPredicate, nextPredicate)) {\n return true;\n }\n }\n\n const prevEntries = Object.entries(prev.columns);\n const nextEntries = Object.entries(next.columns);\n if (prevEntries.length !== nextEntries.length) {\n return true;\n }\n\n // Resolve column names → attnums and compare.\n const prevByAttnum = new Map<number | undefined, string>(\n prevEntries.map(([name, dir]) => [prevTable.columns[name]?.pos, dir]),\n );\n const nextByAttnum = new Map<number | undefined, string>(\n nextEntries.map(([name, dir]) => [nextTable.columns[name]?.pos, dir]),\n );\n\n if (prevByAttnum.has(undefined) || nextByAttnum.has(undefined)) {\n // Column not found in table spec; conservatively treat as changed.\n return true;\n }\n if (prevByAttnum.size !== nextByAttnum.size) {\n return true;\n }\n for (const [attnum, dir] of prevByAttnum) {\n if (nextByAttnum.get(attnum) !== dir) {\n return true;\n }\n }\n return false;\n}\n\nfunction findTableBySchemaAndName(\n tables: Map<number, PublishedTableWithReplicaIdentity>,\n schema: string,\n name: string,\n): PublishedTableWithReplicaIdentity | undefined {\n for (const table of tables.values()) {\n if (table.schema === schema && table.name === name) {\n return table;\n }\n }\n return undefined;\n}\n\nfunction columnsByID(\n columns: Record<string, ColumnSpec>,\n): Map<number, ColumnSpec & {name: string}> {\n const colsByID = new Map<number, ColumnSpec & {name: string}>();\n for (const [name, spec] of Object.entries(columns)) {\n // The `pos` field is the `attnum` in `pg_attribute`, which is a stable\n // identifier for the column in this table (i.e. never reused).\n colsByID.set(spec.pos, {...spec, name});\n }\n return colsByID;\n}\n\nfunction getMetadata(table: PublishedTableWithReplicaIdentity): TableMetadata {\n return {\n schemaOID: must(table.schemaOID),\n relationOID: table.oid,\n rowKey: Object.fromEntries(\n table.replicaIdentityColumns.map(k => [\n k,\n {attNum: table.columns[k].pos},\n ]),\n ),\n };\n}\n\n// Avoid sending the `columns` from the Postgres MessageRelation message.\n// They are not used downstream and the message can be large.\nfunction makeRelation(relation: PostgresRelation): MessageRelation {\n // Avoid sending the `columns` from the Postgres MessageRelation message.\n // They are not used downstream and the message can be large.\n const {columns: _, keyColumns, replicaIdentity, ...rest} = relation;\n return {\n ...rest,\n rowKey: {\n columns: keyColumns,\n type: replicaIdentity,\n },\n // For now, deprecated columns are sent for backwards compatibility.\n // These can be removed when bumping the MIN_PROTOCOL_VERSION to 5.\n keyColumns,\n replicaIdentity,\n };\n}\n\nfunction summarizeSchemaForLog(schema: PublishedSchema) {\n return {\n tables: schema.tables.length,\n indexes: schema.indexes.length,\n };\n}\n\nfunction summarizeReplicationEventForLog(event: ReplicationEvent): JSONObject {\n const {schema, ...rest} = event;\n const {previousSchema, ...eventWithoutSchemas} = rest;\n return {\n ...eventWithoutSchemas,\n ...(schema !== undefined && {schema: summarizeSchemaForLog(schema)}),\n ...(previousSchema !== undefined && {\n previousSchema:\n previousSchema === null ? null : summarizeSchemaForLog(previousSchema),\n }),\n };\n}\n\nclass UnsupportedSchemaChangeError extends Error {\n readonly name = 'UnsupportedSchemaChangeError';\n readonly description: string;\n readonly event: ReplicationEvent;\n\n constructor(\n description: string,\n event: ReplicationEvent,\n options?: ErrorOptions,\n ) {\n super(\n `Replication halted. Resync the replica to recover: ${description}`,\n options,\n );\n this.description = description;\n this.event = event;\n }\n}\n\nclass MissingEventTriggerSupport extends Error {\n readonly name = 'MissingEventTriggerSupport';\n\n constructor(msg: string) {\n super(\n `${msg}. Schema changes cannot be reliably replicated without event trigger support.`,\n );\n }\n}\n\n// TODO(0xcadams): should this be a ProtocolError?\nclass ShutdownSignal extends AbortError {\n readonly name = 'ShutdownSignal';\n\n constructor(cause: unknown) {\n super(\n 'shutdown signal received (e.g. another zero-cache taking over the replication stream)',\n {\n cause,\n },\n );\n }\n}\n\nfunction parseLogicalMessageContent<T>(\n lc: LogContext,\n msg: MessageMessage,\n schema: v.Type<T>,\n) {\n const {content} = msg;\n const str =\n content instanceof Buffer\n ? content.toString('utf-8')\n : new TextDecoder().decode(content);\n try {\n const json = JSON.parse(str);\n return v.parse(json, schema, 'passthrough');\n } catch (e) {\n lc.error?.(`unable to parse logical message content: ${String(e)}`, {\n message: {...msg, content: str},\n });\n throw e;\n }\n}\n"],"mappings":"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"}
@@ -34,6 +34,12 @@ export type InitialSyncOptions = {
34
34
  export type ReplicaOptions = {
35
35
  epoch: number;
36
36
  backupV5: boolean;
37
+ /**
38
+ * Whether the replica's slot is acked only after transactions are stored in
39
+ * the PG change log (see `pgChangeLog` in `ReplicaState`). Defaults to
40
+ * false, i.e. unknown.
41
+ */
42
+ pgChangeLog: boolean;
37
43
  };
38
44
  /** Server context to store with the initial sync metadata for debugging. */
39
45
  export type ServerContext = JSONObject;
@@ -41,7 +47,7 @@ export type ServerContext = JSONObject;
41
47
  * @returns The {@link ReplicaState} of the initialized replica, or `undefined`
42
48
  * for a shadow sync.
43
49
  */
44
- export declare function initialSync(lc: LogContext, shard: ShardConfig, tx: Database, upstreamURI: string, syncOptions: InitialSyncOptions, context: ServerContext, { epoch, backupV5 }?: ReplicaOptions, cleanup?: InitCleanup): Promise<ReplicaState | undefined>;
50
+ export declare function initialSync(lc: LogContext, shard: ShardConfig, tx: Database, upstreamURI: string, syncOptions: InitialSyncOptions, context: ServerContext, { epoch, backupV5, pgChangeLog }?: ReplicaOptions, cleanup?: InitCleanup): Promise<ReplicaState | undefined>;
45
51
  export type ShadowSyncOptions = {
46
52
  sampleRate: number;
47
53
  maxRowsPerTable: number;
@@ -1 +1 @@
1
- {"version":3,"file":"initial-sync.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/initial-sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"initial-sync.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/initial-sync.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,7 @@ import { BinaryCopyParser, hasBinaryDecoder, makeBinaryDecoder, textCastDecoder
26
26
  import { TsvParser } from "../../../db/pg-copy.js";
27
27
  import { getTypeParsers } from "../../../db/pg-type-parser.js";
28
28
  import { TransactionPool, importSnapshot } from "../../../db/transaction-pool.js";
29
+ import { endSession } from "./logical-replication/stream.js";
29
30
  import { createReplicaAndSlot } from "./replication-slots.js";
30
31
  import { ensureShardSchema } from "./schema/init.js";
31
32
  import { platform, tmpdir } from "node:os";
@@ -38,9 +39,10 @@ import { pipeline as pipeline$1 } from "node:stream/promises";
38
39
  * @returns The {@link ReplicaState} of the initialized replica, or `undefined`
39
40
  * for a shadow sync.
40
41
  */
41
- async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context, { epoch, backupV5 } = {
42
+ async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context, { epoch, backupV5, pgChangeLog } = {
42
43
  epoch: 0,
43
- backupV5: true
44
+ backupV5: true,
45
+ pgChangeLog: false
44
46
  }, cleanup) {
45
47
  if (!ALLOWED_APP_ID_CHARACTERS.test(shard.appID)) throw new Error("The App ID may only consist of lower-case letters, numbers, and the underscore character");
46
48
  const { tableCopyWorkers, profileCopy, textCopy = false, replicationSlotFailover = false, installPartialIndexTriggers = true, shadow } = syncOptions;
@@ -93,7 +95,8 @@ async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context, { e
93
95
  } else {
94
96
  const replication = await createReplicaAndSlot(lc, sql, "initial-sync-replication-session", shard, epoch, replicaID, replicationSlotFailover, {
95
97
  backupPath: backupV5 ? replicaID : null,
96
- backupV5
98
+ backupV5,
99
+ pgChangeLog
97
100
  }, captureSnapshot, 0);
98
101
  lsn = replication.slot.consistent_point;
99
102
  slotName = replication.slot.slot_name;
@@ -166,7 +169,7 @@ async function initialSync(lc, shard, tx, upstreamURI, syncOptions, context, { e
166
169
  });
167
170
  if (slotName && sql) {
168
171
  lc.warn?.(`dropping replication slot ${slotName}`, e);
169
- slotSession?.destroy();
172
+ await endSession(slotSession);
170
173
  await sql`
171
174
  SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots
172
175
  WHERE slot_name = ${slotName};