Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal-packages/replication/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ export class LogicalReplicationClient {
} else if (buffer[0] === 0x6b) {
// Primary keepalive message
const timestamp = Math.floor(
buffer.readUInt32BE(9) * 4294967.296 + buffer.readUInt32BE(13) / 1000 + 946080000000
buffer.readUInt32BE(9) * 4294967.296 + buffer.readUInt32BE(13) / 1000 + 946684800000
);
const shouldRespond = !!buffer.readInt8(17);
this.events.emit("heartbeat", { lsn, timestamp, shouldRespond });
Expand Down Expand Up @@ -658,7 +658,7 @@ export class LogicalReplicationClient {
const slice = lsn.split("/");
let [upperWAL, lowerWAL]: [number, number] = [parseInt(slice[0], 16), parseInt(slice[1], 16)];
// Timestamp as microseconds since midnight 2000-01-01
const now = Date.now() - 946080000000;
const now = Date.now() - 946684800000;
const upperTimestamp = Math.floor(now / 4294967.296);
const lowerTimestamp = Math.floor(now - upperTimestamp * 4294967.296);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Lower 32 bits of ACK timestamp computed in milliseconds instead of microseconds

The lowerTimestamp on line 663 computes the remainder of the 64-bit timestamp split in milliseconds instead of microseconds. The comment on line 660 states the timestamp should be "microseconds since midnight 2000-01-01", and the upper 32 bits are correctly derived from microseconds via now / 4294967.296 (which equals now * 1000 / 2^32). However, lowerTimestamp = Math.floor(now - upperTimestamp * 4294967.296) yields a value that is the remainder in milliseconds — it's ~1000× too small. The correct formula is Math.floor(now * 1000 - upperTimestamp * 4294967296) or equivalently Math.floor((now - upperTimestamp * 4294967.296) * 1000). You can verify by comparing the parsing logic at internal-packages/replication/src/client.ts:366 which correctly performs the inverse: upper * 4294967.296 + lower / 1000 — note the /1000 on the lower part, confirming the lower 32 bits are expected to be in microseconds. This pre-existing bug means the standby status update timestamp sent to PostgreSQL (used for pg_stat_replication.reply_time monitoring) can be off by up to ~71.6 minutes.

Suggested change
const lowerTimestamp = Math.floor(now - upperTimestamp * 4294967.296);
const lowerTimestamp = Math.floor((now - upperTimestamp * 4294967.296) * 1000);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

if (lowerWAL === 4294967295) {
Expand Down